From 20c393219fcc37eebfc5f9ac86576745ab661982 Mon Sep 17 00:00:00 2001 From: Ruslan Matkovskyi <100142572+sc-ruslanmatkovskyi@users.noreply.github.com> Date: Mon, 16 Dec 2024 08:33:46 +0100 Subject: [PATCH] [sitecore-jss-nextjs]: Updated logic to properly handle cases like [/]? #SXA-6320 (#1999) * [sitecore-jss-nextjs]: Updated logic to properly handle cases like [/]? * CHANGELOG has been changed --------- Co-authored-by: Ruslan Matkovskyi --- CHANGELOG.md | 4 ++ .../middleware/redirects-middleware.test.ts | 51 +++++++++++++++++ .../src/middleware/redirects-middleware.ts | 55 ++++++++++++++++++- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe0d7570cb..447bf0dd67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ Our versioning strategy is as follows: * `[templates/nextjs-sxa]` Fixed font-awesome import issue in custom workspace configuration ([#1998](https://github.com/Sitecore/jss/pull/1998)) +### 🐛 Bug Fixes + +* `[sitecore-jss-nextjs]` Fixed handling of ? inside square brackets [] in regex patterns to prevent incorrect escaping ([#1999](https://github.com/Sitecore/jss/pull/1999)) + ## 22.3.0 / 22.3.1 ### 🐛 Bug Fixes diff --git a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts index 571295891a..c6e572f31f 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.test.ts @@ -1411,6 +1411,57 @@ describe('RedirectsMiddleware', () => { expect(finalRes).to.deep.equal(res); expect(finalRes.status).to.equal(res.status); }); + + it('should return 301 redirect when pattern has special symbols "?"', async () => { + const cloneUrl = () => Object.assign({}, req.nextUrl); + const url = { + clone: cloneUrl, + href: 'http://localhost:3000/found?a=1&w=1', + locale: 'en', + origin: 'http://localhost:3000', + search: '?a=1&w=1', + pathname: '/found', + }; + + const { res, req } = createTestRequestResponse({ + response: { url }, + request: { + nextUrl: { + pathname: '/not-found/', + search: '?a=1&w=1', + href: 'http://localhost:3000/not-found/?a=1&w=1', + locale: 'en', + origin: 'http://localhost:3000', + clone: cloneUrl, + }, + }, + }); + setupRedirectStub(301); + + const { finalRes, fetchRedirects, siteResolver } = await runTestWithRedirect( + { + pattern: '/[/]?not-found?a=1&w=1/', + target: '/found', + redirectType: REDIRECT_TYPE_301, + isQueryStringPreserved: true, + locale: 'en', + }, + req + ); + + validateEndMessageDebugLog('redirects middleware end in %dms: %o', { + headers: {}, + redirected: undefined, + status: 301, + url, + }); + + expect(siteResolver.getByHost).to.be.calledWith(hostname); + // eslint-disable-next-line no-unused-expressions + expect(fetchRedirects.called).to.be.true; + expect(finalRes).to.deep.equal(res); + expect(finalRes.status).to.equal(res.status); + }); }); describe('should redirect to normalized path when nextjs specific "path" query string parameter is provided', () => { diff --git a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts index 3c40e86595..15a31667c5 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/redirects-middleware.ts @@ -199,14 +199,16 @@ export class RedirectsMiddleware extends MiddlewareBase { return modifyRedirects.length ? modifyRedirects.find((redirect: RedirectInfo & { matchedQueryString?: string }) => { // Modify the redirect pattern to ignore the language prefix in the path - redirect.pattern = redirect.pattern.replace(RegExp(`^[^]?/${language}/`, 'gi'), ''); + // And escapes non-special "?" characters in a string or regex. + redirect.pattern = this.escapeNonSpecialQuestionMarks( + redirect.pattern.replace(RegExp(`^[^]?/${language}/`, 'gi'), '') + ); // Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs redirect.pattern = `/^\/${redirect.pattern .replace(/^\/|\/$/g, '') // Removes leading and trailing slashes .replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors .replace(/^\^|\$$/g, '') // Further cleans up anchors - .replace(/(? route) + .filter((route: string) => route) .map((route) => `path=${route}`); /** @@ -362,4 +364,51 @@ export class RedirectsMiddleware extends MiddlewareBase { ].some(Boolean) ); } + + /** + * Escapes non-special "?" characters in a string or regex. + * + * - For regular strings, it escapes all unescaped "?" characters by adding a backslash (`\`). + * - For regex patterns (strings enclosed in `/.../`), it analyzes each "?" to determine if it has special meaning + * (e.g., `?` in `(abc)?`, `.*?`) or is just a literal character. Only literal "?" characters are escaped. + * @param {string} input - The input string or regex pattern. + * @returns {string} - The modified string or regex with non-special "?" characters escaped. + **/ + private escapeNonSpecialQuestionMarks(input: string): string { + const regexPattern = /(?