undici 7.0.0-alpha.1 → 7.0.0-alpha.10
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/README.md +24 -38
- package/docs/docs/api/Agent.md +14 -14
- package/docs/docs/api/BalancedPool.md +16 -16
- package/docs/docs/api/CacheStore.md +131 -0
- package/docs/docs/api/Client.md +12 -12
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +98 -193
- package/docs/docs/api/EnvHttpProxyAgent.md +12 -12
- package/docs/docs/api/MockAgent.md +5 -3
- package/docs/docs/api/MockClient.md +5 -5
- package/docs/docs/api/MockPool.md +4 -3
- package/docs/docs/api/Pool.md +15 -15
- package/docs/docs/api/PoolStats.md +1 -1
- package/docs/docs/api/ProxyAgent.md +3 -3
- package/docs/docs/api/RedirectHandler.md +1 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +4 -4
- package/docs/docs/api/WebSocket.md +46 -4
- package/docs/docs/api/api-lifecycle.md +11 -11
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +23 -3
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-pipeline.js +4 -2
- package/lib/api/api-request.js +6 -4
- package/lib/api/api-stream.js +3 -1
- package/lib/api/api-upgrade.js +2 -2
- package/lib/api/readable.js +200 -47
- package/lib/api/util.js +2 -0
- package/lib/cache/memory-cache-store.js +177 -0
- package/lib/cache/sqlite-cache-store.js +446 -0
- package/lib/core/connect.js +54 -22
- package/lib/core/constants.js +35 -10
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +2 -2
- package/lib/core/request.js +6 -6
- package/lib/core/symbols.js +2 -0
- package/lib/core/tree.js +4 -2
- package/lib/core/util.js +238 -40
- package/lib/dispatcher/client-h1.js +405 -142
- package/lib/dispatcher/client-h2.js +212 -109
- package/lib/dispatcher/client.js +24 -7
- package/lib/dispatcher/dispatcher-base.js +4 -1
- package/lib/dispatcher/dispatcher.js +4 -0
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/proxy-agent.js +3 -1
- package/lib/handler/cache-handler.js +393 -0
- package/lib/handler/cache-revalidation-handler.js +124 -0
- package/lib/handler/decorator-handler.js +3 -0
- package/lib/handler/redirect-handler.js +45 -59
- package/lib/handler/retry-handler.js +68 -109
- package/lib/handler/unwrap-handler.js +96 -0
- package/lib/handler/wrap-handler.js +98 -0
- package/lib/interceptor/cache.js +350 -0
- package/lib/interceptor/dns.js +375 -0
- package/lib/interceptor/response-error.js +15 -7
- package/lib/mock/mock-agent.js +5 -8
- package/lib/mock/mock-client.js +7 -2
- package/lib/mock/mock-errors.js +3 -1
- package/lib/mock/mock-interceptor.js +8 -6
- package/lib/mock/mock-pool.js +7 -2
- package/lib/mock/mock-symbols.js +2 -1
- package/lib/mock/mock-utils.js +33 -5
- package/lib/util/cache.js +360 -0
- package/lib/util/timers.js +50 -6
- package/lib/web/cache/cache.js +25 -21
- package/lib/web/cache/cachestorage.js +3 -1
- package/lib/web/cookies/index.js +18 -5
- package/lib/web/cookies/parse.js +6 -1
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/body.js +43 -39
- package/lib/web/fetch/constants.js +45 -29
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/formdata-parser.js +84 -46
- package/lib/web/fetch/formdata.js +42 -20
- package/lib/web/fetch/headers.js +119 -85
- package/lib/web/fetch/index.js +69 -65
- package/lib/web/fetch/request.js +132 -55
- package/lib/web/fetch/response.js +81 -36
- package/lib/web/fetch/util.js +274 -103
- package/lib/web/fetch/webidl.js +54 -18
- package/lib/web/websocket/connection.js +92 -15
- package/lib/web/websocket/constants.js +69 -9
- package/lib/web/websocket/events.js +8 -2
- package/lib/web/websocket/receiver.js +20 -26
- package/lib/web/websocket/stream/websocketerror.js +83 -0
- package/lib/web/websocket/stream/websocketstream.js +485 -0
- package/lib/web/websocket/util.js +115 -10
- package/lib/web/websocket/websocket.js +47 -170
- package/package.json +15 -11
- package/types/agent.d.ts +1 -1
- package/types/cache-interceptor.d.ts +172 -0
- package/types/cookies.d.ts +2 -0
- package/types/dispatcher.d.ts +29 -4
- package/types/env-http-proxy-agent.d.ts +1 -1
- package/types/fetch.d.ts +9 -8
- package/types/handlers.d.ts +4 -4
- package/types/index.d.ts +3 -1
- package/types/interceptors.d.ts +18 -1
- package/types/mock-agent.d.ts +4 -1
- package/types/mock-client.d.ts +1 -1
- package/types/mock-pool.d.ts +1 -1
- package/types/proxy-agent.d.ts +1 -1
- package/types/readable.d.ts +10 -7
- package/types/retry-handler.d.ts +3 -3
- package/types/webidl.d.ts +30 -4
- package/types/websocket.d.ts +33 -0
- package/lib/mock/pluralizer.js +0 -29
- package/lib/web/cache/symbols.js +0 -5
- package/lib/web/fetch/symbols.js +0 -8
|
@@ -4,13 +4,14 @@ const { isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util')
|
|
|
4
4
|
const { utf8DecodeBytes } = require('./util')
|
|
5
5
|
const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url')
|
|
6
6
|
const { makeEntry } = require('./formdata')
|
|
7
|
+
const { webidl } = require('./webidl')
|
|
7
8
|
const assert = require('node:assert')
|
|
8
9
|
const { File: NodeFile } = require('node:buffer')
|
|
9
10
|
|
|
10
11
|
const File = globalThis.File ?? NodeFile
|
|
11
12
|
|
|
12
13
|
const formDataNameBuffer = Buffer.from('form-data; name="')
|
|
13
|
-
const filenameBuffer = Buffer.from('
|
|
14
|
+
const filenameBuffer = Buffer.from('filename')
|
|
14
15
|
const dd = Buffer.from('--')
|
|
15
16
|
const ddcrlf = Buffer.from('--\r\n')
|
|
16
17
|
|
|
@@ -74,7 +75,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
74
75
|
// Otherwise, let boundary be the result of UTF-8 decoding mimeType’s
|
|
75
76
|
// parameters["boundary"].
|
|
76
77
|
if (boundaryString === undefined) {
|
|
77
|
-
|
|
78
|
+
throw parsingError('missing boundary in content-type header')
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
const boundary = Buffer.from(`--${boundaryString}`, 'utf8')
|
|
@@ -86,11 +87,21 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
86
87
|
// the first byte.
|
|
87
88
|
const position = { position: 0 }
|
|
88
89
|
|
|
89
|
-
// Note: undici addition,
|
|
90
|
-
|
|
90
|
+
// Note: undici addition, allows leading and trailing CRLFs.
|
|
91
|
+
while (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
|
|
91
92
|
position.position += 2
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
let trailing = input.length
|
|
96
|
+
|
|
97
|
+
while (input[trailing - 1] === 0x0a && input[trailing - 2] === 0x0d) {
|
|
98
|
+
trailing -= 2
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (trailing !== input.length) {
|
|
102
|
+
input = input.subarray(0, trailing)
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
// 5. While true:
|
|
95
106
|
while (true) {
|
|
96
107
|
// 5.1. If position points to a sequence of bytes starting with 0x2D 0x2D
|
|
@@ -100,7 +111,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
100
111
|
if (input.subarray(position.position, position.position + boundary.length).equals(boundary)) {
|
|
101
112
|
position.position += boundary.length
|
|
102
113
|
} else {
|
|
103
|
-
|
|
114
|
+
throw parsingError('expected a value starting with -- and the boundary')
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
// 5.2. If position points to the sequence of bytes 0x2D 0x2D 0x0D 0x0A
|
|
@@ -116,7 +127,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
116
127
|
// 5.3. If position does not point to a sequence of bytes starting with 0x0D
|
|
117
128
|
// 0x0A (CR LF), return failure.
|
|
118
129
|
if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
|
|
119
|
-
|
|
130
|
+
throw parsingError('expected CRLF')
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
// 5.4. Advance position by 2. (This skips past the newline.)
|
|
@@ -127,10 +138,6 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
127
138
|
// is not failure. Otherwise, return failure.
|
|
128
139
|
const result = parseMultipartFormDataHeaders(input, position)
|
|
129
140
|
|
|
130
|
-
if (result === 'failure') {
|
|
131
|
-
return 'failure'
|
|
132
|
-
}
|
|
133
|
-
|
|
134
141
|
let { name, filename, contentType, encoding } = result
|
|
135
142
|
|
|
136
143
|
// 5.6. Advance position by 2. (This skips past the empty line that marks
|
|
@@ -146,7 +153,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
146
153
|
const boundaryIndex = input.indexOf(boundary.subarray(2), position.position)
|
|
147
154
|
|
|
148
155
|
if (boundaryIndex === -1) {
|
|
149
|
-
|
|
156
|
+
throw parsingError('expected boundary after body')
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
body = input.subarray(position.position, boundaryIndex - 4)
|
|
@@ -163,7 +170,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
163
170
|
// 5.9. If position does not point to a sequence of bytes starting with
|
|
164
171
|
// 0x0D 0x0A (CR LF), return failure. Otherwise, advance position by 2.
|
|
165
172
|
if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
|
|
166
|
-
|
|
173
|
+
throw parsingError('expected CRLF')
|
|
167
174
|
} else {
|
|
168
175
|
position.position += 2
|
|
169
176
|
}
|
|
@@ -194,7 +201,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
194
201
|
|
|
195
202
|
// 5.12. Assert: name is a scalar value string and value is either a scalar value string or a File object.
|
|
196
203
|
assert(isUSVString(name))
|
|
197
|
-
assert((typeof value === 'string' && isUSVString(value)) || value
|
|
204
|
+
assert((typeof value === 'string' && isUSVString(value)) || webidl.is.File(value))
|
|
198
205
|
|
|
199
206
|
// 5.13. Create an entry with name and value, and append it to entry list.
|
|
200
207
|
entryList.push(makeEntry(name, value, filename))
|
|
@@ -219,7 +226,7 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
219
226
|
if (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
|
|
220
227
|
// 2.1.1. If name is null, return failure.
|
|
221
228
|
if (name === null) {
|
|
222
|
-
|
|
229
|
+
throw parsingError('header name is null')
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
// 2.1.2. Return name, filename and contentType.
|
|
@@ -239,12 +246,12 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
239
246
|
|
|
240
247
|
// 2.4. If header name does not match the field-name token production, return failure.
|
|
241
248
|
if (!HTTP_TOKEN_CODEPOINTS.test(headerName.toString())) {
|
|
242
|
-
|
|
249
|
+
throw parsingError('header name does not match the field-name token production')
|
|
243
250
|
}
|
|
244
251
|
|
|
245
252
|
// 2.5. If the byte at position is not 0x3A (:), return failure.
|
|
246
253
|
if (input[position.position] !== 0x3a) {
|
|
247
|
-
|
|
254
|
+
throw parsingError('expected :')
|
|
248
255
|
}
|
|
249
256
|
|
|
250
257
|
// 2.6. Advance position by 1.
|
|
@@ -267,7 +274,7 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
267
274
|
// 2. If position does not point to a sequence of bytes starting with
|
|
268
275
|
// `form-data; name="`, return failure.
|
|
269
276
|
if (!bufferStartsWith(input, formDataNameBuffer, position)) {
|
|
270
|
-
|
|
277
|
+
throw parsingError('expected form-data; name=" for content-disposition header')
|
|
271
278
|
}
|
|
272
279
|
|
|
273
280
|
// 3. Advance position so it points at the byte after the next 0x22 (")
|
|
@@ -279,34 +286,61 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
279
286
|
// failure.
|
|
280
287
|
name = parseMultipartFormDataName(input, position)
|
|
281
288
|
|
|
282
|
-
if (name === null) {
|
|
283
|
-
return 'failure'
|
|
284
|
-
}
|
|
285
|
-
|
|
286
289
|
// 5. If position points to a sequence of bytes starting with `; filename="`:
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
290
|
+
if (input[position.position] === 0x3b /* ; */ && input[position.position + 1] === 0x20 /* ' ' */) {
|
|
291
|
+
const at = { position: position.position + 2 }
|
|
292
|
+
|
|
293
|
+
if (bufferStartsWith(input, filenameBuffer, at)) {
|
|
294
|
+
if (input[at.position + 8] === 0x2a /* '*' */) {
|
|
295
|
+
at.position += 10 // skip past filename*=
|
|
296
|
+
|
|
297
|
+
// Remove leading http tab and spaces. See RFC for examples.
|
|
298
|
+
// https://datatracker.ietf.org/doc/html/rfc6266#section-5
|
|
299
|
+
collectASequenceOfBytes(
|
|
300
|
+
(char) => char === 0x20 || char === 0x09,
|
|
301
|
+
input,
|
|
302
|
+
at
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
const headerValue = collectASequenceOfBytes(
|
|
306
|
+
(char) => char !== 0x20 && char !== 0x0d && char !== 0x0a, // ' ' or CRLF
|
|
307
|
+
input,
|
|
308
|
+
at
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if (
|
|
312
|
+
(headerValue[0] !== 0x75 && headerValue[0] !== 0x55) || // u or U
|
|
313
|
+
(headerValue[1] !== 0x74 && headerValue[1] !== 0x54) || // t or T
|
|
314
|
+
(headerValue[2] !== 0x66 && headerValue[2] !== 0x46) || // f or F
|
|
315
|
+
headerValue[3] !== 0x2d || // -
|
|
316
|
+
headerValue[4] !== 0x38 // 8
|
|
317
|
+
) {
|
|
318
|
+
throw parsingError('unknown encoding, expected utf-8\'\'')
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// skip utf-8''
|
|
322
|
+
filename = decodeURIComponent(new TextDecoder().decode(headerValue.subarray(7)))
|
|
323
|
+
|
|
324
|
+
position.position = at.position
|
|
325
|
+
} else {
|
|
326
|
+
// 1. Advance position so it points at the byte after the next 0x22 (") byte
|
|
327
|
+
// (the one in the sequence of bytes matched above).
|
|
328
|
+
position.position += 11
|
|
329
|
+
|
|
330
|
+
// Remove leading http tab and spaces. See RFC for examples.
|
|
331
|
+
// https://datatracker.ietf.org/doc/html/rfc6266#section-5
|
|
332
|
+
collectASequenceOfBytes(
|
|
333
|
+
(char) => char === 0x20 || char === 0x09,
|
|
334
|
+
input,
|
|
335
|
+
position
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
position.position++ // skip past " after removing whitespace
|
|
339
|
+
|
|
340
|
+
// 2. Set filename to the result of parsing a multipart/form-data name given
|
|
341
|
+
// input and position, if the result is not failure. Otherwise, return failure.
|
|
342
|
+
filename = parseMultipartFormDataName(input, position)
|
|
343
|
+
}
|
|
310
344
|
}
|
|
311
345
|
}
|
|
312
346
|
|
|
@@ -356,7 +390,7 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
356
390
|
// 2.9. If position does not point to a sequence of bytes starting with 0x0D 0x0A
|
|
357
391
|
// (CR LF), return failure. Otherwise, advance position by 2 (past the newline).
|
|
358
392
|
if (input[position.position] !== 0x0d && input[position.position + 1] !== 0x0a) {
|
|
359
|
-
|
|
393
|
+
throw parsingError('expected CRLF')
|
|
360
394
|
} else {
|
|
361
395
|
position.position += 2
|
|
362
396
|
}
|
|
@@ -382,7 +416,7 @@ function parseMultipartFormDataName (input, position) {
|
|
|
382
416
|
|
|
383
417
|
// 3. If the byte at position is not 0x22 ("), return failure. Otherwise, advance position by 1.
|
|
384
418
|
if (input[position.position] !== 0x22) {
|
|
385
|
-
|
|
419
|
+
throw parsingError('expected "')
|
|
386
420
|
} else {
|
|
387
421
|
position.position++
|
|
388
422
|
}
|
|
@@ -457,6 +491,10 @@ function bufferStartsWith (buffer, start, position) {
|
|
|
457
491
|
return true
|
|
458
492
|
}
|
|
459
493
|
|
|
494
|
+
function parsingError (cause) {
|
|
495
|
+
return new TypeError('Failed to parse body as FormData.', { cause: new TypeError(cause) })
|
|
496
|
+
}
|
|
497
|
+
|
|
460
498
|
module.exports = {
|
|
461
499
|
multipartFormDataParser,
|
|
462
500
|
validateBoundary
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { iteratorMixin } = require('./util')
|
|
4
|
-
const { kState } = require('./symbols')
|
|
5
4
|
const { kEnumerableProperty } = require('../../core/util')
|
|
6
5
|
const { webidl } = require('./webidl')
|
|
7
6
|
const { File: NativeFile } = require('node:buffer')
|
|
@@ -12,7 +11,11 @@ const File = globalThis.File ?? NativeFile
|
|
|
12
11
|
|
|
13
12
|
// https://xhr.spec.whatwg.org/#formdata
|
|
14
13
|
class FormData {
|
|
14
|
+
#state = []
|
|
15
|
+
|
|
15
16
|
constructor (form) {
|
|
17
|
+
webidl.util.markAsUncloneable(this)
|
|
18
|
+
|
|
16
19
|
if (form !== undefined) {
|
|
17
20
|
throw webidl.errors.conversionFailed({
|
|
18
21
|
prefix: 'FormData constructor',
|
|
@@ -20,8 +23,6 @@ class FormData {
|
|
|
20
23
|
types: ['undefined']
|
|
21
24
|
})
|
|
22
25
|
}
|
|
23
|
-
|
|
24
|
-
this[kState] = []
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
append (name, value, filename = undefined) {
|
|
@@ -32,7 +33,7 @@ class FormData {
|
|
|
32
33
|
|
|
33
34
|
name = webidl.converters.USVString(name)
|
|
34
35
|
|
|
35
|
-
if (arguments.length === 3 || value
|
|
36
|
+
if (arguments.length === 3 || webidl.is.Blob(value)) {
|
|
36
37
|
value = webidl.converters.Blob(value, prefix, 'value')
|
|
37
38
|
|
|
38
39
|
if (filename !== undefined) {
|
|
@@ -49,7 +50,7 @@ class FormData {
|
|
|
49
50
|
const entry = makeEntry(name, value, filename)
|
|
50
51
|
|
|
51
52
|
// 3. Append entry to this’s entry list.
|
|
52
|
-
this
|
|
53
|
+
this.#state.push(entry)
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
delete (name) {
|
|
@@ -62,7 +63,7 @@ class FormData {
|
|
|
62
63
|
|
|
63
64
|
// The delete(name) method steps are to remove all entries whose name
|
|
64
65
|
// is name from this’s entry list.
|
|
65
|
-
this
|
|
66
|
+
this.#state = this.#state.filter(entry => entry.name !== name)
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
get (name) {
|
|
@@ -75,14 +76,14 @@ class FormData {
|
|
|
75
76
|
|
|
76
77
|
// 1. If there is no entry whose name is name in this’s entry list,
|
|
77
78
|
// then return null.
|
|
78
|
-
const idx = this
|
|
79
|
+
const idx = this.#state.findIndex((entry) => entry.name === name)
|
|
79
80
|
if (idx === -1) {
|
|
80
81
|
return null
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// 2. Return the value of the first entry whose name is name from
|
|
84
85
|
// this’s entry list.
|
|
85
|
-
return this[
|
|
86
|
+
return this.#state[idx].value
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
getAll (name) {
|
|
@@ -97,7 +98,7 @@ class FormData {
|
|
|
97
98
|
// then return the empty list.
|
|
98
99
|
// 2. Return the values of all entries whose name is name, in order,
|
|
99
100
|
// from this’s entry list.
|
|
100
|
-
return this
|
|
101
|
+
return this.#state
|
|
101
102
|
.filter((entry) => entry.name === name)
|
|
102
103
|
.map((entry) => entry.value)
|
|
103
104
|
}
|
|
@@ -112,7 +113,7 @@ class FormData {
|
|
|
112
113
|
|
|
113
114
|
// The has(name) method steps are to return true if there is an entry
|
|
114
115
|
// whose name is name in this’s entry list; otherwise false.
|
|
115
|
-
return this
|
|
116
|
+
return this.#state.findIndex((entry) => entry.name === name) !== -1
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
set (name, value, filename = undefined) {
|
|
@@ -123,7 +124,7 @@ class FormData {
|
|
|
123
124
|
|
|
124
125
|
name = webidl.converters.USVString(name)
|
|
125
126
|
|
|
126
|
-
if (arguments.length === 3 || value
|
|
127
|
+
if (arguments.length === 3 || webidl.is.Blob(value)) {
|
|
127
128
|
value = webidl.converters.Blob(value, prefix, 'value')
|
|
128
129
|
|
|
129
130
|
if (filename !== undefined) {
|
|
@@ -144,21 +145,21 @@ class FormData {
|
|
|
144
145
|
|
|
145
146
|
// 3. If there are entries in this’s entry list whose name is name, then
|
|
146
147
|
// replace the first such entry with entry and remove the others.
|
|
147
|
-
const idx = this
|
|
148
|
+
const idx = this.#state.findIndex((entry) => entry.name === name)
|
|
148
149
|
if (idx !== -1) {
|
|
149
|
-
this
|
|
150
|
-
...this
|
|
150
|
+
this.#state = [
|
|
151
|
+
...this.#state.slice(0, idx),
|
|
151
152
|
entry,
|
|
152
|
-
...this
|
|
153
|
+
...this.#state.slice(idx + 1).filter((entry) => entry.name !== name)
|
|
153
154
|
]
|
|
154
155
|
} else {
|
|
155
156
|
// 4. Otherwise, append entry to this’s entry list.
|
|
156
|
-
this
|
|
157
|
+
this.#state.push(entry)
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
[nodeUtil.inspect.custom] (depth, options) {
|
|
161
|
-
const state = this
|
|
162
|
+
const state = this.#state.reduce((a, b) => {
|
|
162
163
|
if (a[b.name]) {
|
|
163
164
|
if (Array.isArray(a[b.name])) {
|
|
164
165
|
a[b.name].push(b.value)
|
|
@@ -180,9 +181,28 @@ class FormData {
|
|
|
180
181
|
// remove [Object null prototype]
|
|
181
182
|
return `FormData ${output.slice(output.indexOf(']') + 2)}`
|
|
182
183
|
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {FormData} formData
|
|
187
|
+
*/
|
|
188
|
+
static getFormDataState (formData) {
|
|
189
|
+
return formData.#state
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {FormData} formData
|
|
194
|
+
* @param {any[]} newState
|
|
195
|
+
*/
|
|
196
|
+
static setFormDataState (formData, newState) {
|
|
197
|
+
formData.#state = newState
|
|
198
|
+
}
|
|
183
199
|
}
|
|
184
200
|
|
|
185
|
-
|
|
201
|
+
const { getFormDataState, setFormDataState } = FormData
|
|
202
|
+
Reflect.deleteProperty(FormData, 'getFormDataState')
|
|
203
|
+
Reflect.deleteProperty(FormData, 'setFormDataState')
|
|
204
|
+
|
|
205
|
+
iteratorMixin('FormData', FormData, getFormDataState, 'name', 'value')
|
|
186
206
|
|
|
187
207
|
Object.defineProperties(FormData.prototype, {
|
|
188
208
|
append: kEnumerableProperty,
|
|
@@ -217,7 +237,7 @@ function makeEntry (name, value, filename) {
|
|
|
217
237
|
|
|
218
238
|
// 1. If value is not a File object, then set value to a new File object,
|
|
219
239
|
// representing the same bytes, whose name attribute value is "blob"
|
|
220
|
-
if (!(value
|
|
240
|
+
if (!webidl.is.File(value)) {
|
|
221
241
|
value = new File([value], 'blob', { type: value.type })
|
|
222
242
|
}
|
|
223
243
|
|
|
@@ -238,4 +258,6 @@ function makeEntry (name, value, filename) {
|
|
|
238
258
|
return { name, value }
|
|
239
259
|
}
|
|
240
260
|
|
|
241
|
-
|
|
261
|
+
webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData)
|
|
262
|
+
|
|
263
|
+
module.exports = { FormData, makeEntry, setFormDataState }
|