@@ -6935,7 +6935,13 @@ function processHeader (request, key, val) {
69356935 } else if (typeof val[i] === 'object') {
69366936 throw new InvalidArgumentError(`invalid ${key} header`)
69376937 } else {
6938- arr.push(`${val[i]}`)
6938+ // Coerce primitives (and reject unsafe coercions such as functions
6939+ // with a crafted toString/Symbol.toPrimitive).
6940+ const str = `${val[i]}`
6941+ if (!isValidHeaderValue(str)) {
6942+ throw new InvalidArgumentError(`invalid ${key} header`)
6943+ }
6944+ arr.push(str)
69396945 }
69406946 }
69416947 val = arr
@@ -6946,7 +6952,12 @@ function processHeader (request, key, val) {
69466952 } else if (val === null) {
69476953 val = ''
69486954 } else {
6955+ // Coerce primitives (and reject unsafe coercions such as functions
6956+ // with a crafted toString/Symbol.toPrimitive).
69496957 val = `${val}`
6958+ if (!isValidHeaderValue(val)) {
6959+ throw new InvalidArgumentError(`invalid ${key} header`)
6960+ }
69506961 }
69516962
69526963 if (headerName === 'host') {
@@ -8323,6 +8334,7 @@ const {
83238334 RequestContentLengthMismatchError,
83248335 ResponseContentLengthMismatchError,
83258336 RequestAbortedError,
8337+ InvalidArgumentError,
83268338 HeadersTimeoutError,
83278339 HeadersOverflowError,
83288340 SocketError,
@@ -9306,8 +9318,16 @@ function writeH1 (client, request) {
93069318 }
93079319 body = bodyStream.stream
93089320 contentLength = bodyStream.length
9309- } else if (util.isBlobLike(body) && request.contentType == null && body.type) {
9310- headers.push('content-type', body.type)
9321+ } else if (util.isBlobLike(body) && request.contentType == null) {
9322+ const contentType = body.type
9323+ if (contentType) {
9324+ const contentTypeValue = `${contentType}`
9325+ if (!util.isValidHeaderValue(contentTypeValue)) {
9326+ util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header'))
9327+ return false
9328+ }
9329+ headers.push('content-type', contentTypeValue)
9330+ }
93119331 }
93129332
93139333 if (body && typeof body.read === 'function') {
@@ -12794,6 +12814,28 @@ function calculateRetryAfterHeader (retryAfter) {
1279412814 return new Date(retryAfter).getTime() - current
1279512815}
1279612816
12817+ function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
12818+ const contentLength = headers['content-length']
12819+ if (contentLength == null) {
12820+ return null
12821+ }
12822+
12823+ if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
12824+ return null
12825+ }
12826+
12827+ const length = Number(contentLength)
12828+ const expectedLength = range.end - range.start + 1
12829+ if (!Number.isFinite(length) || length !== expectedLength) {
12830+ return new RequestRetryError('Content-Length mismatch', statusCode, {
12831+ headers,
12832+ data: { count: retryCount }
12833+ })
12834+ }
12835+
12836+ return null
12837+ }
12838+
1279712839class RetryHandler {
1279812840 constructor (opts, handlers) {
1279912841 const { retryOptions, ...dispatchOpts } = opts
@@ -13008,6 +13050,12 @@ class RetryHandler {
1300813050 return false
1300913051 }
1301013052
13053+ const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
13054+ if (contentLengthError != null) {
13055+ this.abort(contentLengthError)
13056+ return false
13057+ }
13058+
1301113059 const { start, size, end = size - 1 } = contentRange
1301213060
1301313061 assert(this.start === start, 'content-range mismatch')
@@ -13031,6 +13079,12 @@ class RetryHandler {
1303113079 )
1303213080 }
1303313081
13082+ const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
13083+ if (contentLengthError != null) {
13084+ this.abort(contentLengthError)
13085+ return false
13086+ }
13087+
1303413088 const { start, size, end = size - 1 } = range
1303513089 assert(
1303613090 start != null && Number.isFinite(start),
@@ -17302,7 +17356,7 @@ function validateCookiePath (path) {
1730217356
1730317357 if (
1730417358 code < 0x20 || // exclude CTLs (0-31)
17305- code === 0x7F || // DEL
17359+ code > 0x7E || // exclude DEL and non-ascii
1730617360 code === 0x3B // ;
1730717361 ) {
1730817362 throw new Error('Invalid cookie path')
@@ -17311,16 +17365,80 @@ function validateCookiePath (path) {
1731117365}
1731217366
1731317367/**
17314- * I have no idea why these values aren't allowed to be honest,
17315- * but Deno tests these. - Khafra
17368+ * <let-dig> ::= <letter> | <digit>
17369+ *
17370+ * <letter> ::= any one of the 52 alphabetic characters A through Z in
17371+ * upper case and a through z in lower case
17372+ *
17373+ * <digit> ::= any one of the ten digits 0 through 9r
17374+ *
17375+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
17376+ * @param {number} code
17377+ */
17378+ function isLetterOrDigit (code) {
17379+ return (
17380+ (code >= 0x30 && code <= 0x39) || // 0-9
17381+ (code >= 0x41 && code <= 0x5A) || // A-Z
17382+ (code >= 0x61 && code <= 0x7A) // a-z
17383+ )
17384+ }
17385+
17386+ /**
17387+ * Validates a cookie domain against the "preferred name syntax".
17388+ *
17389+ * <domain> ::= <subdomain> | " "
17390+ * <subdomain> ::= <label> | <subdomain> "." <label>
17391+ * <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ]
17392+ * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
17393+ * <let-dig-hyp> ::= <let-dig> | "-"
17394+ *
17395+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
17396+ * @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1
17397+ * @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
1731617398 * @param {string} domain
1731717399 */
1731817400function validateCookieDomain (domain) {
17319- if (
17320- domain.startsWith('-') ||
17321- domain.endsWith('.') ||
17322- domain.endsWith('-')
17323- ) {
17401+ // <domain> ::= <subdomain> | " "
17402+ if (domain === ' ') {
17403+ return
17404+ }
17405+
17406+ if (domain.length > 255) {
17407+ throw new Error('Invalid cookie domain')
17408+ }
17409+
17410+ let labelLength = 0
17411+
17412+ for (let i = 0; i < domain.length; ++i) {
17413+ const code = domain.charCodeAt(i)
17414+
17415+ if (code === 0x2E) {
17416+ if (labelLength === 0) {
17417+ throw new Error('Invalid cookie domain')
17418+ }
17419+
17420+ if (domain.charCodeAt(i - 1) === 0x2D) { // "-"
17421+ throw new Error('Invalid cookie domain')
17422+ }
17423+
17424+ labelLength = 0
17425+ continue
17426+ }
17427+
17428+ if (labelLength === 0 && !isLetterOrDigit(code)) {
17429+ throw new Error('Invalid cookie domain')
17430+ }
17431+
17432+ if (!isLetterOrDigit(code) && code !== 0x2D) { // "-"
17433+ throw new Error('Invalid cookie domain')
17434+ }
17435+
17436+ if (++labelLength > 63) {
17437+ throw new Error('Invalid cookie domain')
17438+ }
17439+ }
17440+
17441+ if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-"
1732417442 throw new Error('Invalid cookie domain')
1732517443 }
1732617444}
@@ -17463,7 +17581,13 @@ function stringify (cookie) {
1746317581
1746417582 const [key, ...value] = part.split('=')
1746517583
17466- out.push(`${key.trim()}=${value.join('=')}`)
17584+ const trimmedKey = key.trim()
17585+ const joinedValue = value.join('=')
17586+
17587+ validateCookieName(trimmedKey)
17588+ validateCookieValue(joinedValue)
17589+
17590+ out.push(`${trimmedKey}=${joinedValue}`)
1746717591 }
1746817592
1746917593 return out.join('; ')
0 commit comments