@@ -16069,7 +16069,13 @@ function processHeader (request, key, val) {
1606916069 } else if (typeof val[i] === 'object') {
1607016070 throw new InvalidArgumentError(`invalid ${key} header`)
1607116071 } else {
16072- arr.push(`${val[i]}`)
16072+ // Coerce primitives (and reject unsafe coercions such as functions
16073+ // with a crafted toString/Symbol.toPrimitive).
16074+ const str = `${val[i]}`
16075+ if (!isValidHeaderValue(str)) {
16076+ throw new InvalidArgumentError(`invalid ${key} header`)
16077+ }
16078+ arr.push(str)
1607316079 }
1607416080 }
1607516081 val = arr
@@ -16080,7 +16086,12 @@ function processHeader (request, key, val) {
1608016086 } else if (val === null) {
1608116087 val = ''
1608216088 } else {
16089+ // Coerce primitives (and reject unsafe coercions such as functions
16090+ // with a crafted toString/Symbol.toPrimitive).
1608316091 val = `${val}`
16092+ if (!isValidHeaderValue(val)) {
16093+ throw new InvalidArgumentError(`invalid ${key} header`)
16094+ }
1608416095 }
1608516096
1608616097 if (headerName === 'host') {
@@ -17452,6 +17463,7 @@ const {
1745217463 RequestContentLengthMismatchError,
1745317464 ResponseContentLengthMismatchError,
1745417465 RequestAbortedError,
17466+ InvalidArgumentError,
1745517467 HeadersTimeoutError,
1745617468 HeadersOverflowError,
1745717469 SocketError,
@@ -18435,8 +18447,16 @@ function writeH1 (client, request) {
1843518447 }
1843618448 body = bodyStream.stream
1843718449 contentLength = bodyStream.length
18438- } else if (util.isBlobLike(body) && request.contentType == null && body.type) {
18439- headers.push('content-type', body.type)
18450+ } else if (util.isBlobLike(body) && request.contentType == null) {
18451+ const contentType = body.type
18452+ if (contentType) {
18453+ const contentTypeValue = `${contentType}`
18454+ if (!util.isValidHeaderValue(contentTypeValue)) {
18455+ util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header'))
18456+ return false
18457+ }
18458+ headers.push('content-type', contentTypeValue)
18459+ }
1844018460 }
1844118461
1844218462 if (body && typeof body.read === 'function') {
@@ -21909,6 +21929,28 @@ function calculateRetryAfterHeader (retryAfter) {
2190921929 return new Date(retryAfter).getTime() - current
2191021930}
2191121931
21932+ function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
21933+ const contentLength = headers['content-length']
21934+ if (contentLength == null) {
21935+ return null
21936+ }
21937+
21938+ if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
21939+ return null
21940+ }
21941+
21942+ const length = Number(contentLength)
21943+ const expectedLength = range.end - range.start + 1
21944+ if (!Number.isFinite(length) || length !== expectedLength) {
21945+ return new RequestRetryError('Content-Length mismatch', statusCode, {
21946+ headers,
21947+ data: { count: retryCount }
21948+ })
21949+ }
21950+
21951+ return null
21952+ }
21953+
2191221954class RetryHandler {
2191321955 constructor (opts, handlers) {
2191421956 const { retryOptions, ...dispatchOpts } = opts
@@ -22123,6 +22165,12 @@ class RetryHandler {
2212322165 return false
2212422166 }
2212522167
22168+ const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
22169+ if (contentLengthError != null) {
22170+ this.abort(contentLengthError)
22171+ return false
22172+ }
22173+
2212622174 const { start, size, end = size - 1 } = contentRange
2212722175
2212822176 assert(this.start === start, 'content-range mismatch')
@@ -22146,6 +22194,12 @@ class RetryHandler {
2214622194 )
2214722195 }
2214822196
22197+ const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
22198+ if (contentLengthError != null) {
22199+ this.abort(contentLengthError)
22200+ return false
22201+ }
22202+
2214922203 const { start, size, end = size - 1 } = range
2215022204 assert(
2215122205 start != null && Number.isFinite(start),
@@ -26390,7 +26444,7 @@ function validateCookiePath (path) {
2639026444
2639126445 if (
2639226446 code < 0x20 || // exclude CTLs (0-31)
26393- code === 0x7F || // DEL
26447+ code > 0x7E || // exclude DEL and non-ascii
2639426448 code === 0x3B // ;
2639526449 ) {
2639626450 throw new Error('Invalid cookie path')
@@ -26399,16 +26453,80 @@ function validateCookiePath (path) {
2639926453}
2640026454
2640126455/**
26402- * I have no idea why these values aren't allowed to be honest,
26403- * but Deno tests these. - Khafra
26456+ * <let-dig> ::= <letter> | <digit>
26457+ *
26458+ * <letter> ::= any one of the 52 alphabetic characters A through Z in
26459+ * upper case and a through z in lower case
26460+ *
26461+ * <digit> ::= any one of the ten digits 0 through 9r
26462+ *
26463+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
26464+ * @param {number} code
26465+ */
26466+ function isLetterOrDigit (code) {
26467+ return (
26468+ (code >= 0x30 && code <= 0x39) || // 0-9
26469+ (code >= 0x41 && code <= 0x5A) || // A-Z
26470+ (code >= 0x61 && code <= 0x7A) // a-z
26471+ )
26472+ }
26473+
26474+ /**
26475+ * Validates a cookie domain against the "preferred name syntax".
26476+ *
26477+ * <domain> ::= <subdomain> | " "
26478+ * <subdomain> ::= <label> | <subdomain> "." <label>
26479+ * <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ]
26480+ * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
26481+ * <let-dig-hyp> ::= <let-dig> | "-"
26482+ *
26483+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
26484+ * @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1
26485+ * @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
2640426486 * @param {string} domain
2640526487 */
2640626488function validateCookieDomain (domain) {
26407- if (
26408- domain.startsWith('-') ||
26409- domain.endsWith('.') ||
26410- domain.endsWith('-')
26411- ) {
26489+ // <domain> ::= <subdomain> | " "
26490+ if (domain === ' ') {
26491+ return
26492+ }
26493+
26494+ if (domain.length > 255) {
26495+ throw new Error('Invalid cookie domain')
26496+ }
26497+
26498+ let labelLength = 0
26499+
26500+ for (let i = 0; i < domain.length; ++i) {
26501+ const code = domain.charCodeAt(i)
26502+
26503+ if (code === 0x2E) {
26504+ if (labelLength === 0) {
26505+ throw new Error('Invalid cookie domain')
26506+ }
26507+
26508+ if (domain.charCodeAt(i - 1) === 0x2D) { // "-"
26509+ throw new Error('Invalid cookie domain')
26510+ }
26511+
26512+ labelLength = 0
26513+ continue
26514+ }
26515+
26516+ if (labelLength === 0 && !isLetterOrDigit(code)) {
26517+ throw new Error('Invalid cookie domain')
26518+ }
26519+
26520+ if (!isLetterOrDigit(code) && code !== 0x2D) { // "-"
26521+ throw new Error('Invalid cookie domain')
26522+ }
26523+
26524+ if (++labelLength > 63) {
26525+ throw new Error('Invalid cookie domain')
26526+ }
26527+ }
26528+
26529+ if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-"
2641226530 throw new Error('Invalid cookie domain')
2641326531 }
2641426532}
@@ -26551,7 +26669,13 @@ function stringify (cookie) {
2655126669
2655226670 const [key, ...value] = part.split('=')
2655326671
26554- out.push(`${key.trim()}=${value.join('=')}`)
26672+ const trimmedKey = key.trim()
26673+ const joinedValue = value.join('=')
26674+
26675+ validateCookieName(trimmedKey)
26676+ validateCookieValue(joinedValue)
26677+
26678+ out.push(`${trimmedKey}=${joinedValue}`)
2655526679 }
2655626680
2655726681 return out.join('; ')
@@ -41084,7 +41208,7 @@ function combine(acc, pre, values, max, maxLength, dropEmpties) {
4108441208}
4108541209// The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`)
4108641210// sequence body.
41087- function expandSequence(body, isAlphaSequence, max) {
41211+ function expandSequence(body, isAlphaSequence, max, maxLength ) {
4108841212 const n = body.split(/\.\./);
4108941213 const N = [];
4109041214 // A sequence body always splits into two or three parts, but the compiler
@@ -41107,6 +41231,7 @@ function expandSequence(body, isAlphaSequence, max) {
4110741231 test = gte;
4110841232 }
4110941233 const pad = n.some(isPadded);
41234+ let length = 0;
4111041235 for (let i = x; test(i, y) && N.length < max; i += incr) {
4111141236 let c;
4111241237 if (isAlphaSequence) {
@@ -41130,7 +41255,10 @@ function expandSequence(body, isAlphaSequence, max) {
4113041255 }
4113141256 }
4113241257 }
41258+ if (length + c.length > maxLength)
41259+ break;
4113341260 N.push(c);
41261+ length += c.length;
4113441262 }
4113541263 return N;
4113641264}
@@ -41184,7 +41312,7 @@ function expand_(str, max, maxLength, isTop) {
4118441312 }
4118541313 let values;
4118641314 if (isSequence) {
41187- values = expandSequence(m.body, isAlphaSequence, max);
41315+ values = expandSequence(m.body, isAlphaSequence, max, maxLength );
4118841316 }
4118941317 else {
4119041318 let n = parseCommaParts(m.body);
@@ -41202,9 +41330,31 @@ function expand_(str, max, maxLength, isTop) {
4120241330 }
4120341331 /* c8 ignore stop */
4120441332 }
41333+ // Values that `combine` is going to drop as empty produce no result, so
41334+ // they must not count against `max` - otherwise `{a,,b}` with `max: 2`
41335+ // would stop at `['a', '']` and yield one result instead of two. Skipping
41336+ // them outright keeps `values` bounded while leaving `max` a bound on
41337+ // *kept* results.
41338+ let dropsEmpties = dropEmpties && !m.post.length && !pre;
41339+ for (let d = 0; dropsEmpties && d < acc.length; d++) {
41340+ if (acc[d]) {
41341+ dropsEmpties = false;
41342+ }
41343+ }
4120541344 values = [];
41206- for (let j = 0; j < n.length; j++) {
41207- values.push.apply(values, expand_(n[j], max, maxLength, false));
41345+ let valuesLength = 0;
41346+ outer: for (let j = 0; j < n.length; j++) {
41347+ const expanded = expand_(n[j], max, maxLength, false);
41348+ for (let k = 0; k < expanded.length; k++) {
41349+ const v = expanded[k];
41350+ if (dropsEmpties && !v)
41351+ continue;
41352+ if (values.length >= max || valuesLength + v.length > maxLength) {
41353+ break outer;
41354+ }
41355+ values.push(v);
41356+ valuesLength += v.length;
41357+ }
4120841358 }
4120941359 }
4121041360 acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length);
0 commit comments