Skip to content

Commit

Permalink
feat: add Url::host_argument_safe() and Url::path_argument_safe()
Browse files Browse the repository at this point in the history
This will not provide values if they could be confused for an argument
to to a commaneline application.
  • Loading branch information
Byron committed Sep 24, 2023
1 parent 54a8495 commit d80b5f6
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 0 deletions.
36 changes: 36 additions & 0 deletions gix-url/src/lib.rs
Expand Up @@ -48,6 +48,13 @@ pub struct Url {
/// The port to use when connecting to a host. If `None`, standard ports depending on `scheme` will be used.
pub port: Option<u16>,
/// The path portion of the URL, usually the location of the git repository.
///
/// # Security-Warning
///
/// URLs allow paths to start with `-` which makes it possible to mask command-line arguments as path which then leads to
/// the invocation of programs from an attacker controlled URL. See https://secure.phabricator.com/T12961 for details.
///
/// If this value is going to be used in a command-line application, call [Self::path_argument_safe()] instead.
pub path: bstr::BString,
}

Expand Down Expand Up @@ -123,9 +130,34 @@ impl Url {
self.password.as_deref()
}
/// Returns the host mentioned in the url, if present.
///
/// # Security-Warning
///
/// URLs allow hosts to start with `-` which makes it possible to mask command-line arguments as host which then leads to
/// the invocation of programs from an attacker controlled URL. See https://secure.phabricator.com/T12961 for details.
///
/// If this value is going to be used in a command-line application, call [Self::host_argument_safe()] instead.
pub fn host(&self) -> Option<&str> {
self.host.as_deref()
}

/// Return the host of this URL if present *and* if it can't be mistaken for a command-line argument.
///
/// Use this method if the host is going to be passed to a command-line application.
pub fn host_argument_safe(&self) -> Option<&str> {
self.host().filter(|host| !looks_like_argument(host.as_bytes()))
}

/// Return the path of this URL *and* if it can't be mistaken for a command-line argument.
/// Note that it always begins with a slash, which is ignored for this comparison.
///
/// Use this method if the path is going to be passed to a command-line application.
pub fn path_argument_safe(&self) -> Option<&BStr> {
self.path
.get(1..)
.and_then(|truncated| (!looks_like_argument(truncated)).then_some(self.path.as_ref()))
}

/// Returns true if the path portion of the url is `/`.
pub fn path_is_root(&self) -> bool {
self.path == "/"
Expand All @@ -146,6 +178,10 @@ impl Url {
}
}

fn looks_like_argument(b: &[u8]) -> bool {
b.get(0) == Some(&b'-')
}

/// Transformation
impl Url {
/// Turn a file url like `file://relative` into `file:///root/relative`, hence it assures the url's path component is absolute, using
Expand Down
20 changes: 20 additions & 0 deletions gix-url/tests/access/mod.rs
Expand Up @@ -29,3 +29,23 @@ mod canonicalized {
Ok(())
}
}

#[test]
fn host_argument_safe() -> crate::Result {
let url = gix_url::parse("ssh://-oProxyCommand=open$IFS-aCalculator/foo".into())?;
assert_eq!(url.host(), Some("-oProxyCommand=open$IFS-aCalculator"));
assert_eq!(url.host_argument_safe(), None);
assert_eq!(url.path, "/foo");
assert_eq!(url.path_argument_safe(), Some("/foo".into()));
Ok(())
}

#[test]
fn path_argument_safe() -> crate::Result {
let url = gix_url::parse("ssh://foo/-oProxyCommand=open$IFS-aCalculator".into())?;
assert_eq!(url.host(), Some("foo"));
assert_eq!(url.host_argument_safe(), Some("foo"));
assert_eq!(url.path, "/-oProxyCommand=open$IFS-aCalculator");
assert_eq!(url.path_argument_safe(), None);
Ok(())
}

0 comments on commit d80b5f6

Please sign in to comment.