Skip to content

Commit

Permalink
Close a loophole in reference resolution algorithm
Browse files Browse the repository at this point in the history
Also add more fuzz targets against `iri-string`
  • Loading branch information
yescallop committed Apr 13, 2024
1 parent 0e07c71 commit 6755c5c
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 8 deletions.
12 changes: 12 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,15 @@ name = "against_uriparser"
path = "fuzz_targets/against_uriparser.rs"
test = false
doc = false

[[bin]]
name = "resolve_against_iri_string"
path = "fuzz_targets/resolve_against_iri_string.rs"
test = false
doc = false

[[bin]]
name = "normalize_against_iri_string"
path = "fuzz_targets/normalize_against_iri_string.rs"
test = false
doc = false
22 changes: 22 additions & 0 deletions fuzz/fuzz_targets/normalize_against_iri_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#![no_main]
use fluent_uri::Uri;
use iri_string::{format::ToDedicatedString, types::UriStr};
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &str| {
let Ok(u1) = Uri::parse(data) else { return };

if u1.is_relative_reference() || u1.path().is_rootless() {
return;
}

let u2 = UriStr::new(data).unwrap();

let u1 = u1.normalize();

// if u1.path() == "/.//" {
// return;
// }

assert_eq!(u1.as_str(), u2.normalize().to_dedicated_string().as_str());
});
30 changes: 30 additions & 0 deletions fuzz/fuzz_targets/resolve_against_iri_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#![no_main]
use fluent_uri::Uri;
use iri_string::{
format::ToDedicatedString,
types::{UriAbsoluteStr, UriReferenceStr},
};
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: (&str, &str)| {
let (Ok(base), Ok(r)) = (Uri::parse(data.0), Uri::parse(data.1)) else {
return;
};

if r.path().is_rootless() {
return;
}

let Ok(u1) = r.resolve(&base) else { return };

// if u1.path() == "/.//" {
// return;
// }

let base = UriAbsoluteStr::new(data.0).unwrap();
let r = UriReferenceStr::new(data.1).unwrap();

let u2 = r.resolve_against(base).to_dedicated_string();

assert_eq!(u1.as_str(), u2.as_str());
});
45 changes: 37 additions & 8 deletions src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,45 @@ pub(crate) fn resolve(
/// Removes dot segments from an absolute path.
pub(crate) fn remove_dot_segments<'a>(buf: &'a mut String, path: &str) -> &'a str {
for seg in path.split_inclusive('/') {
if seg == "." || seg == "./" {
buf.truncate(buf.rfind('/').unwrap() + 1);
} else if seg == ".." || seg == "../" {
if buf.len() != 1 {
buf.truncate(buf.rfind('/').unwrap());
buf.truncate(buf.rfind('/').unwrap() + 1);
match classify_segment(seg) {
SegKind::Dot => buf.truncate(buf.rfind('/').unwrap() + 1),
SegKind::DoubleDot => {
if buf.len() != 1 {
buf.truncate(buf.rfind('/').unwrap());
buf.truncate(buf.rfind('/').unwrap() + 1);
}
}
} else {
buf.push_str(seg);
SegKind::Normal => buf.push_str(seg),
}
}
buf
}

enum SegKind {
Dot,
DoubleDot,
Normal,
}

fn classify_segment(mut seg: &str) -> SegKind {
if let Some(rem) = seg.strip_suffix('/') {
seg = rem;
}
if seg.is_empty() {
return SegKind::Normal;
}
if let Some(rem) = seg.strip_prefix('.') {
seg = rem;
} else if let Some(rem) = seg.strip_prefix("%2E") {
seg = rem;
} else if let Some(rem) = seg.strip_prefix("%2e") {
seg = rem;
}
if seg.is_empty() {
SegKind::Dot
} else if seg == "." || seg == "%2E" || seg == "%2e" {
SegKind::DoubleDot
} else {
SegKind::Normal
}
}

0 comments on commit 6755c5c

Please sign in to comment.