@@ -66,7 +66,7 @@ fn strip_prefix(uri: &Uri, prefix: &str) -> Option<Uri> {
66
66
67
67
match item {
68
68
Item :: Both ( path_segment, prefix_segment) => {
69
- if is_capture ( prefix_segment) || path_segment == prefix_segment {
69
+ if prefix_matches ( prefix_segment, path_segment) {
70
70
// the prefix segment is either a param, which matches anything, or
71
71
// it actually matches the path segment
72
72
* matching_prefix_length. as_mut ( ) . unwrap ( ) += path_segment. len ( ) ;
@@ -148,12 +148,67 @@ where
148
148
} )
149
149
}
150
150
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 ..] ) )
157
212
}
158
213
159
214
#[ derive( Debug ) ]
@@ -380,6 +435,27 @@ mod tests {
380
435
expected = Some ( "/a" ) ,
381
436
) ;
382
437
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
+
383
459
#[ quickcheck]
384
460
fn does_not_panic ( uri_and_prefix : UriAndPrefix ) -> bool {
385
461
let UriAndPrefix { uri, prefix } = uri_and_prefix;
0 commit comments