From 31c9323093481500421d35d6122d4a2e64d27b7d Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Fri, 8 Dec 2023 20:32:27 +0900 Subject: [PATCH 1/4] perf: Improve processHeader --- lib/core/request.js | 78 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/lib/core/request.js b/lib/core/request.js index 0f89f318568..c6d887d3fd5 100644 --- a/lib/core/request.js +++ b/lib/core/request.js @@ -7,17 +7,11 @@ const { const assert = require('assert') const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = require('./symbols') const util = require('./util') +const { headerNameLowerCasedRecord } = require('./constants') -// tokenRegExp and headerCharRegex have been lifted from +// headerCharRegex have been lifted from // https://github.com/nodejs/node/blob/main/lib/_http_common.js -/** - * Verifies that the given val is a valid HTTP token - * per the rules defined in RFC 7230 - * See https://tools.ietf.org/html/rfc7230#section-3.2.6 - */ -const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/ - /** * Matches if val contains an invalid field-vchar * field-value = *( field-content / obs-fold ) @@ -416,6 +410,72 @@ function processHeader (request, key, val, skipAppend = false) { return } + const mayBeLowerCasedKey = headerNameLowerCasedRecord[key] + + if (mayBeLowerCasedKey !== undefined) { + switch (mayBeLowerCasedKey) { + case 'host': { + if (request.host !== null) break + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + // Consumed by Client + request.host = val + return + } + case 'content-length': { + if (request.contentLength !== null) break + request.contentLength = parseInt(val, 10) + if (!Number.isFinite(request.contentLength)) { + throw new InvalidArgumentError('invalid content-length header') + } + return + } + case 'content-type': { + if (request.contentType !== null) break + request.contentType = val + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) + return + } + case 'transfer-encoding': { + throw new InvalidArgumentError('invalid transfer-encoding header') + } + case 'connection': { + const value = typeof val === 'string' ? val.toLowerCase() : null + if (value !== 'close' && value !== 'keep-alive') { + throw new InvalidArgumentError('invalid connection header') + } else if (value === 'close') { + request.reset = true + } + return + } + case 'keep-alive': { + throw new InvalidArgumentError('invalid keep-alive header') + } + case 'upgrade': { + throw new InvalidArgumentError('invalid upgrade header') + } + case 'expect': { + throw new NotSupportedError('expect header not supported') + } + } + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + if (skipAppend) { + if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` + else request.headers[key] = processHeaderValue(key, val[i], skipAppend) + } else { + request.headers += processHeaderValue(key, val[i]) + } + } + } else { + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) + } + return + } + if ( request.host === null && key.length === 4 && @@ -473,7 +533,7 @@ function processHeader (request, key, val, skipAppend = false) { key.toLowerCase() === 'expect' ) { throw new NotSupportedError('expect header not supported') - } else if (tokenRegExp.exec(key) === null) { + } else if (!util.isValidHTTPToken(key)) { throw new InvalidArgumentError('invalid header key') } else { if (Array.isArray(val)) { From 3029aa21212a3cdb4bb373f74e0235e7b984e815 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:07:45 +0900 Subject: [PATCH 2/4] rewrite --- lib/core/request.js | 189 ++++++++++++++------------------------------ 1 file changed, 59 insertions(+), 130 deletions(-) diff --git a/lib/core/request.js b/lib/core/request.js index c6d887d3fd5..936e11d8db1 100644 --- a/lib/core/request.js +++ b/lib/core/request.js @@ -410,145 +410,74 @@ function processHeader (request, key, val, skipAppend = false) { return } - const mayBeLowerCasedKey = headerNameLowerCasedRecord[key] - - if (mayBeLowerCasedKey !== undefined) { - switch (mayBeLowerCasedKey) { - case 'host': { - if (request.host !== null) break - if (headerCharRegex.exec(val) !== null) { - throw new InvalidArgumentError(`invalid ${key} header`) - } - // Consumed by Client - request.host = val - return - } - case 'content-length': { - if (request.contentLength !== null) break - request.contentLength = parseInt(val, 10) - if (!Number.isFinite(request.contentLength)) { - throw new InvalidArgumentError('invalid content-length header') - } - return - } - case 'content-type': { - if (request.contentType !== null) break - request.contentType = val - if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) - else request.headers += processHeaderValue(key, val) - return - } - case 'transfer-encoding': { - throw new InvalidArgumentError('invalid transfer-encoding header') - } - case 'connection': { - const value = typeof val === 'string' ? val.toLowerCase() : null - if (value !== 'close' && value !== 'keep-alive') { - throw new InvalidArgumentError('invalid connection header') - } else if (value === 'close') { - request.reset = true - } - return - } - case 'keep-alive': { - throw new InvalidArgumentError('invalid keep-alive header') - } - case 'upgrade': { - throw new InvalidArgumentError('invalid upgrade header') - } - case 'expect': { - throw new NotSupportedError('expect header not supported') + let headerName = headerNameLowerCasedRecord[key] + + if (headerName === undefined) { + headerName = key.toLowerCase() + if (!util.isValidHTTPToken(headerName)) { + throw new InvalidArgumentError('invalid header key') + } + } + + switch (headerName) { + case 'host': { + if (request.host !== null) break + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) } + // Consumed by Client + request.host = val + return } - if (Array.isArray(val)) { - for (let i = 0; i < val.length; i++) { - if (skipAppend) { - if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` - else request.headers[key] = processHeaderValue(key, val[i], skipAppend) - } else { - request.headers += processHeaderValue(key, val[i]) - } + case 'content-length': { + if (request.contentLength !== null) break + request.contentLength = parseInt(val, 10) + if (!Number.isFinite(request.contentLength)) { + throw new InvalidArgumentError('invalid content-length header') } - } else { + return + } + case 'content-type': { + if (request.contentType !== null) break + request.contentType = val if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) else request.headers += processHeaderValue(key, val) + return + } + case 'transfer-encoding': { + throw new InvalidArgumentError('invalid transfer-encoding header') + } + case 'connection': { + const value = typeof val === 'string' ? val.toLowerCase() : null + if (value !== 'close' && value !== 'keep-alive') { + throw new InvalidArgumentError('invalid connection header') + } else if (value === 'close') { + request.reset = true + } + return + } + case 'keep-alive': { + throw new InvalidArgumentError('invalid keep-alive header') + } + case 'upgrade': { + throw new InvalidArgumentError('invalid upgrade header') + } + case 'expect': { + throw new NotSupportedError('expect header not supported') } - return } - - if ( - request.host === null && - key.length === 4 && - key.toLowerCase() === 'host' - ) { - if (headerCharRegex.exec(val) !== null) { - throw new InvalidArgumentError(`invalid ${key} header`) - } - // Consumed by Client - request.host = val - } else if ( - request.contentLength === null && - key.length === 14 && - key.toLowerCase() === 'content-length' - ) { - request.contentLength = parseInt(val, 10) - if (!Number.isFinite(request.contentLength)) { - throw new InvalidArgumentError('invalid content-length header') - } - } else if ( - request.contentType === null && - key.length === 12 && - key.toLowerCase() === 'content-type' - ) { - request.contentType = val - if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) - else request.headers += processHeaderValue(key, val) - } else if ( - key.length === 17 && - key.toLowerCase() === 'transfer-encoding' - ) { - throw new InvalidArgumentError('invalid transfer-encoding header') - } else if ( - key.length === 10 && - key.toLowerCase() === 'connection' - ) { - const value = typeof val === 'string' ? val.toLowerCase() : null - if (value !== 'close' && value !== 'keep-alive') { - throw new InvalidArgumentError('invalid connection header') - } else if (value === 'close') { - request.reset = true - } - } else if ( - key.length === 10 && - key.toLowerCase() === 'keep-alive' - ) { - throw new InvalidArgumentError('invalid keep-alive header') - } else if ( - key.length === 7 && - key.toLowerCase() === 'upgrade' - ) { - throw new InvalidArgumentError('invalid upgrade header') - } else if ( - key.length === 6 && - key.toLowerCase() === 'expect' - ) { - throw new NotSupportedError('expect header not supported') - } else if (!util.isValidHTTPToken(key)) { - throw new InvalidArgumentError('invalid header key') - } else { - if (Array.isArray(val)) { - for (let i = 0; i < val.length; i++) { - if (skipAppend) { - if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` - else request.headers[key] = processHeaderValue(key, val[i], skipAppend) - } else { - request.headers += processHeaderValue(key, val[i]) - } + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + if (skipAppend) { + if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` + else request.headers[key] = processHeaderValue(key, val[i], skipAppend) + } else { + request.headers += processHeaderValue(key, val[i]) } - } else { - if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) - else request.headers += processHeaderValue(key, val) } + } else { + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) } } From 198ee2194b0df8b595ff8fefd53099756d2154ab Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:38:07 +0900 Subject: [PATCH 3/4] use if statement --- lib/core/request.js | 91 ++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/lib/core/request.js b/lib/core/request.js index 936e11d8db1..6084a3f198d 100644 --- a/lib/core/request.js +++ b/lib/core/request.js @@ -414,70 +414,51 @@ function processHeader (request, key, val, skipAppend = false) { if (headerName === undefined) { headerName = key.toLowerCase() - if (!util.isValidHTTPToken(headerName)) { + if (headerNameLowerCasedRecord[headerName] === undefined && !util.isValidHTTPToken(headerName)) { throw new InvalidArgumentError('invalid header key') } } - switch (headerName) { - case 'host': { - if (request.host !== null) break - if (headerCharRegex.exec(val) !== null) { - throw new InvalidArgumentError(`invalid ${key} header`) - } - // Consumed by Client - request.host = val - return + if (request.host !== null && headerName === 'host') { + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) } - case 'content-length': { - if (request.contentLength !== null) break - request.contentLength = parseInt(val, 10) - if (!Number.isFinite(request.contentLength)) { - throw new InvalidArgumentError('invalid content-length header') - } - return + // Consumed by Client + request.host = val + } else if (request.contentLength !== null && headerName === 'content-length') { + request.contentLength = parseInt(val, 10) + if (!Number.isFinite(request.contentLength)) { + throw new InvalidArgumentError('invalid content-length header') } - case 'content-type': { - if (request.contentType !== null) break - request.contentType = val + } else if (request.contentType !== null && headerName === 'content-type') { + request.contentType = val + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) + } else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') { + throw new InvalidArgumentError(`invalid ${headerName} header`) + } else if (headerName === 'connection') { + const value = typeof val === 'string' ? val.toLowerCase() : null + if (value !== 'close' && value !== 'keep-alive') { + throw new InvalidArgumentError('invalid connection header') + } else if (value === 'close') { + request.reset = true + } + } else if (headerName === 'expect') { + throw new NotSupportedError('expect header not supported') + } else { + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + if (skipAppend) { + if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` + else request.headers[key] = processHeaderValue(key, val[i], skipAppend) + } else { + request.headers += processHeaderValue(key, val[i]) + } + } + } else { if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) else request.headers += processHeaderValue(key, val) - return - } - case 'transfer-encoding': { - throw new InvalidArgumentError('invalid transfer-encoding header') } - case 'connection': { - const value = typeof val === 'string' ? val.toLowerCase() : null - if (value !== 'close' && value !== 'keep-alive') { - throw new InvalidArgumentError('invalid connection header') - } else if (value === 'close') { - request.reset = true - } - return - } - case 'keep-alive': { - throw new InvalidArgumentError('invalid keep-alive header') - } - case 'upgrade': { - throw new InvalidArgumentError('invalid upgrade header') - } - case 'expect': { - throw new NotSupportedError('expect header not supported') - } - } - if (Array.isArray(val)) { - for (let i = 0; i < val.length; i++) { - if (skipAppend) { - if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` - else request.headers[key] = processHeaderValue(key, val[i], skipAppend) - } else { - request.headers += processHeaderValue(key, val[i]) - } - } - } else { - if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) - else request.headers += processHeaderValue(key, val) } } From 457481114026e2194083c6025525c90aa797d889 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:47:29 +0900 Subject: [PATCH 4/4] fixup --- lib/core/request.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/request.js b/lib/core/request.js index 6084a3f198d..caaf70d36bb 100644 --- a/lib/core/request.js +++ b/lib/core/request.js @@ -419,18 +419,18 @@ function processHeader (request, key, val, skipAppend = false) { } } - if (request.host !== null && headerName === 'host') { + if (request.host === null && headerName === 'host') { if (headerCharRegex.exec(val) !== null) { throw new InvalidArgumentError(`invalid ${key} header`) } // Consumed by Client request.host = val - } else if (request.contentLength !== null && headerName === 'content-length') { + } else if (request.contentLength === null && headerName === 'content-length') { request.contentLength = parseInt(val, 10) if (!Number.isFinite(request.contentLength)) { throw new InvalidArgumentError('invalid content-length header') } - } else if (request.contentType !== null && headerName === 'content-type') { + } else if (request.contentType === null && headerName === 'content-type') { request.contentType = val if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) else request.headers += processHeaderValue(key, val)