| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0d07248 commit 3fd0aa5
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -350,7 +350,13 @@ function processHeader (request, key, val) { | |||
| 350 | 350 | } else if (typeof val[i] === 'object') { | |
| 351 | 351 | throw new InvalidArgumentError(`invalid ${key} header`) | |
| 352 | 352 | } else { | |
| 353 | - arr.push(`${val[i]}`) | ||
| 353 | + // Coerce primitives (and reject unsafe coercions such as functions | ||
| 354 | + // with a crafted toString/Symbol.toPrimitive). | ||
| 355 | + const str = `${val[i]}` | ||
| 356 | + if (!isValidHeaderValue(str)) { | ||
| 357 | + throw new InvalidArgumentError(`invalid ${key} header`) | ||
| 358 | + } | ||
| 359 | + arr.push(str) | ||
| 354 | 360 | } | |
| 355 | 361 | } | |
| 356 | 362 | val = arr | |
@@ -361,7 +367,12 @@ function processHeader (request, key, val) { | |||
| 361 | 367 | } else if (val === null) { | |
| 362 | 368 | val = '' | |
| 363 | 369 | } else { | |
| 370 | + // Coerce primitives (and reject unsafe coercions such as functions | ||
| 371 | + // with a crafted toString/Symbol.toPrimitive). | ||
| 364 | 372 | val = `${val}` | |
| 373 | + if (!isValidHeaderValue(val)) { | ||
| 374 | + throw new InvalidArgumentError(`invalid ${key} header`) | ||
| 375 | + } | ||
| 365 | 376 | } | |
| 366 | 377 | ||
| 367 | 378 | if (headerName === 'host') { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,7 @@ const { | |||
| 10 | 10 | RequestContentLengthMismatchError, | |
| 11 | 11 | ResponseContentLengthMismatchError, | |
| 12 | 12 | RequestAbortedError, | |
| 13 | + InvalidArgumentError, | ||
| 13 | 14 | HeadersTimeoutError, | |
| 14 | 15 | HeadersOverflowError, | |
| 15 | 16 | SocketError, | |
@@ -993,8 +994,16 @@ function writeH1 (client, request) { | |||
| 993 | 994 | } | |
| 994 | 995 | body = bodyStream.stream | |
| 995 | 996 | contentLength = bodyStream.length | |
| 996 | - } else if (util.isBlobLike(body) && request.contentType == null && body.type) { | ||
| 997 | - headers.push('content-type', body.type) | ||
| 997 | + } else if (util.isBlobLike(body) && request.contentType == null) { | ||
| 998 | + const contentType = body.type | ||
| 999 | + if (contentType) { | ||
| 1000 | + const contentTypeValue = `${contentType}` | ||
| 1001 | + if (!util.isValidHeaderValue(contentTypeValue)) { | ||
| 1002 | + util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header')) | ||
| 1003 | + return false | ||
| 1004 | + } | ||
| 1005 | + headers.push('content-type', contentTypeValue) | ||
| 1006 | + } | ||
| 998 | 1007 | } | |
| 999 | 1008 | ||
| 1000 | 1009 | if (body && typeof body.read === 'function') { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,6 +15,28 @@ function calculateRetryAfterHeader (retryAfter) { | |||
| 15 | 15 | return new Date(retryAfter).getTime() - current | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | + function validatePartialResponseContentLength (headers, range, statusCode, retryCount) { | ||
| 19 | + const contentLength = headers['content-length'] | ||
| 20 | + if (contentLength == null) { | ||
| 21 | + return null | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) { | ||
| 25 | + return null | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + const length = Number(contentLength) | ||
| 29 | + const expectedLength = range.end - range.start + 1 | ||
| 30 | + if (!Number.isFinite(length) || length !== expectedLength) { | ||
| 31 | + return new RequestRetryError('Content-Length mismatch', statusCode, { | ||
| 32 | + headers, | ||
| 33 | + data: { count: retryCount } | ||
| 34 | + }) | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + return null | ||
| 38 | + } | ||
| 39 | + | ||
| 18 | 40 | class RetryHandler { | |
| 19 | 41 | constructor (opts, handlers) { | |
| 20 | 42 | const { retryOptions, ...dispatchOpts } = opts | |
@@ -229,6 +251,12 @@ class RetryHandler { | |||
| 229 | 251 | return false | |
| 230 | 252 | } | |
| 231 | 253 | ||
| 254 | + const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount) | ||
| 255 | + if (contentLengthError != null) { | ||
| 256 | + this.abort(contentLengthError) | ||
| 257 | + return false | ||
| 258 | + } | ||
| 259 | + | ||
| 232 | 260 | const { start, size, end = size - 1 } = contentRange | |
| 233 | 261 | ||
| 234 | 262 | assert(this.start === start, 'content-range mismatch') | |
@@ -252,6 +280,12 @@ class RetryHandler { | |||
| 252 | 280 | ) | |
| 253 | 281 | } | |
| 254 | 282 | ||
| 283 | + const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount) | ||
| 284 | + if (contentLengthError != null) { | ||
| 285 | + this.abort(contentLengthError) | ||
| 286 | + return false | ||
| 287 | + } | ||
| 288 | + | ||
| 255 | 289 | const { start, size, end = size - 1 } = range | |
| 256 | 290 | assert( | |
| 257 | 291 | start != null && Number.isFinite(start), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,12 +1,12 @@ | |||
| 1 | 1 | ||
| 2 | - > undici@6.27.0 prebuild:wasm | ||
| 2 | + > undici@6.28.0 prebuild:wasm | ||
| 3 | 3 | > node build/wasm.js --prebuild | |
| 4 | 4 | ||
| 5 | 5 | > docker build --platform=linux/x86_64 -t llhttp_wasm_builder -f /home/runner/work/node/node/deps/undici/src/build/Dockerfile /home/runner/work/node/node/deps/undici/src | |
| 6 | 6 | ||
| 7 | 7 | ||
| 8 | 8 | ||
| 9 | - > undici@6.27.0 build:wasm | ||
| 9 | + > undici@6.28.0 build:wasm | ||
| 10 | 10 | > node build/wasm.js --docker | |
| 11 | 11 | ||
| 12 | 12 | > docker run --rm -t --platform=linux/x86_64 --user 1001:1001 --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/lib/llhttp,target=/home/node/undici/lib/llhttp llhttp_wasm_builder node build/wasm.js | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -105,7 +105,7 @@ function validateCookiePath (path) { | |||
| 105 | 105 | ||
| 106 | 106 | if ( | |
| 107 | 107 | code < 0x20 || // exclude CTLs (0-31) | |
| 108 | - code === 0x7F || // DEL | ||
| 108 | + code > 0x7E || // exclude DEL and non-ascii | ||
| 109 | 109 | code === 0x3B // ; | |
| 110 | 110 | ) { | |
| 111 | 111 | throw new Error('Invalid cookie path') | |
@@ -114,16 +114,80 @@ function validateCookiePath (path) { | |||
| 114 | 114 | } | |
| 115 | 115 | ||
| 116 | 116 | /** | |
| 117 | - * I have no idea why these values aren't allowed to be honest, | ||
| 118 | - * but Deno tests these. - Khafra | ||
| 117 | + * <let-dig> ::= <letter> | <digit> | ||
| 118 | + * | ||
| 119 | + * <letter> ::= any one of the 52 alphabetic characters A through Z in | ||
| 120 | + * upper case and a through z in lower case | ||
| 121 | + * | ||
| 122 | + * <digit> ::= any one of the ten digits 0 through 9r | ||
| 123 | + * | ||
| 124 | + * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5 | ||
| 125 | + * @param {number} code | ||
| 126 | + */ | ||
| 127 | + function isLetterOrDigit (code) { | ||
| 128 | + return ( | ||
| 129 | + (code >= 0x30 && code <= 0x39) || // 0-9 | ||
| 130 | + (code >= 0x41 && code <= 0x5A) || // A-Z | ||
| 131 | + (code >= 0x61 && code <= 0x7A) // a-z | ||
| 132 | + ) | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + /** | ||
| 136 | + * Validates a cookie domain against the "preferred name syntax". | ||
| 137 | + * | ||
| 138 | + * <domain> ::= <subdomain> | " " | ||
| 139 | + * <subdomain> ::= <label> | <subdomain> "." <label> | ||
| 140 | + * <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ] | ||
| 141 | + * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str> | ||
| 142 | + * <let-dig-hyp> ::= <let-dig> | "-" | ||
| 143 | + * | ||
| 144 | + * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5 | ||
| 145 | + * @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1 | ||
| 146 | + * @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4 | ||
| 119 | 147 | * @param {string} domain | |
| 120 | 148 | */ | |
| 121 | 149 | function validateCookieDomain (domain) { | |
| 122 | - if ( | ||
| 123 | - domain.startsWith('-') || | ||
| 124 | - domain.endsWith('.') || | ||
| 125 | - domain.endsWith('-') | ||
| 126 | - ) { | ||
| 150 | + // <domain> ::= <subdomain> | " " | ||
| 151 | + if (domain === ' ') { | ||
| 152 | + return | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + if (domain.length > 255) { | ||
| 156 | + throw new Error('Invalid cookie domain') | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + let labelLength = 0 | ||
| 160 | + | ||
| 161 | + for (let i = 0; i < domain.length; ++i) { | ||
| 162 | + const code = domain.charCodeAt(i) | ||
| 163 | + | ||
| 164 | + if (code === 0x2E) { | ||
| 165 | + if (labelLength === 0) { | ||
| 166 | + throw new Error('Invalid cookie domain') | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + if (domain.charCodeAt(i - 1) === 0x2D) { // "-" | ||
| 170 | + throw new Error('Invalid cookie domain') | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + labelLength = 0 | ||
| 174 | + continue | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + if (labelLength === 0 && !isLetterOrDigit(code)) { | ||
| 178 | + throw new Error('Invalid cookie domain') | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + if (!isLetterOrDigit(code) && code !== 0x2D) { // "-" | ||
| 182 | + throw new Error('Invalid cookie domain') | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + if (++labelLength > 63) { | ||
| 186 | + throw new Error('Invalid cookie domain') | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-" | ||
| 127 | 191 | throw new Error('Invalid cookie domain') | |
| 128 | 192 | } | |
| 129 | 193 | } | |
@@ -266,7 +330,13 @@ function stringify (cookie) { | |||
| 266 | 330 | ||
| 267 | 331 | const [key, ...value] = part.split('=') | |
| 268 | 332 | ||
| 269 | - out.push(`${key.trim()}=${value.join('=')}`) | ||
| 333 | + const trimmedKey = key.trim() | ||
| 334 | + const joinedValue = value.join('=') | ||
| 335 | + | ||
| 336 | + validateCookieName(trimmedKey) | ||
| 337 | + validateCookieValue(joinedValue) | ||
| 338 | + | ||
| 339 | + out.push(`${trimmedKey}=${joinedValue}`) | ||
| 270 | 340 | } | |
| 271 | 341 | ||
| 272 | 342 | return out.join('; ') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,6 @@ | |||
| 1 | 1 | { | |
| 2 | 2 | "name": "undici", | |
| 3 | - "version": "6.27.0", | ||
| 3 | + "version": "6.28.0", | ||
| 4 | 4 | "description": "An HTTP/1.1 client, written from scratch for Node.js", | |
| 5 | 5 | "homepage": "https://undici.nodejs.org", | |
| 6 | 6 | "bugs": { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2280,7 +2280,11 @@ var require_request = __commonJS({ | |||
| 2280 | 2280 | } else if (typeof val[i] === "object") { | |
| 2281 | 2281 | throw new InvalidArgumentError(`invalid ${key} header`); | |
| 2282 | 2282 | } else { | |
| 2283 | - arr.push(`${val[i]}`); | ||
| 2283 | + const str = `${val[i]}`; | ||
| 2284 | + if (!isValidHeaderValue(str)) { | ||
| 2285 | + throw new InvalidArgumentError(`invalid ${key} header`); | ||
| 2286 | + } | ||
| 2287 | + arr.push(str); | ||
| 2284 | 2288 | } | |
| 2285 | 2289 | } | |
| 2286 | 2290 | val = arr; | |
@@ -2292,6 +2296,9 @@ var require_request = __commonJS({ | |||
| 2292 | 2296 | val = ""; | |
| 2293 | 2297 | } else { | |
| 2294 | 2298 | val = `${val}`; | |
| 2299 | + if (!isValidHeaderValue(val)) { | ||
| 2300 | + throw new InvalidArgumentError(`invalid ${key} header`); | ||
| 2301 | + } | ||
| 2295 | 2302 | } | |
| 2296 | 2303 | if (headerName === "host") { | |
| 2297 | 2304 | if (request.host !== null) { | |
@@ -5921,6 +5928,7 @@ var require_client_h1 = __commonJS({ | |||
| 5921 | 5928 | RequestContentLengthMismatchError, | |
| 5922 | 5929 | ResponseContentLengthMismatchError, | |
| 5923 | 5930 | RequestAbortedError, | |
| 5931 | + InvalidArgumentError, | ||
| 5924 | 5932 | HeadersTimeoutError, | |
| 5925 | 5933 | HeadersOverflowError, | |
| 5926 | 5934 | SocketError, | |
@@ -6657,8 +6665,16 @@ var require_client_h1 = __commonJS({ | |||
| 6657 | 6665 | } | |
| 6658 | 6666 | body = bodyStream.stream; | |
| 6659 | 6667 | contentLength = bodyStream.length; | |
| 6660 | - } else if (util.isBlobLike(body) && request.contentType == null && body.type) { | ||
| 6661 | - headers.push("content-type", body.type); | ||
| 6668 | + } else if (util.isBlobLike(body) && request.contentType == null) { | ||
| 6669 | + const contentType = body.type; | ||
| 6670 | + if (contentType) { | ||
| 6671 | + const contentTypeValue = `${contentType}`; | ||
| 6672 | + if (!util.isValidHeaderValue(contentTypeValue)) { | ||
| 6673 | + util.errorRequest(client, request, new InvalidArgumentError("invalid content-type header")); | ||
| 6674 | + return false; | ||
| 6675 | + } | ||
| 6676 | + headers.push("content-type", contentTypeValue); | ||
| 6677 | + } | ||
| 6662 | 6678 | } | |
| 6663 | 6679 | if (body && typeof body.read === "function") { | |
| 6664 | 6680 | body.read(0); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,5 +2,5 @@ | |||
| 2 | 2 | // Refer to tools/dep_updaters/update-undici.sh | |
| 3 | 3 | #ifndef SRC_UNDICI_VERSION_H_ | |
| 4 | 4 | #define SRC_UNDICI_VERSION_H_ | |
| 5 | - #define UNDICI_VERSION "6.27.0" | ||
| 5 | + #define UNDICI_VERSION "6.28.0" | ||
| 6 | 6 | #endif // SRC_UNDICI_VERSION_H_ | |
| Back | FazBrowse Home | New Git URL |
0 commit comments