Skip to content

Commit 198d4bc

Browse files
fix: redefine opaque pathname detection for custom protocols (#61) (#62)
1 parent cfcf429 commit 198d4bc

File tree

2 files changed

+90
-20
lines changed

2 files changed

+90
-20
lines changed

src/canonicalize_and_process.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,24 @@ pub fn process_pathname_init(
211211
if kind == &ProcessType::Pattern {
212212
Ok(pathname_value.to_string())
213213
} else {
214-
match protocol_value {
214+
// A path is non-opaque if:
215+
// 1. The protocol is empty, OR
216+
// 2. The protocol is a special scheme (http, https, etc.), OR
217+
// 3. The pathname has a leading '/' (indicating hierarchical path)
218+
let is_non_opaque = match protocol_value {
215219
Some(protocol) if protocol.is_empty() || is_special_scheme(protocol) => {
216-
canonicalize_pathname(pathname_value)
220+
true
217221
}
218-
_ => canonicalize_an_opaque_pathname(pathname_value),
222+
_ => {
223+
// For non-special schemes, treat as non-opaque if pathname starts with '/'
224+
pathname_value.starts_with('/')
225+
}
226+
};
227+
228+
if is_non_opaque {
229+
canonicalize_pathname(pathname_value)
230+
} else {
231+
canonicalize_an_opaque_pathname(pathname_value)
219232
}
220233
}
221234
}

src/lib.rs

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -371,23 +371,38 @@ impl<R: RegExp> UrlPattern<R> {
371371
..Default::default()
372372
};
373373

374-
let pathname = if protocol.protocol_component_matches_special_scheme() {
375-
Component::compile(
376-
processed_init.pathname.as_deref(),
377-
canonicalize_and_process::canonicalize_pathname,
378-
parser::Options {
379-
ignore_case: options.ignore_case,
380-
..parser::Options::pathname()
381-
},
382-
)?
383-
.optionally_transpose_regex_error(report_regex_errors)?
384-
} else {
385-
Component::compile(
386-
processed_init.pathname.as_deref(),
387-
canonicalize_and_process::canonicalize_an_opaque_pathname,
388-
compile_options.clone(),
389-
)?
390-
.optionally_transpose_regex_error(report_regex_errors)?
374+
let pathname = {
375+
// Determine if path is non-opaque using the same criteria as process_pathname_init
376+
let protocol_is_empty = processed_init
377+
.protocol
378+
.as_ref()
379+
.is_some_and(|p| p.is_empty());
380+
let has_leading_slash = processed_init
381+
.pathname
382+
.as_ref()
383+
.is_some_and(|p| p.starts_with('/'));
384+
let is_non_opaque = protocol_is_empty
385+
|| protocol.protocol_component_matches_special_scheme()
386+
|| has_leading_slash;
387+
388+
if is_non_opaque {
389+
Component::compile(
390+
processed_init.pathname.as_deref(),
391+
canonicalize_and_process::canonicalize_pathname,
392+
parser::Options {
393+
ignore_case: options.ignore_case,
394+
..parser::Options::pathname()
395+
},
396+
)?
397+
.optionally_transpose_regex_error(report_regex_errors)?
398+
} else {
399+
Component::compile(
400+
processed_init.pathname.as_deref(),
401+
canonicalize_and_process::canonicalize_an_opaque_pathname,
402+
compile_options.clone(),
403+
)?
404+
.optionally_transpose_regex_error(report_regex_errors)?
405+
}
391406
};
392407

393408
Ok(UrlPattern {
@@ -1047,4 +1062,46 @@ mod tests {
10471062
.unwrap();
10481063
assert!(pattern.has_regexp_groups());
10491064
}
1065+
1066+
#[test]
1067+
fn issue61() {
1068+
// Test case for https://github.com/denoland/deno/issues/29935
1069+
// Custom protocols should not escape colons and slashes in pattern pathnames
1070+
1071+
// Test using init with pattern components
1072+
let pattern = <UrlPattern>::parse(
1073+
UrlPatternInit {
1074+
protocol: Some("myhttp".to_string()),
1075+
hostname: Some("example.com".to_string()),
1076+
pathname: Some("/:directory/:file".to_string()),
1077+
..Default::default()
1078+
},
1079+
Default::default(),
1080+
)
1081+
.unwrap();
1082+
1083+
println!("Pattern: {pattern:?}");
1084+
println!("Protocol: {}", pattern.protocol());
1085+
println!("Hostname: {}", pattern.hostname());
1086+
println!("Pathname: {}", pattern.pathname());
1087+
1088+
// The pathname should be "/:directory/:file", not "%2F:directory%2F:file"
1089+
assert_eq!(pattern.pathname().to_string(), "/:directory/:file");
1090+
1091+
// Also test myfile:///test case - empty hostname with leading slash
1092+
let myfile_pattern = <UrlPattern>::parse(
1093+
UrlPatternInit {
1094+
protocol: Some("myfile".to_string()),
1095+
hostname: Some("".to_string()), // empty hostname
1096+
pathname: Some("/test".to_string()),
1097+
..Default::default()
1098+
},
1099+
Default::default(),
1100+
)
1101+
.unwrap();
1102+
1103+
println!("\nMyfile pattern pathname: {}", myfile_pattern.pathname());
1104+
// Should use non-opaque canonicalization because of leading slash
1105+
assert_eq!(myfile_pattern.pathname().to_string(), "/test");
1106+
}
10501107
}

0 commit comments

Comments
 (0)