Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sitecore-jss]: The logic of method escapeNonSpecialQuestionMarks has been fixed #SXA-7975 #2014

Merged
merged 2 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Our versioning strategy is as follows:
* `[sitecore-jss-nextjs]` Fixed handling of ? inside square brackets [] in regex patterns to prevent incorrect escaping ([#1999](https://github.com/Sitecore/jss/pull/1999))
* `[sitecore-jss-nextjs]` Improve performance for redirect middleware ([#2003](https://github.com/Sitecore/jss/pull/2003))
* `[templates/nextjs]` `[templates/nextjs-sxa]` Fixed condition of DISABLE_FETCH_SSG ([#2007](https://github.com/Sitecore/jss/pull/2007))
* `[sitecore-jss]` Fixed incorrect escaping of question marks in negative lookahead patterns in `escapeNonSpecialQuestionMarks` function ([#2014](https://github.com/Sitecore/jss/pull/2014))


### 🛠 Breaking Change

Expand Down
50 changes: 31 additions & 19 deletions packages/sitecore-jss/src/utils/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,45 +297,57 @@ describe('utils', () => {
});

describe('escapeNonSpecialQuestionMarks', () => {
it('should escape non-special "?" in plain strings', () => {
const input = 'What? is this? really?';
const expected = 'What\\? is this\\? really\\?';
it('should escape simple unescaped question marks', () => {
const input = 'abc?def?ghi';
const expected = 'abc\\?def\\?ghi';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should not escape special regex "?" characters', () => {
const input = '(abc)? .*? [a-z]?';
const expected = '(abc)? .*? [a-z]?';
it('should not escape question marks in negative lookaheads', () => {
const input = 'abc(?!def)?ghi';
const expected = 'abc(?!def)?ghi';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should handle mixed cases with special and non-special "?"', () => {
const input = 'Is this (true)? or false?';
const expected = 'Is this (true)? or false\\?';
it('should not escape question marks following special regex symbols', () => {
const input = 'abc.*?def+?ghi';
const expected = 'abc.*?def+?ghi';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should escape "?" in a string with no special characters', () => {
const input = 'Just a plain string?';
const expected = 'Just a plain string\\?';
it('should escape mixed cases correctly', () => {
const input = 'abc?de(?!f)?g?hi.*?';
const expected = 'abc\\?de(?!f)?g\\?hi.*?';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should handle strings with escaped "?" already', () => {
const input = 'This \\? should stay escaped?';
const expected = 'This \\? should stay escaped\\?';
it('should handle strings without question marks', () => {
const input = 'abcdefghi';
const expected = 'abcdefghi';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should handle an empty string', () => {
it('should handle escaped question marks', () => {
const input = 'abc\\?def?ghi';
const expected = 'abc\\?def\\?ghi';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should handle empty strings', () => {
const input = '';
const expected = '';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should handle strings without any "?"', () => {
const input = 'No question marks here.';
const expected = 'No question marks here.';
it('should handle consecutive unescaped question marks', () => {
const input = 'abc??def';
const expected = 'abc\\?\\?def';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});

it('should handle consecutive special symbols with question marks', () => {
const input = 'abc.*??ghi';
const expected = 'abc.*?\\?ghi';
expect(escapeNonSpecialQuestionMarks(input)).to.equal(expected);
});
});
Expand Down
30 changes: 17 additions & 13 deletions packages/sitecore-jss/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,34 +197,38 @@ export const areURLSearchParamsEqual = (params1: URLSearchParams, params2: URLSe
* 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.
* (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.
*/
export const escapeNonSpecialQuestionMarks = (input: string): string => {
const regexPattern = /(?<!\\)\?/g; // Find unescaped "?" characters
const regexPattern = /(?<!\\)\?/g; // Match unescaped "?" characters
const negativeLookaheadPattern = /\(\?!$/; // Detect the start of a Negative Lookahead pattern
const specialRegexSymbols = /[.*+)\[\]|\(]$/; // Check for special regex symbols before "?"

// If it's a regex, analyze each "?" character
let result = '';
let lastIndex = 0;

let match;
let match: RegExpExecArray | null;
while ((match = regexPattern.exec(input)) !== null) {
const index = match.index; // Position of "?" in the string
const before = input.slice(0, index).replace(/\s+$/, ''); // Context before "?"
const lastChar = before.slice(-1); // Last character before "?"
const index = match.index; // Position of the "?" in the string
const before = input.slice(lastIndex, index); // Context before the "?"

// Determine if the "?" is a special regex symbol
const isSpecialRegexSymbol = /[\.\*\+\)\[\]]$/.test(lastChar);
// Check if "?" is part of a Negative Lookahead
const isNegativeLookahead = negativeLookaheadPattern.test(before.slice(-3));

if (isSpecialRegexSymbol) {
// If it's special, keep it as is
// Check if "?" follows a special regex symbol
const isSpecialRegexSymbol = specialRegexSymbols.test(before.slice(-1));

if (isNegativeLookahead || isSpecialRegexSymbol) {
// If it's a special case, keep the "?" as is
result += input.slice(lastIndex, index + 1);
} else {
// If it's not special, escape it
// Otherwise, escape the "?"
result += input.slice(lastIndex, index) + '\\?';
}
lastIndex = index + 1;

lastIndex = index + 1; // Move to the next part of the string
}

// Append the remaining part of the string
Expand Down