undici 5.12.0 → 5.14.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/README.md +7 -5
- package/docs/api/Connector.md +3 -1
- package/docs/assets/lifecycle-diagram.png +0 -0
- package/index.d.ts +23 -23
- package/lib/core/connect.js +62 -19
- package/lib/fetch/body.js +56 -27
- package/lib/fetch/constants.js +19 -1
- package/lib/fetch/dataURL.js +5 -70
- package/lib/fetch/file.js +25 -46
- package/lib/fetch/formdata.js +30 -74
- package/lib/fetch/headers.js +52 -91
- package/lib/fetch/index.js +49 -43
- package/lib/fetch/request.js +35 -113
- package/lib/fetch/response.js +26 -54
- package/lib/fetch/symbols.js +2 -1
- package/lib/fetch/util.js +64 -21
- package/lib/fetch/webidl.js +38 -54
- package/lib/fileapi/filereader.js +47 -71
- package/lib/fileapi/progressevent.js +3 -9
- package/lib/fileapi/util.js +3 -9
- package/lib/mock/mock-utils.js +22 -4
- package/package.json +6 -6
- package/types/agent.d.ts +4 -5
- package/types/api.d.ts +1 -1
- package/types/balanced-pool.d.ts +3 -4
- package/types/client.d.ts +5 -5
- package/types/connector.d.ts +6 -4
- package/types/diagnostics-channel.d.ts +4 -4
- package/types/dispatcher.d.ts +23 -23
- package/types/errors.d.ts +3 -3
- package/types/fetch.d.ts +1 -1
- package/types/filereader.d.ts +8 -3
- package/types/global-dispatcher.d.ts +1 -1
- package/types/interceptors.d.ts +2 -2
- package/types/mock-agent.d.ts +3 -3
- package/types/mock-client.d.ts +4 -4
- package/types/mock-errors.d.ts +3 -3
- package/types/mock-interceptor.d.ts +1 -1
- package/types/mock-pool.d.ts +4 -4
- package/types/patch.d.ts +51 -0
- package/types/pool-stats.d.ts +2 -2
- package/types/pool.d.ts +5 -6
- package/types/proxy-agent.d.ts +3 -3
- package/types/readable.d.ts +1 -1
- package/types/webidl.d.ts +213 -0
package/README.md
CHANGED
|
@@ -178,10 +178,6 @@ Implements [fetch](https://fetch.spec.whatwg.org/#fetch-method).
|
|
|
178
178
|
|
|
179
179
|
Only supported on Node 16.8+.
|
|
180
180
|
|
|
181
|
-
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard.
|
|
182
|
-
We plan to ship breaking changes to this feature until it is out of experimental.
|
|
183
|
-
Help us improve the test coverage by following instructions at [nodejs/undici/#951](https://github.com/nodejs/undici/issues/951).
|
|
184
|
-
|
|
185
181
|
Basic usage example:
|
|
186
182
|
|
|
187
183
|
```js
|
|
@@ -234,9 +230,15 @@ const data = {
|
|
|
234
230
|
},
|
|
235
231
|
}
|
|
236
232
|
|
|
237
|
-
await fetch('https://example.com', { body: data, method: 'POST' })
|
|
233
|
+
await fetch('https://example.com', { body: data, method: 'POST', duplex: 'half' })
|
|
238
234
|
```
|
|
239
235
|
|
|
236
|
+
#### `request.duplex`
|
|
237
|
+
|
|
238
|
+
- half
|
|
239
|
+
|
|
240
|
+
In this implementation of fetch, `request.duplex` must be set if `request.body` is `ReadableStream` or `Async Iterables`. And fetch requests are currently always be full duplex. More detail refer to [Fetch Standard.](https://fetch.spec.whatwg.org/#dom-requestinit-duplex)
|
|
241
|
+
|
|
240
242
|
#### `response.body`
|
|
241
243
|
|
|
242
244
|
Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
|
package/docs/api/Connector.md
CHANGED
|
@@ -24,8 +24,10 @@ Once you call `buildConnector`, it will return a connector function, which takes
|
|
|
24
24
|
* **hostname** `string` (required)
|
|
25
25
|
* **host** `string` (optional)
|
|
26
26
|
* **protocol** `string` (required)
|
|
27
|
-
* **port** `
|
|
27
|
+
* **port** `string` (required)
|
|
28
28
|
* **servername** `string` (optional)
|
|
29
|
+
* **localAddress** `string | null` (optional) Local address the socket should connect from.
|
|
30
|
+
* **httpSocket** `Socket` (optional) Establish secure connection on a given socket rather than creating a new socket. It can only be sent on TLS update.
|
|
29
31
|
|
|
30
32
|
### Basic example
|
|
31
33
|
|
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import Dispatcher
|
|
1
|
+
import Dispatcher from'./types/dispatcher'
|
|
2
2
|
import { setGlobalDispatcher, getGlobalDispatcher } from './types/global-dispatcher'
|
|
3
3
|
import { setGlobalOrigin, getGlobalOrigin } from './types/global-origin'
|
|
4
|
-
import Pool
|
|
4
|
+
import Pool from'./types/pool'
|
|
5
5
|
import { RedirectHandler, DecoratorHandler } from './types/handlers'
|
|
6
6
|
|
|
7
|
-
import BalancedPool
|
|
8
|
-
import Client
|
|
9
|
-
import buildConnector
|
|
10
|
-
import errors
|
|
11
|
-
import Agent
|
|
12
|
-
import MockClient
|
|
13
|
-
import MockPool
|
|
14
|
-
import MockAgent
|
|
15
|
-
import mockErrors
|
|
16
|
-
import ProxyAgent
|
|
7
|
+
import BalancedPool from './types/balanced-pool'
|
|
8
|
+
import Client from'./types/client'
|
|
9
|
+
import buildConnector from'./types/connector'
|
|
10
|
+
import errors from'./types/errors'
|
|
11
|
+
import Agent from'./types/agent'
|
|
12
|
+
import MockClient from'./types/mock-client'
|
|
13
|
+
import MockPool from'./types/mock-pool'
|
|
14
|
+
import MockAgent from'./types/mock-agent'
|
|
15
|
+
import mockErrors from'./types/mock-errors'
|
|
16
|
+
import ProxyAgent from'./types/proxy-agent'
|
|
17
17
|
import { request, pipeline, stream, connect, upgrade } from './types/api'
|
|
18
18
|
|
|
19
19
|
export * from './types/fetch'
|
|
@@ -27,16 +27,16 @@ export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent,
|
|
|
27
27
|
export default Undici
|
|
28
28
|
|
|
29
29
|
declare namespace Undici {
|
|
30
|
-
var Dispatcher: typeof import('./types/dispatcher')
|
|
31
|
-
var Pool: typeof import('./types/pool');
|
|
30
|
+
var Dispatcher: typeof import('./types/dispatcher').default
|
|
31
|
+
var Pool: typeof import('./types/pool').default;
|
|
32
32
|
var RedirectHandler: typeof import ('./types/handlers').RedirectHandler
|
|
33
33
|
var DecoratorHandler: typeof import ('./types/handlers').DecoratorHandler
|
|
34
34
|
var createRedirectInterceptor: typeof import ('./types/interceptors').createRedirectInterceptor
|
|
35
|
-
var BalancedPool: typeof import('./types/balanced-pool');
|
|
36
|
-
var Client: typeof import('./types/client');
|
|
37
|
-
var buildConnector: typeof import('./types/connector');
|
|
38
|
-
var errors: typeof import('./types/errors');
|
|
39
|
-
var Agent: typeof import('./types/agent');
|
|
35
|
+
var BalancedPool: typeof import('./types/balanced-pool').default;
|
|
36
|
+
var Client: typeof import('./types/client').default;
|
|
37
|
+
var buildConnector: typeof import('./types/connector').default;
|
|
38
|
+
var errors: typeof import('./types/errors').default;
|
|
39
|
+
var Agent: typeof import('./types/agent').default;
|
|
40
40
|
var setGlobalDispatcher: typeof import('./types/global-dispatcher').setGlobalDispatcher;
|
|
41
41
|
var getGlobalDispatcher: typeof import('./types/global-dispatcher').getGlobalDispatcher;
|
|
42
42
|
var request: typeof import('./types/api').request;
|
|
@@ -44,9 +44,9 @@ declare namespace Undici {
|
|
|
44
44
|
var pipeline: typeof import('./types/api').pipeline;
|
|
45
45
|
var connect: typeof import('./types/api').connect;
|
|
46
46
|
var upgrade: typeof import('./types/api').upgrade;
|
|
47
|
-
var MockClient: typeof import('./types/mock-client');
|
|
48
|
-
var MockPool: typeof import('./types/mock-pool');
|
|
49
|
-
var MockAgent: typeof import('./types/mock-agent');
|
|
50
|
-
var mockErrors: typeof import('./types/mock-errors');
|
|
47
|
+
var MockClient: typeof import('./types/mock-client').default;
|
|
48
|
+
var MockPool: typeof import('./types/mock-pool').default;
|
|
49
|
+
var MockAgent: typeof import('./types/mock-agent').default;
|
|
50
|
+
var mockErrors: typeof import('./types/mock-errors').default;
|
|
51
51
|
var fetch: typeof import('./types/fetch').fetch;
|
|
52
52
|
}
|
package/lib/core/connect.js
CHANGED
|
@@ -4,6 +4,7 @@ const net = require('net')
|
|
|
4
4
|
const assert = require('assert')
|
|
5
5
|
const util = require('./util')
|
|
6
6
|
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
|
|
7
|
+
|
|
7
8
|
let tls // include tls conditionally since it is not always available
|
|
8
9
|
|
|
9
10
|
// TODO: session re-use does not wait for the first
|
|
@@ -11,15 +12,73 @@ let tls // include tls conditionally since it is not always available
|
|
|
11
12
|
// resolve the same servername multiple times even when
|
|
12
13
|
// re-use is enabled.
|
|
13
14
|
|
|
15
|
+
let SessionCache
|
|
16
|
+
if (global.FinalizationRegistry) {
|
|
17
|
+
SessionCache = class WeakSessionCache {
|
|
18
|
+
constructor (maxCachedSessions) {
|
|
19
|
+
this._maxCachedSessions = maxCachedSessions
|
|
20
|
+
this._sessionCache = new Map()
|
|
21
|
+
this._sessionRegistry = new global.FinalizationRegistry((key) => {
|
|
22
|
+
if (this._sessionCache.size < this._maxCachedSessions) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ref = this._sessionCache.get(key)
|
|
27
|
+
if (ref !== undefined && ref.deref() === undefined) {
|
|
28
|
+
this._sessionCache.delete(key)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get (sessionKey) {
|
|
34
|
+
const ref = this._sessionCache.get(sessionKey)
|
|
35
|
+
return ref ? ref.deref() : null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set (sessionKey, session) {
|
|
39
|
+
if (this._maxCachedSessions === 0) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this._sessionCache.set(sessionKey, new WeakRef(session))
|
|
44
|
+
this._sessionRegistry.register(session, sessionKey)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
SessionCache = class SimpleSessionCache {
|
|
49
|
+
constructor (maxCachedSessions) {
|
|
50
|
+
this._maxCachedSessions = maxCachedSessions
|
|
51
|
+
this._sessionCache = new Map()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get (sessionKey) {
|
|
55
|
+
return this._sessionCache.get(sessionKey)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set (sessionKey, session) {
|
|
59
|
+
if (this._maxCachedSessions === 0) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this._sessionCache.size >= this._maxCachedSessions) {
|
|
64
|
+
// remove the oldest session
|
|
65
|
+
const { value: oldestKey } = this._sessionCache.keys().next()
|
|
66
|
+
this._sessionCache.delete(oldestKey)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this._sessionCache.set(sessionKey, session)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
14
74
|
function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
|
15
75
|
if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
|
|
16
76
|
throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
|
|
17
77
|
}
|
|
18
78
|
|
|
19
79
|
const options = { path: socketPath, ...opts }
|
|
20
|
-
const sessionCache = new
|
|
80
|
+
const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions)
|
|
21
81
|
timeout = timeout == null ? 10e3 : timeout
|
|
22
|
-
maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions
|
|
23
82
|
|
|
24
83
|
return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
|
|
25
84
|
let socket
|
|
@@ -47,25 +106,9 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
|
|
47
106
|
|
|
48
107
|
socket
|
|
49
108
|
.on('session', function (session) {
|
|
50
|
-
//
|
|
51
|
-
if (maxCachedSessions === 0) {
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (sessionCache.size >= maxCachedSessions) {
|
|
56
|
-
// remove the oldest session
|
|
57
|
-
const { value: oldestKey } = sessionCache.keys().next()
|
|
58
|
-
sessionCache.delete(oldestKey)
|
|
59
|
-
}
|
|
60
|
-
|
|
109
|
+
// TODO (fix): Can a session become invalid once established? Don't think so?
|
|
61
110
|
sessionCache.set(sessionKey, session)
|
|
62
111
|
})
|
|
63
|
-
.on('error', function (err) {
|
|
64
|
-
if (sessionKey && err.code !== 'UND_ERR_INFO') {
|
|
65
|
-
// TODO (fix): Only delete for session related errors.
|
|
66
|
-
sessionCache.delete(sessionKey)
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
112
|
} else {
|
|
70
113
|
assert(!httpSocket, 'httpSocket can only be sent on TLS update')
|
|
71
114
|
socket = net.connect({
|
package/lib/fetch/body.js
CHANGED
|
@@ -2,22 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
const Busboy = require('busboy')
|
|
4
4
|
const util = require('../core/util')
|
|
5
|
-
const { ReadableStreamFrom,
|
|
5
|
+
const { ReadableStreamFrom, isBlobLike, isReadableStreamLike, readableStreamClose } = require('./util')
|
|
6
6
|
const { FormData } = require('./formdata')
|
|
7
7
|
const { kState } = require('./symbols')
|
|
8
8
|
const { webidl } = require('./webidl')
|
|
9
9
|
const { DOMException, structuredClone } = require('./constants')
|
|
10
|
-
const { Blob } = require('buffer')
|
|
10
|
+
const { Blob, File: NativeFile } = require('buffer')
|
|
11
11
|
const { kBodyUsed } = require('../core/symbols')
|
|
12
12
|
const assert = require('assert')
|
|
13
13
|
const { isErrored } = require('../core/util')
|
|
14
14
|
const { isUint8Array, isArrayBuffer } = require('util/types')
|
|
15
|
-
const { File } = require('./file')
|
|
15
|
+
const { File: UndiciFile } = require('./file')
|
|
16
16
|
const { StringDecoder } = require('string_decoder')
|
|
17
17
|
const { parseMIMEType, serializeAMimeType } = require('./dataURL')
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
let ReadableStream = globalThis.ReadableStream
|
|
20
|
+
|
|
21
|
+
/** @type {globalThis['File']} */
|
|
22
|
+
const File = NativeFile ?? UndiciFile
|
|
21
23
|
|
|
22
24
|
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
|
23
25
|
function extractBody (object, keepalive = false) {
|
|
@@ -66,9 +68,13 @@ function extractBody (object, keepalive = false) {
|
|
|
66
68
|
let type = null
|
|
67
69
|
|
|
68
70
|
// 10. Switch on object:
|
|
69
|
-
if (object
|
|
70
|
-
//
|
|
71
|
-
//
|
|
71
|
+
if (typeof object === 'string') {
|
|
72
|
+
// Set source to the UTF-8 encoding of object.
|
|
73
|
+
// Note: setting source to a Uint8Array here breaks some mocking assumptions.
|
|
74
|
+
source = object
|
|
75
|
+
|
|
76
|
+
// Set type to `text/plain;charset=UTF-8`.
|
|
77
|
+
type = 'text/plain;charset=UTF-8'
|
|
72
78
|
} else if (object instanceof URLSearchParams) {
|
|
73
79
|
// URLSearchParams
|
|
74
80
|
|
|
@@ -126,7 +132,8 @@ function extractBody (object, keepalive = false) {
|
|
|
126
132
|
|
|
127
133
|
yield * value.stream()
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
// '\r\n' encoded
|
|
136
|
+
yield new Uint8Array([13, 10])
|
|
130
137
|
}
|
|
131
138
|
}
|
|
132
139
|
|
|
@@ -137,7 +144,33 @@ function extractBody (object, keepalive = false) {
|
|
|
137
144
|
source = object
|
|
138
145
|
|
|
139
146
|
// Set length to unclear, see html/6424 for improving this.
|
|
140
|
-
|
|
147
|
+
length = (() => {
|
|
148
|
+
const prefixLength = prefix.length
|
|
149
|
+
const boundaryLength = boundary.length
|
|
150
|
+
let bodyLength = 0
|
|
151
|
+
|
|
152
|
+
for (const [name, value] of object) {
|
|
153
|
+
if (typeof value === 'string') {
|
|
154
|
+
bodyLength +=
|
|
155
|
+
prefixLength +
|
|
156
|
+
Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
|
|
157
|
+
} else {
|
|
158
|
+
bodyLength +=
|
|
159
|
+
prefixLength +
|
|
160
|
+
Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : '')) +
|
|
161
|
+
2 + // \r\n
|
|
162
|
+
`Content-Type: ${
|
|
163
|
+
value.type || 'application/octet-stream'
|
|
164
|
+
}\r\n\r\n`.length
|
|
165
|
+
|
|
166
|
+
// value is a Blob or File, and \r\n
|
|
167
|
+
bodyLength += value.size + 2
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
bodyLength += boundaryLength + 4 // --boundary--
|
|
172
|
+
return bodyLength
|
|
173
|
+
})()
|
|
141
174
|
|
|
142
175
|
// Set type to `multipart/form-data; boundary=`,
|
|
143
176
|
// followed by the multipart/form-data boundary string generated
|
|
@@ -157,6 +190,11 @@ function extractBody (object, keepalive = false) {
|
|
|
157
190
|
if (object.type) {
|
|
158
191
|
type = object.type
|
|
159
192
|
}
|
|
193
|
+
} else if (object instanceof Uint8Array) {
|
|
194
|
+
// byte sequence
|
|
195
|
+
|
|
196
|
+
// Set source to object.
|
|
197
|
+
source = object
|
|
160
198
|
} else if (typeof object[Symbol.asyncIterator] === 'function') {
|
|
161
199
|
// If keepalive is true, then throw a TypeError.
|
|
162
200
|
if (keepalive) {
|
|
@@ -172,17 +210,10 @@ function extractBody (object, keepalive = false) {
|
|
|
172
210
|
|
|
173
211
|
stream =
|
|
174
212
|
object instanceof ReadableStream ? object : ReadableStreamFrom(object)
|
|
175
|
-
} else {
|
|
176
|
-
// TODO: byte sequence?
|
|
177
|
-
// TODO: scalar value string?
|
|
178
|
-
// TODO: else?
|
|
179
|
-
source = toUSVString(object)
|
|
180
|
-
type = 'text/plain;charset=UTF-8'
|
|
181
213
|
}
|
|
182
214
|
|
|
183
215
|
// 11. If source is a byte sequence, then set action to a
|
|
184
216
|
// step that returns source and length to source’s length.
|
|
185
|
-
// TODO: What is a "byte sequence?"
|
|
186
217
|
if (typeof source === 'string' || util.isBuffer(source)) {
|
|
187
218
|
length = Buffer.byteLength(source)
|
|
188
219
|
}
|
|
@@ -329,9 +360,7 @@ function bodyMixinMethods (instance) {
|
|
|
329
360
|
},
|
|
330
361
|
|
|
331
362
|
async formData () {
|
|
332
|
-
|
|
333
|
-
throw new TypeError('Illegal invocation')
|
|
334
|
-
}
|
|
363
|
+
webidl.brandCheck(this, instance)
|
|
335
364
|
|
|
336
365
|
throwIfAborted(this[kState])
|
|
337
366
|
|
|
@@ -347,7 +376,10 @@ function bodyMixinMethods (instance) {
|
|
|
347
376
|
let busboy
|
|
348
377
|
|
|
349
378
|
try {
|
|
350
|
-
busboy = Busboy({
|
|
379
|
+
busboy = Busboy({
|
|
380
|
+
headers,
|
|
381
|
+
defParamCharset: 'utf8'
|
|
382
|
+
})
|
|
351
383
|
} catch (err) {
|
|
352
384
|
// Error due to headers:
|
|
353
385
|
throw Object.assign(new TypeError(), { cause: err })
|
|
@@ -360,7 +392,7 @@ function bodyMixinMethods (instance) {
|
|
|
360
392
|
const { filename, encoding, mimeType } = info
|
|
361
393
|
const chunks = []
|
|
362
394
|
|
|
363
|
-
if (encoding.toLowerCase() === 'base64') {
|
|
395
|
+
if (encoding === 'base64' || encoding.toLowerCase() === 'base64') {
|
|
364
396
|
let base64chunk = ''
|
|
365
397
|
|
|
366
398
|
value.on('data', (chunk) => {
|
|
@@ -433,7 +465,7 @@ function bodyMixinMethods (instance) {
|
|
|
433
465
|
throwIfAborted(this[kState])
|
|
434
466
|
|
|
435
467
|
// Otherwise, throw a TypeError.
|
|
436
|
-
webidl.errors.exception({
|
|
468
|
+
throw webidl.errors.exception({
|
|
437
469
|
header: `${instance.name}.formData`,
|
|
438
470
|
message: 'Could not parse content as FormData.'
|
|
439
471
|
})
|
|
@@ -450,11 +482,8 @@ function mixinBody (prototype) {
|
|
|
450
482
|
|
|
451
483
|
// https://fetch.spec.whatwg.org/#concept-body-consume-body
|
|
452
484
|
async function specConsumeBody (object, type, instance) {
|
|
453
|
-
|
|
454
|
-
throw new TypeError('Illegal invocation')
|
|
455
|
-
}
|
|
485
|
+
webidl.brandCheck(object, instance)
|
|
456
486
|
|
|
457
|
-
// TODO: why is this needed?
|
|
458
487
|
throwIfAborted(object[kState])
|
|
459
488
|
|
|
460
489
|
// 1. If object is unusable, then return a promise rejected
|
package/lib/fetch/constants.js
CHANGED
|
@@ -8,6 +8,17 @@ const nullBodyStatus = [101, 204, 205, 304]
|
|
|
8
8
|
|
|
9
9
|
const redirectStatus = [301, 302, 303, 307, 308]
|
|
10
10
|
|
|
11
|
+
// https://fetch.spec.whatwg.org/#block-bad-port
|
|
12
|
+
const badPorts = [
|
|
13
|
+
'1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
|
|
14
|
+
'87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
|
|
15
|
+
'139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
|
|
16
|
+
'540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
|
|
17
|
+
'2049', '3659', '4045', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6697',
|
|
18
|
+
'10080'
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
// https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
|
|
11
22
|
const referrerPolicy = [
|
|
12
23
|
'',
|
|
13
24
|
'no-referrer',
|
|
@@ -44,6 +55,11 @@ const requestBodyHeader = [
|
|
|
44
55
|
'content-type'
|
|
45
56
|
]
|
|
46
57
|
|
|
58
|
+
// https://fetch.spec.whatwg.org/#enumdef-requestduplex
|
|
59
|
+
const requestDuplex = [
|
|
60
|
+
'half'
|
|
61
|
+
]
|
|
62
|
+
|
|
47
63
|
// http://fetch.spec.whatwg.org/#forbidden-method
|
|
48
64
|
const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK']
|
|
49
65
|
|
|
@@ -108,5 +124,7 @@ module.exports = {
|
|
|
108
124
|
redirectStatus,
|
|
109
125
|
corsSafeListedMethods,
|
|
110
126
|
nullBodyStatus,
|
|
111
|
-
safeMethods
|
|
127
|
+
safeMethods,
|
|
128
|
+
badPorts,
|
|
129
|
+
requestDuplex
|
|
112
130
|
}
|
package/lib/fetch/dataURL.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const { atob } = require('buffer')
|
|
3
|
-
const {
|
|
3
|
+
const { format } = require('url')
|
|
4
|
+
const { isValidHTTPToken, isomorphicDecode } = require('./util')
|
|
4
5
|
|
|
5
6
|
const encoder = new TextEncoder()
|
|
6
7
|
|
|
@@ -54,7 +55,6 @@ function dataURLProcessor (dataURL) {
|
|
|
54
55
|
const encodedBody = input.slice(mimeTypeLength + 1)
|
|
55
56
|
|
|
56
57
|
// 10. Let body be the percent-decoding of encodedBody.
|
|
57
|
-
/** @type {Uint8Array|string} */
|
|
58
58
|
let body = stringPercentDecode(encodedBody)
|
|
59
59
|
|
|
60
60
|
// 11. If mimeType ends with U+003B (;), followed by
|
|
@@ -62,7 +62,8 @@ function dataURLProcessor (dataURL) {
|
|
|
62
62
|
// case-insensitive match for "base64", then:
|
|
63
63
|
if (/;(\u0020){0,}base64$/i.test(mimeType)) {
|
|
64
64
|
// 1. Let stringBody be the isomorphic decode of body.
|
|
65
|
-
const stringBody =
|
|
65
|
+
const stringBody = isomorphicDecode(body)
|
|
66
|
+
|
|
66
67
|
// 2. Set body to the forgiving-base64 decode of
|
|
67
68
|
// stringBody.
|
|
68
69
|
body = forgivingBase64(stringBody)
|
|
@@ -111,73 +112,7 @@ function dataURLProcessor (dataURL) {
|
|
|
111
112
|
* @param {boolean} excludeFragment
|
|
112
113
|
*/
|
|
113
114
|
function URLSerializer (url, excludeFragment = false) {
|
|
114
|
-
|
|
115
|
-
let output = url.protocol
|
|
116
|
-
|
|
117
|
-
// 2. If url’s host is non-null:
|
|
118
|
-
if (url.host.length > 0) {
|
|
119
|
-
// 1. Append "//" to output.
|
|
120
|
-
output += '//'
|
|
121
|
-
|
|
122
|
-
// 2. If url includes credentials, then:
|
|
123
|
-
if (url.username.length > 0 || url.password.length > 0) {
|
|
124
|
-
// 1. Append url’s username to output.
|
|
125
|
-
output += url.username
|
|
126
|
-
|
|
127
|
-
// 2. If url’s password is not the empty string, then append U+003A (:),
|
|
128
|
-
// followed by url’s password, to output.
|
|
129
|
-
if (url.password.length > 0) {
|
|
130
|
-
output += ':' + url.password
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 3. Append U+0040 (@) to output.
|
|
134
|
-
output += '@'
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 3. Append url’s host, serialized, to output.
|
|
138
|
-
output += decodeURIComponent(url.hostname)
|
|
139
|
-
|
|
140
|
-
// 4. If url’s port is non-null, append U+003A (:) followed by url’s port,
|
|
141
|
-
// serialized, to output.
|
|
142
|
-
if (url.port.length > 0) {
|
|
143
|
-
output += ':' + url.port
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 3. If url’s host is null, url does not have an opaque path,
|
|
148
|
-
// url’s path’s size is greater than 1, and url’s path[0]
|
|
149
|
-
// is the empty string, then append U+002F (/) followed by
|
|
150
|
-
// U+002E (.) to output.
|
|
151
|
-
// Note: This prevents web+demo:/.//not-a-host/ or web+demo:/path/..//not-a-host/,
|
|
152
|
-
// when parsed and then serialized, from ending up as web+demo://not-a-host/
|
|
153
|
-
// (they end up as web+demo:/.//not-a-host/).
|
|
154
|
-
// Undici implementation note: url's path[0] can never be an
|
|
155
|
-
// empty string, so we have to slightly alter what the spec says.
|
|
156
|
-
if (
|
|
157
|
-
url.host.length === 0 &&
|
|
158
|
-
url.pathname.length > 1 &&
|
|
159
|
-
url.href.slice(url.protocol.length + 1)[0] === '.'
|
|
160
|
-
) {
|
|
161
|
-
output += '/.'
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// 4. Append the result of URL path serializing url to output.
|
|
165
|
-
output += url.pathname
|
|
166
|
-
|
|
167
|
-
// 5. If url’s query is non-null, append U+003F (?),
|
|
168
|
-
// followed by url’s query, to output.
|
|
169
|
-
if (url.search.length > 0) {
|
|
170
|
-
output += url.search
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 6. If exclude fragment is false and url’s fragment is non-null,
|
|
174
|
-
// then append U+0023 (#), followed by url’s fragment, to output.
|
|
175
|
-
if (excludeFragment === false && url.hash.length > 0) {
|
|
176
|
-
output += url.hash
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// 7. Return output.
|
|
180
|
-
return output
|
|
115
|
+
return format(url, { fragment: !excludeFragment })
|
|
181
116
|
}
|
|
182
117
|
|
|
183
118
|
// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|