@@ -199,14 +199,16 @@ export class RedirectsMiddleware extends MiddlewareBase {
199
199
return modifyRedirects . length
200
200
? modifyRedirects . find ( ( redirect : RedirectInfo & { matchedQueryString ?: string } ) => {
201
201
// Modify the redirect pattern to ignore the language prefix in the path
202
- redirect . pattern = redirect . pattern . replace ( RegExp ( `^[^]?/${ language } /` , 'gi' ) , '' ) ;
202
+ // And escapes non-special "?" characters in a string or regex.
203
+ redirect . pattern = this . escapeNonSpecialQuestionMarks (
204
+ redirect . pattern . replace ( RegExp ( `^[^]?/${ language } /` , 'gi' ) , '' )
205
+ ) ;
203
206
204
207
// Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs
205
208
redirect . pattern = `/^\/${ redirect . pattern
206
209
. replace ( / ^ \/ | \/ $ / g, '' ) // Removes leading and trailing slashes
207
210
. replace ( / ^ \^ \/ | \/ \$ $ / g, '' ) // Removes unnecessary start (^) and end ($) anchors
208
211
. replace ( / ^ \^ | \$ $ / g, '' ) // Further cleans up anchors
209
- . replace ( / (?< ! \\ ) \? / g, '\\?' ) // Escapes question marks in the pattern
210
212
. replace ( / \$ \/ g i $ / g, '' ) } [\/]?$/i`; // Ensures the pattern allows an optional trailing slash
211
213
212
214
/**
@@ -272,7 +274,7 @@ export class RedirectsMiddleware extends MiddlewareBase {
272
274
*/
273
275
const splittedPathname = url . pathname
274
276
. split ( '/' )
275
- . filter ( ( route ) => route )
277
+ . filter ( ( route : string ) => route )
276
278
. map ( ( route ) => `path=${ route } ` ) ;
277
279
278
280
/**
@@ -362,4 +364,51 @@ export class RedirectsMiddleware extends MiddlewareBase {
362
364
] . some ( Boolean )
363
365
) ;
364
366
}
367
+
368
+ /**
369
+ * Escapes non-special "?" characters in a string or regex.
370
+ *
371
+ * - For regular strings, it escapes all unescaped "?" characters by adding a backslash (`\`).
372
+ * - For regex patterns (strings enclosed in `/.../`), it analyzes each "?" to determine if it has special meaning
373
+ * (e.g., `?` in `(abc)?`, `.*?`) or is just a literal character. Only literal "?" characters are escaped.
374
+ * @param {string } input - The input string or regex pattern.
375
+ * @returns {string } - The modified string or regex with non-special "?" characters escaped.
376
+ **/
377
+ private escapeNonSpecialQuestionMarks ( input : string ) : string {
378
+ const regexPattern = / (?< ! \\ ) \? / g; // Find unescaped "?" characters
379
+ const isRegex = input . startsWith ( '/' ) && input . endsWith ( '/' ) ; // Check if the string is a regex
380
+
381
+ if ( ! isRegex ) {
382
+ // If not a regex, escape all unescaped "?" characters
383
+ return input . replace ( regexPattern , '\\?' ) ;
384
+ }
385
+
386
+ // If it's a regex, analyze each "?" character
387
+ let result = '' ;
388
+ let lastIndex = 0 ;
389
+
390
+ let match ;
391
+ while ( ( match = regexPattern . exec ( input ) ) !== null ) {
392
+ const index = match . index ; // Position of "?" in the string
393
+ const before = input . slice ( 0 , index ) . replace ( / \s + $ / , '' ) ; // Context before "?"
394
+ const lastChar = before . slice ( - 1 ) ; // Last character before "?"
395
+
396
+ // Determine if the "?" is a special regex symbol
397
+ const isSpecialRegexSymbol = / [ \. \* \+ \) \[ \] ] $ / . test ( lastChar ) ;
398
+
399
+ if ( isSpecialRegexSymbol ) {
400
+ // If it's special, keep it as is
401
+ result += input . slice ( lastIndex , index + 1 ) ;
402
+ } else {
403
+ // If it's not special, escape it
404
+ result += input . slice ( lastIndex , index ) + '\\?' ;
405
+ }
406
+ lastIndex = index + 1 ;
407
+ }
408
+
409
+ // Append the remaining part of the string
410
+ result += input . slice ( lastIndex ) ;
411
+
412
+ return result ;
413
+ }
365
414
}
0 commit comments