Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion gix-credentials/src/protocol/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ mod mutate {
self.username = url.user().map(ToOwned::to_owned);
self.password = url.password().map(ToOwned::to_owned);
self.host = url.host().map(ToOwned::to_owned).map(|mut host| {
if let Some(port) = url.port {
let port = url.port.filter(|port| {
url.scheme
.default_port()
.is_none_or(|default_port| *port != default_port)
});
if let Some(port) = port {
use std::fmt::Write;
write!(host, ":{port}").expect("infallible");
}
Expand Down
1 change: 0 additions & 1 deletion gix-url/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ gix-path = { version = "^0.10.21", path = "../gix-path" }

serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"] }
thiserror = "2.0.17"
url = "2.5.2"
bstr = { version = "1.12.0", default-features = false, features = ["std"] }
percent-encoding = "2.3.1"

Expand Down
3 changes: 3 additions & 0 deletions gix-url/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ mod impls;
///
pub mod parse;

/// Minimal URL parser to replace the `url` crate dependency
mod simple_url;

/// Parse the given `bytes` as a [git url](Url).
///
/// # Note
Expand Down
61 changes: 37 additions & 24 deletions gix-url/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub enum Error {
Url {
url: String,
kind: UrlKind,
source: url::ParseError,
source: crate::simple_url::UrlParseError,
},

#[error("The host portion of the following URL is too long ({} bytes, {len} bytes total): {truncated_url:?}", truncated_url.len())]
Expand Down Expand Up @@ -99,30 +99,33 @@ pub(crate) fn url(input: &BStr, protocol_end: usize) -> Result<crate::Url, Error
});
}
let (input, url) = input_to_utf8_and_url(input, UrlKind::Url)?;
let scheme = url.scheme().into();
let scheme = Scheme::from(url.scheme.as_str());

if matches!(scheme, Scheme::Git | Scheme::Ssh) && url.path().is_empty() {
if matches!(scheme, Scheme::Git | Scheme::Ssh) && url.path.is_empty() {
return Err(Error::MissingRepositoryPath {
url: input.into(),
kind: UrlKind::Url,
});
}

if url.cannot_be_a_base() {
return Err(Error::RelativeUrl { url: input.to_owned() });
}
// Normalize empty path to "/" for http/https URLs only
let path = if url.path.is_empty() && matches!(scheme, Scheme::Http | Scheme::Https) {
"/".into()
} else {
url.path.into()
};

Ok(crate::Url {
serialize_alternative_form: false,
scheme,
user: url_user(&url, UrlKind::Url)?,
password: url
.password()
.password
.map(|s| percent_decoded_utf8(s, UrlKind::Url))
.transpose()?,
host: url.host_str().map(Into::into),
port: url.port(),
path: url.path().into(),
host: url.host,
port: url.port,
path,
})
}

Expand Down Expand Up @@ -156,31 +159,32 @@ pub(crate) fn scp(input: &BStr, colon: usize) -> Result<crate::Url, Error> {
// should never differ in any other way (ssh URLs should not contain a query or fragment part).
// To avoid the various off-by-one errors caused by the `/` characters, we keep using the path
// determined above and can therefore skip parsing it here as well.
let url = url::Url::parse(&format!("ssh://{host}")).map_err(|source| Error::Url {
let url_string = format!("ssh://{host}");
let url = crate::simple_url::ParsedUrl::parse(&url_string).map_err(|source| Error::Url {
url: input.to_owned(),
kind: UrlKind::Scp,
source,
})?;

Ok(crate::Url {
serialize_alternative_form: true,
scheme: url.scheme().into(),
scheme: Scheme::from(url.scheme.as_str()),
user: url_user(&url, UrlKind::Scp)?,
password: url
.password()
.password
.map(|s| percent_decoded_utf8(s, UrlKind::Scp))
.transpose()?,
host: url.host_str().map(Into::into),
port: url.port(),
host: url.host,
port: url.port,
path: path.into(),
})
}

fn url_user(url: &url::Url, kind: UrlKind) -> Result<Option<String>, Error> {
if url.username().is_empty() && url.password().is_none() {
fn url_user(url: &crate::simple_url::ParsedUrl<'_>, kind: UrlKind) -> Result<Option<String>, Error> {
if url.username.is_empty() && url.password.is_none() {
Ok(None)
} else {
Ok(Some(percent_decoded_utf8(url.username(), kind)?))
Ok(Some(percent_decoded_utf8(url.username, kind)?))
}
}

Expand Down Expand Up @@ -269,13 +273,22 @@ fn input_to_utf8(input: &BStr, kind: UrlKind) -> Result<&str, Error> {
})
}

fn input_to_utf8_and_url(input: &BStr, kind: UrlKind) -> Result<(&str, url::Url), Error> {
fn input_to_utf8_and_url(input: &BStr, kind: UrlKind) -> Result<(&str, crate::simple_url::ParsedUrl<'_>), Error> {
let input = input_to_utf8(input, kind)?;
url::Url::parse(input)
crate::simple_url::ParsedUrl::parse(input)
.map(|url| (input, url))
.map_err(|source| Error::Url {
url: input.to_owned(),
kind,
source,
.map_err(|source| {
// If the parser rejected it as RelativeUrlWithoutBase, map to Error::RelativeUrl
// to match the expected error type for malformed URLs like "invalid:://"
match source {
crate::simple_url::UrlParseError::RelativeUrlWithoutBase => {
Error::RelativeUrl { url: input.to_owned() }
}
_ => Error::Url {
url: input.to_owned(),
kind,
source,
},
}
})
}
11 changes: 11 additions & 0 deletions gix-url/src/scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ impl Scheme {
Ext(name) => name.as_str(),
}
}

/// Return the default port for this scheme, or `None` if it is not known.
pub fn default_port(&self) -> Option<u16> {
match self {
Scheme::Http => Some(80),
Scheme::Https => Some(443),
Scheme::Ssh => Some(22),
Scheme::Git => Some(9418),
_ => None,
}
}
}

impl std::fmt::Display for Scheme {
Expand Down
Loading
Loading