undici 6.27.0 → 6.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/request.js +12 -1
- package/lib/dispatcher/client-h1.js +11 -2
- package/lib/handler/retry-handler.js +34 -0
- package/lib/web/cookies/util.js +79 -9
- package/package.json +1 -1
package/lib/core/request.js
CHANGED
|
@@ -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
|
-
|
|
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') {
|
|
@@ -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
|
|
997
|
-
|
|
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') {
|
|
@@ -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),
|
package/lib/web/cookies/util.js
CHANGED
|
@@ -105,7 +105,7 @@ function validateCookiePath (path) {
|
|
|
105
105
|
|
|
106
106
|
if (
|
|
107
107
|
code < 0x20 || // exclude CTLs (0-31)
|
|
108
|
-
code
|
|
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
|
-
*
|
|
118
|
-
*
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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('; ')
|