-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
feat: add CORS-aware ETag modes and configurable query parser options #6908
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
Changes from all commits
f267d2c
e4002f0
5105f41
459662e
77bd92a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -50,6 +50,36 @@ exports.etag = createETagGenerator({ weak: false }) | |||||||||
|
|
||||||||||
| exports.wetag = createETagGenerator({ weak: true }) | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Return strong ETag for `body` including CORS headers. | ||||||||||
| * | ||||||||||
| * @param {String|Buffer} body | ||||||||||
| * @param {String} [encoding] | ||||||||||
| * @param {Object} [headers] | ||||||||||
| * @return {String} | ||||||||||
| * @api private | ||||||||||
| */ | ||||||||||
|
|
||||||||||
| exports.etagCors = createETagGenerator({ | ||||||||||
| weak: false, | ||||||||||
| includeHeaders: ['access-control-allow-origin'] | ||||||||||
| }) | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Return weak ETag for `body` including CORS headers. | ||||||||||
| * | ||||||||||
| * @param {String|Buffer} body | ||||||||||
| * @param {String} [encoding] | ||||||||||
| * @param {Object} [headers] | ||||||||||
| * @return {String} | ||||||||||
| * @api private | ||||||||||
| */ | ||||||||||
|
|
||||||||||
| exports.wetagCors = createETagGenerator({ | ||||||||||
| weak: true, | ||||||||||
| includeHeaders: ['access-control-allow-origin'] | ||||||||||
| }) | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Normalize the given `type`, for example "html" becomes "text/html". | ||||||||||
| * | ||||||||||
|
|
@@ -144,6 +174,12 @@ exports.compileETag = function(val) { | |||||||||
| case 'strong': | ||||||||||
| fn = exports.etag; | ||||||||||
| break; | ||||||||||
| case 'weak-cors': | ||||||||||
| fn = exports.wetagCors; | ||||||||||
| break; | ||||||||||
| case 'strong-cors': | ||||||||||
| fn = exports.etagCors; | ||||||||||
| break; | ||||||||||
| default: | ||||||||||
| throw new TypeError('unknown value for etag function: ' + val); | ||||||||||
| } | ||||||||||
|
|
@@ -155,11 +191,12 @@ exports.compileETag = function(val) { | |||||||||
| * Compile "query parser" value to function. | ||||||||||
| * | ||||||||||
| * @param {String|Function} val | ||||||||||
| * @param {Object} [qsOptions] - Options for qs parser | ||||||||||
| * @return {Function} | ||||||||||
| * @api private | ||||||||||
| */ | ||||||||||
|
|
||||||||||
| exports.compileQueryParser = function compileQueryParser(val) { | ||||||||||
| exports.compileQueryParser = function compileQueryParser(val, qsOptions) { | ||||||||||
| var fn; | ||||||||||
|
|
||||||||||
| if (typeof val === 'function') { | ||||||||||
|
|
@@ -174,7 +211,7 @@ exports.compileQueryParser = function compileQueryParser(val) { | |||||||||
| case false: | ||||||||||
| break; | ||||||||||
| case 'extended': | ||||||||||
| fn = parseExtendedQueryString; | ||||||||||
| fn = createExtendedQueryParser(qsOptions); | ||||||||||
| break; | ||||||||||
| default: | ||||||||||
| throw new TypeError('unknown value for query parser function: ' + val); | ||||||||||
|
|
@@ -242,30 +279,64 @@ exports.setCharset = function setCharset(type, charset) { | |||||||||
| * the given options. | ||||||||||
| * | ||||||||||
| * @param {object} options | ||||||||||
| * @param {boolean} options.weak - Generate weak ETags | ||||||||||
| * @param {string[]} options.includeHeaders - Response headers to include in hash | ||||||||||
| * @return {function} | ||||||||||
| * @private | ||||||||||
| */ | ||||||||||
|
|
||||||||||
| function createETagGenerator (options) { | ||||||||||
| return function generateETag (body, encoding) { | ||||||||||
| var weak = options.weak; | ||||||||||
| var includeHeaders = options.includeHeaders || []; | ||||||||||
|
|
||||||||||
| return function generateETag (body, encoding, headers) { | ||||||||||
| var buf = !Buffer.isBuffer(body) | ||||||||||
| ? Buffer.from(body, encoding) | ||||||||||
| : body | ||||||||||
| : body; | ||||||||||
|
|
||||||||||
| return etag(buf, options) | ||||||||||
| } | ||||||||||
| // If no headers to include, use body-only hashing (backward compatible) | ||||||||||
| if (includeHeaders.length === 0 || !headers) { | ||||||||||
| return etag(buf, { weak: weak }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Combine body with specified headers | ||||||||||
| var headerParts = includeHeaders | ||||||||||
| .map(function(name) { | ||||||||||
| var value = headers[name.toLowerCase()]; | ||||||||||
| return value ? String(value) : ''; | ||||||||||
| }) | ||||||||||
| .filter(Boolean); | ||||||||||
|
|
||||||||||
| if (headerParts.length === 0) { | ||||||||||
| // No headers present, fall back to body-only | ||||||||||
| return etag(buf, { weak: weak }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Create combined buffer: body + header values | ||||||||||
| var headerBuf = Buffer.from(headerParts.join('|'), 'utf8'); | ||||||||||
|
Comment on lines
+303
to
+316
|
||||||||||
| var combined = Buffer.concat([buf, Buffer.from('|'), headerBuf]); | ||||||||||
|
Comment on lines
+316
to
+317
|
||||||||||
| var headerBuf = Buffer.from(headerParts.join('|'), 'utf8'); | |
| var combined = Buffer.concat([buf, Buffer.from('|'), headerBuf]); | |
| var headerBuf = Buffer.from(headerParts.join('\x00'), 'utf8'); | |
| var combined = Buffer.concat([buf, Buffer.from('\x00'), headerBuf]); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting allowPrototypes: true as the default (line 333) maintains backward compatibility but leaves applications vulnerable to prototype pollution attacks unless developers explicitly set it to false. While the comment mentions "consider changing to false in v6", this is a security risk in the current version. Consider documenting this security concern prominently in the PR description and migration guide, or at minimum add a deprecation warning when this default is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] When
'query parser options'is set before'query parser'mode is set, the options will be stored in settings but won't be used until the parser mode is set. However, when'query parser'is subsequently set, it will correctly retrieve and use these options viathis.get('query parser options')on line 368. This behavior should be documented or the code should handle this edge case more explicitly to avoid confusion.