@@ -66,7 +66,7 @@ fn strip_prefix(uri: &Uri, prefix: &str) -> Option<Uri> {
6666
6767 match item {
6868 Item :: Both ( path_segment, prefix_segment) => {
69- if is_capture ( prefix_segment) || path_segment == prefix_segment {
69+ if prefix_matches ( prefix_segment, path_segment) {
7070 // the prefix segment is either a param, which matches anything, or
7171 // it actually matches the path segment
7272 * matching_prefix_length. as_mut ( ) . unwrap ( ) += path_segment. len ( ) ;
@@ -148,12 +148,67 @@ where
148148 } )
149149}
150150
151- fn is_capture ( segment : & str ) -> bool {
152- segment. starts_with ( '{' )
153- && segment. ends_with ( '}' )
154- && !segment. starts_with ( "{{" )
155- && !segment. ends_with ( "}}" )
156- && !segment. starts_with ( "{*" )
151+ fn prefix_matches ( prefix_segment : & str , path_segment : & str ) -> bool {
152+ if let Some ( ( prefix, suffix) ) = capture_prefix_suffix ( prefix_segment) {
153+ path_segment. starts_with ( prefix) && path_segment. ends_with ( suffix)
154+ } else {
155+ prefix_segment == path_segment
156+ }
157+ }
158+
159+ /// Takes a segment and returns prefix and suffix of the path, omitting the capture. Currently,
160+ /// matchit supports only one capture so this can be a pair. If there is no capture, `None` is
161+ /// returned.
162+ fn capture_prefix_suffix ( segment : & str ) -> Option < ( & str , & str ) > {
163+ fn find_first_not_double ( needle : u8 , haystack : & [ u8 ] ) -> Option < usize > {
164+ let mut possible_capture = 0 ;
165+ while let Some ( index) = haystack
166+ . get ( possible_capture..)
167+ . and_then ( |haystack| haystack. iter ( ) . position ( |byte| byte == & needle) )
168+ {
169+ let index = index + possible_capture;
170+
171+ if haystack. get ( index + 1 ) == Some ( & needle) {
172+ possible_capture = index + 2 ;
173+ continue ;
174+ }
175+
176+ return Some ( index) ;
177+ }
178+
179+ None
180+ }
181+
182+ let capture_start = find_first_not_double ( b'{' , segment. as_bytes ( ) ) ?;
183+
184+ let Some ( capture_end) = find_first_not_double ( b'}' , segment. as_bytes ( ) ) else {
185+ if cfg ! ( debug_assertions) {
186+ panic ! (
187+ "Segment `{segment}` is malformed. It seems to contain a capture start but no \
188+ capture end. This should have been rejected at application start, please file a \
189+ bug in axum repository."
190+ ) ;
191+ } else {
192+ // This is very bad but let's not panic in production. This will most likely not match.
193+ return None ;
194+ }
195+ } ;
196+
197+ if capture_start > capture_end {
198+ if cfg ! ( debug_assertions) {
199+ panic ! (
200+ "Segment `{segment}` is malformed. It seems to contain a capture start after \
201+ capture end. This should have been rejected at application start, please file a \
202+ bug in axum repository."
203+ ) ;
204+ } else {
205+ // This is very bad but let's not panic in production. This will most likely not match.
206+ return None ;
207+ }
208+ }
209+
210+ // Slicing may panic but we found the indexes inside the string so this should be fine.
211+ Some ( ( & segment[ ..capture_start] , & segment[ capture_end + 1 ..] ) )
157212}
158213
159214#[ derive( Debug ) ]
@@ -380,6 +435,27 @@ mod tests {
380435 expected = Some ( "/a" ) ,
381436 ) ;
382437
438+ test ! (
439+ param_14,
440+ uri = "/abc" ,
441+ prefix = "/a{b}c" ,
442+ expected = Some ( "/" ) ,
443+ ) ;
444+
445+ test ! (
446+ param_15,
447+ uri = "/z/abc/d" ,
448+ prefix = "/z/a{b}c" ,
449+ expected = Some ( "/d" ) ,
450+ ) ;
451+
452+ test ! (
453+ param_16,
454+ uri = "/abc/d/e" ,
455+ prefix = "/a{b}c/d/" ,
456+ expected = Some ( "/e" ) ,
457+ ) ;
458+
383459 #[ quickcheck]
384460 fn does_not_panic ( uri_and_prefix : UriAndPrefix ) -> bool {
385461 let UriAndPrefix { uri, prefix } = uri_and_prefix;
0 commit comments