use super::parse_graceful;
use crate::uri::{ResolveError, Uri};
const BASE: &str = "http://a/b/c/d;p?q";
fn resolve(base: &str, reference: &str) -> String {
let base: Uri = parse_graceful(base).unwrap();
let reference: Uri = Uri::parse_reference(reference).unwrap();
base.resolve(&reference).unwrap().to_string()
}
fn resolve_strict(base: &str, reference: &str) -> Result<String, ResolveError> {
let base: Uri = parse_graceful(base).unwrap();
let reference: Uri = Uri::parse_reference(reference).unwrap();
base.resolve_strict(&reference).map(|u| u.to_string())
}
#[test]
fn rfc_5_4_1_normal_examples() {
for (reference, expected) in [
("g:h", "g:h"),
("g", "http://a/b/c/g"),
("./g", "http://a/b/c/g"),
("g/", "http://a/b/c/g/"),
("/g", "http://a/g"),
("//g", "http://g"),
("?y", "http://a/b/c/d;p?y"),
("g?y", "http://a/b/c/g?y"),
("#s", "http://a/b/c/d;p?q#s"),
("g#s", "http://a/b/c/g#s"),
("g?y#s", "http://a/b/c/g?y#s"),
(";x", "http://a/b/c/;x"),
("g;x", "http://a/b/c/g;x"),
("g;x?y#s", "http://a/b/c/g;x?y#s"),
("", "http://a/b/c/d;p?q"),
(".", "http://a/b/c/"),
("./", "http://a/b/c/"),
("..", "http://a/b/"),
("../", "http://a/b/"),
("../g", "http://a/b/g"),
("../..", "http://a/"),
("../../", "http://a/"),
("../../g", "http://a/g"),
] {
assert_eq!(
resolve(BASE, reference),
expected,
"reference: {reference:?}"
);
}
}
#[test]
fn rfc_5_4_2_abnormal_examples() {
for (reference, expected) in [
("../../../g", "http://a/g"),
("../../../../g", "http://a/g"),
("/./g", "http://a/g"),
("/../g", "http://a/g"),
("g.", "http://a/b/c/g."),
(".g", "http://a/b/c/.g"),
("g..", "http://a/b/c/g.."),
("..g", "http://a/b/c/..g"),
("./../g", "http://a/b/g"),
("./g/.", "http://a/b/c/g/"),
("g/./h", "http://a/b/c/g/h"),
("g/../h", "http://a/b/c/h"),
("g;x=1/./y", "http://a/b/c/g;x=1/y"),
("g;x=1/../y", "http://a/b/c/y"),
("g?y/./x", "http://a/b/c/g?y/./x"),
("g?y/../x", "http://a/b/c/g?y/../x"),
("g#s/./x", "http://a/b/c/g#s/./x"),
("g#s/../x", "http://a/b/c/g#s/../x"),
] {
assert_eq!(
resolve(BASE, reference),
expected,
"reference: {reference:?}"
);
}
}
#[test]
fn graceful_applies_scheme_matching_loophole() {
let base: Uri = parse_graceful(BASE).unwrap();
let reference: Uri = Uri::parse_reference("http:g").unwrap();
assert_eq!(
base.resolve(&reference).unwrap().to_string(),
"http://a/b/c/g"
);
assert_eq!(
base.resolve_strict(&reference).unwrap().to_string(),
"http:g",
);
}
#[test]
fn loophole_does_not_apply_when_schemes_differ() {
assert_eq!(resolve(BASE, "mailto:g"), "mailto:g");
assert_eq!(resolve_strict(BASE, "mailto:g").unwrap(), "mailto:g",);
}
#[test]
fn graceful_clamps_dot_dot_at_root() {
assert_eq!(resolve(BASE, "../../../g"), "http://a/g");
assert_eq!(resolve(BASE, "../../../../../../g"), "http://a/g");
}
#[test]
fn strict_rejects_dot_dot_traversal_past_root() {
let base: Uri = parse_graceful(BASE).unwrap();
let reference: Uri = Uri::parse_reference("../../../g").unwrap();
let err = base.resolve_strict(&reference).unwrap_err();
assert_eq!(err, ResolveError::DotSegmentTraversalPastRoot);
}
#[test]
fn strict_allows_exact_root_traversal() {
assert_eq!(resolve_strict(BASE, "../..").unwrap(), "http://a/");
let base: Uri = parse_graceful(BASE).unwrap();
let reference: Uri = Uri::parse_reference("../../..").unwrap();
assert!(matches!(
base.resolve_strict(&reference),
Err(ResolveError::DotSegmentTraversalPastRoot),
));
}
#[test]
fn base_without_scheme_errors() {
let base: Uri = parse_graceful("/p").unwrap();
let reference: Uri = Uri::parse_reference("foo").unwrap();
assert_eq!(
base.resolve(&reference).unwrap_err(),
ResolveError::BaseHasNoScheme,
);
}
#[test]
fn asterisk_as_base_or_reference_errors() {
let star: Uri = parse_graceful("*").unwrap();
let normal: Uri = parse_graceful("http://a/b").unwrap();
assert_eq!(
star.resolve(&normal).unwrap_err(),
ResolveError::AsteriskNotResolvable,
);
assert_eq!(
normal.resolve(&star).unwrap_err(),
ResolveError::AsteriskNotResolvable,
);
}
#[test]
fn merge_empty_base_path_with_authority() {
let base: Uri = parse_graceful("http://a").unwrap();
let reference: Uri = Uri::parse_reference("g").unwrap();
assert_eq!(base.resolve(&reference).unwrap().to_string(), "http://a/g");
}
#[test]
fn fragment_taken_from_reference_unconditionally() {
assert_eq!(resolve("http://a/b?q", "#frag"), "http://a/b?q#frag");
assert_eq!(resolve("http://a/b#oldfrag", "g"), "http://a/g");
assert_eq!(resolve("http://a/b#old", "g#new"), "http://a/g#new");
}
#[test]
fn branch_3_empty_ref_inherits_query() {
assert_eq!(resolve("http://a/b?q", ""), "http://a/b?q");
}
#[test]
fn branch_3_fragment_only_ref_inherits_query() {
assert_eq!(resolve("http://a/b?q", "#s"), "http://a/b?q#s");
}
#[test]
fn branch_3_query_only_ref_overrides_query() {
assert_eq!(resolve("http://a/b?q", "?y"), "http://a/b?y");
}
#[test]
fn opaque_base_resolution() {
assert_eq!(resolve("mailto:foo", "bar"), "mailto:bar");
}
#[test]
fn reference_with_different_scheme_passes_through() {
assert_eq!(
resolve("http://a/b/", "ftp://example.com/x/./y"),
"ftp://example.com/x/y",
);
}
#[test]
fn resolved_uri_reparses_to_same_string() {
let cases = [
("g", "http://a/b/c/g"),
("../../../g", "http://a/g"),
("g?y#s", "http://a/b/c/g?y#s"),
];
for (reference, _expected) in cases {
let resolved = resolve(BASE, reference);
let reparsed: Uri = resolved.parse().unwrap();
assert_eq!(reparsed.to_string(), resolved, "round-trip {reference:?}");
}
}
#[test]
fn resolve_too_long_result_returns_typed_error() {
let base = Uri::parse("http://a/").unwrap();
let oversized_path = "/".to_owned() + &"a".repeat(65529);
let reference = Uri::parse_reference(oversized_path.as_str()).unwrap();
let err = base.resolve(&reference).unwrap_err();
match err {
ResolveError::ResultTooLong { len } => {
assert!(
len > u16::MAX as usize - 1,
"expected result length above cap, got {len}"
);
}
other => panic!("expected ResultTooLong, got {other:?}"),
}
}