Skip to content
Open
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
20 changes: 17 additions & 3 deletions lib/internals/querystring.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ const noEscape = new Int8Array([
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 112 - 127
]);

const needEscape = /[^A-Za-z0-9!\-.~'()*]/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex omits underscore from safe set

Medium Severity

The needEscape regex omits _, which noEscape treats as safe. Long strings containing underscores therefore miss the unchanged-return path and allocate a prefix plus a concatenation even when every character is already safe.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6bb6c67. Configure here.

// Below this length the tight per-character scanning loop is faster than a
// native regex pre-scan, because the regex call overhead dominates for short
// inputs. Above it, escaping only the tail is a clear win.
const SHORT_STRING_LENGTH = 16;

/**
* @param {string} str
* @returns {string}
Expand All @@ -32,9 +38,17 @@ function encodeString(str) {
const len = str.length;
if (len === 0) return "";

let out = "";
let lastPos = 0;
let i = 0;
// Fast path: for short strings we scan every character with a tight loop. For
// longer ones we first use a native regex scan to find the first character
// that needs escaping. When the whole string is already safe (the common case
// for already-escaped values) we can return it unchanged without the loop, and
// otherwise we resume the loop right where escaping starts.
const firstEscape = len < SHORT_STRING_LENGTH ? 0 : str.search(needEscape);
if (firstEscape === -1) return str;

let out = len < SHORT_STRING_LENGTH ? "" : str.slice(0, firstEscape);
let lastPos = firstEscape;
let i = firstEscape;

outer: for (; i < len; i++) {
let c = str.charCodeAt(i);
Expand Down
Loading