@@ -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 ! ( "\n Myfile 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