1 | /* globals AbortController */
|
---|
2 |
|
---|
3 | 'use strict'
|
---|
4 |
|
---|
5 | const { extractBody, mixinBody, cloneBody } = require('./body')
|
---|
6 | const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
|
---|
7 | const { FinalizationRegistry } = require('../compat/dispatcher-weakref')()
|
---|
8 | const util = require('../core/util')
|
---|
9 | const {
|
---|
10 | isValidHTTPToken,
|
---|
11 | sameOrigin,
|
---|
12 | normalizeMethod,
|
---|
13 | makePolicyContainer,
|
---|
14 | normalizeMethodRecord
|
---|
15 | } = require('./util')
|
---|
16 | const {
|
---|
17 | forbiddenMethodsSet,
|
---|
18 | corsSafeListedMethodsSet,
|
---|
19 | referrerPolicy,
|
---|
20 | requestRedirect,
|
---|
21 | requestMode,
|
---|
22 | requestCredentials,
|
---|
23 | requestCache,
|
---|
24 | requestDuplex
|
---|
25 | } = require('./constants')
|
---|
26 | const { kEnumerableProperty } = util
|
---|
27 | const { kHeaders, kSignal, kState, kGuard, kRealm } = require('./symbols')
|
---|
28 | const { webidl } = require('./webidl')
|
---|
29 | const { getGlobalOrigin } = require('./global')
|
---|
30 | const { URLSerializer } = require('./dataURL')
|
---|
31 | const { kHeadersList, kConstruct } = require('../core/symbols')
|
---|
32 | const assert = require('assert')
|
---|
33 | const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = require('events')
|
---|
34 |
|
---|
35 | let TransformStream = globalThis.TransformStream
|
---|
36 |
|
---|
37 | const kAbortController = Symbol('abortController')
|
---|
38 |
|
---|
39 | const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
|
---|
40 | signal.removeEventListener('abort', abort)
|
---|
41 | })
|
---|
42 |
|
---|
43 | // https://fetch.spec.whatwg.org/#request-class
|
---|
44 | class Request {
|
---|
45 | // https://fetch.spec.whatwg.org/#dom-request
|
---|
46 | constructor (input, init = {}) {
|
---|
47 | if (input === kConstruct) {
|
---|
48 | return
|
---|
49 | }
|
---|
50 |
|
---|
51 | webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' })
|
---|
52 |
|
---|
53 | input = webidl.converters.RequestInfo(input)
|
---|
54 | init = webidl.converters.RequestInit(init)
|
---|
55 |
|
---|
56 | // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
|
---|
57 | this[kRealm] = {
|
---|
58 | settingsObject: {
|
---|
59 | baseUrl: getGlobalOrigin(),
|
---|
60 | get origin () {
|
---|
61 | return this.baseUrl?.origin
|
---|
62 | },
|
---|
63 | policyContainer: makePolicyContainer()
|
---|
64 | }
|
---|
65 | }
|
---|
66 |
|
---|
67 | // 1. Let request be null.
|
---|
68 | let request = null
|
---|
69 |
|
---|
70 | // 2. Let fallbackMode be null.
|
---|
71 | let fallbackMode = null
|
---|
72 |
|
---|
73 | // 3. Let baseURL be this’s relevant settings object’s API base URL.
|
---|
74 | const baseUrl = this[kRealm].settingsObject.baseUrl
|
---|
75 |
|
---|
76 | // 4. Let signal be null.
|
---|
77 | let signal = null
|
---|
78 |
|
---|
79 | // 5. If input is a string, then:
|
---|
80 | if (typeof input === 'string') {
|
---|
81 | // 1. Let parsedURL be the result of parsing input with baseURL.
|
---|
82 | // 2. If parsedURL is failure, then throw a TypeError.
|
---|
83 | let parsedURL
|
---|
84 | try {
|
---|
85 | parsedURL = new URL(input, baseUrl)
|
---|
86 | } catch (err) {
|
---|
87 | throw new TypeError('Failed to parse URL from ' + input, { cause: err })
|
---|
88 | }
|
---|
89 |
|
---|
90 | // 3. If parsedURL includes credentials, then throw a TypeError.
|
---|
91 | if (parsedURL.username || parsedURL.password) {
|
---|
92 | throw new TypeError(
|
---|
93 | 'Request cannot be constructed from a URL that includes credentials: ' +
|
---|
94 | input
|
---|
95 | )
|
---|
96 | }
|
---|
97 |
|
---|
98 | // 4. Set request to a new request whose URL is parsedURL.
|
---|
99 | request = makeRequest({ urlList: [parsedURL] })
|
---|
100 |
|
---|
101 | // 5. Set fallbackMode to "cors".
|
---|
102 | fallbackMode = 'cors'
|
---|
103 | } else {
|
---|
104 | // 6. Otherwise:
|
---|
105 |
|
---|
106 | // 7. Assert: input is a Request object.
|
---|
107 | assert(input instanceof Request)
|
---|
108 |
|
---|
109 | // 8. Set request to input’s request.
|
---|
110 | request = input[kState]
|
---|
111 |
|
---|
112 | // 9. Set signal to input’s signal.
|
---|
113 | signal = input[kSignal]
|
---|
114 | }
|
---|
115 |
|
---|
116 | // 7. Let origin be this’s relevant settings object’s origin.
|
---|
117 | const origin = this[kRealm].settingsObject.origin
|
---|
118 |
|
---|
119 | // 8. Let window be "client".
|
---|
120 | let window = 'client'
|
---|
121 |
|
---|
122 | // 9. If request’s window is an environment settings object and its origin
|
---|
123 | // is same origin with origin, then set window to request’s window.
|
---|
124 | if (
|
---|
125 | request.window?.constructor?.name === 'EnvironmentSettingsObject' &&
|
---|
126 | sameOrigin(request.window, origin)
|
---|
127 | ) {
|
---|
128 | window = request.window
|
---|
129 | }
|
---|
130 |
|
---|
131 | // 10. If init["window"] exists and is non-null, then throw a TypeError.
|
---|
132 | if (init.window != null) {
|
---|
133 | throw new TypeError(`'window' option '${window}' must be null`)
|
---|
134 | }
|
---|
135 |
|
---|
136 | // 11. If init["window"] exists, then set window to "no-window".
|
---|
137 | if ('window' in init) {
|
---|
138 | window = 'no-window'
|
---|
139 | }
|
---|
140 |
|
---|
141 | // 12. Set request to a new request with the following properties:
|
---|
142 | request = makeRequest({
|
---|
143 | // URL request’s URL.
|
---|
144 | // undici implementation note: this is set as the first item in request's urlList in makeRequest
|
---|
145 | // method request’s method.
|
---|
146 | method: request.method,
|
---|
147 | // header list A copy of request’s header list.
|
---|
148 | // undici implementation note: headersList is cloned in makeRequest
|
---|
149 | headersList: request.headersList,
|
---|
150 | // unsafe-request flag Set.
|
---|
151 | unsafeRequest: request.unsafeRequest,
|
---|
152 | // client This’s relevant settings object.
|
---|
153 | client: this[kRealm].settingsObject,
|
---|
154 | // window window.
|
---|
155 | window,
|
---|
156 | // priority request’s priority.
|
---|
157 | priority: request.priority,
|
---|
158 | // origin request’s origin. The propagation of the origin is only significant for navigation requests
|
---|
159 | // being handled by a service worker. In this scenario a request can have an origin that is different
|
---|
160 | // from the current client.
|
---|
161 | origin: request.origin,
|
---|
162 | // referrer request’s referrer.
|
---|
163 | referrer: request.referrer,
|
---|
164 | // referrer policy request’s referrer policy.
|
---|
165 | referrerPolicy: request.referrerPolicy,
|
---|
166 | // mode request’s mode.
|
---|
167 | mode: request.mode,
|
---|
168 | // credentials mode request’s credentials mode.
|
---|
169 | credentials: request.credentials,
|
---|
170 | // cache mode request’s cache mode.
|
---|
171 | cache: request.cache,
|
---|
172 | // redirect mode request’s redirect mode.
|
---|
173 | redirect: request.redirect,
|
---|
174 | // integrity metadata request’s integrity metadata.
|
---|
175 | integrity: request.integrity,
|
---|
176 | // keepalive request’s keepalive.
|
---|
177 | keepalive: request.keepalive,
|
---|
178 | // reload-navigation flag request’s reload-navigation flag.
|
---|
179 | reloadNavigation: request.reloadNavigation,
|
---|
180 | // history-navigation flag request’s history-navigation flag.
|
---|
181 | historyNavigation: request.historyNavigation,
|
---|
182 | // URL list A clone of request’s URL list.
|
---|
183 | urlList: [...request.urlList]
|
---|
184 | })
|
---|
185 |
|
---|
186 | const initHasKey = Object.keys(init).length !== 0
|
---|
187 |
|
---|
188 | // 13. If init is not empty, then:
|
---|
189 | if (initHasKey) {
|
---|
190 | // 1. If request’s mode is "navigate", then set it to "same-origin".
|
---|
191 | if (request.mode === 'navigate') {
|
---|
192 | request.mode = 'same-origin'
|
---|
193 | }
|
---|
194 |
|
---|
195 | // 2. Unset request’s reload-navigation flag.
|
---|
196 | request.reloadNavigation = false
|
---|
197 |
|
---|
198 | // 3. Unset request’s history-navigation flag.
|
---|
199 | request.historyNavigation = false
|
---|
200 |
|
---|
201 | // 4. Set request’s origin to "client".
|
---|
202 | request.origin = 'client'
|
---|
203 |
|
---|
204 | // 5. Set request’s referrer to "client"
|
---|
205 | request.referrer = 'client'
|
---|
206 |
|
---|
207 | // 6. Set request’s referrer policy to the empty string.
|
---|
208 | request.referrerPolicy = ''
|
---|
209 |
|
---|
210 | // 7. Set request’s URL to request’s current URL.
|
---|
211 | request.url = request.urlList[request.urlList.length - 1]
|
---|
212 |
|
---|
213 | // 8. Set request’s URL list to « request’s URL ».
|
---|
214 | request.urlList = [request.url]
|
---|
215 | }
|
---|
216 |
|
---|
217 | // 14. If init["referrer"] exists, then:
|
---|
218 | if (init.referrer !== undefined) {
|
---|
219 | // 1. Let referrer be init["referrer"].
|
---|
220 | const referrer = init.referrer
|
---|
221 |
|
---|
222 | // 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
|
---|
223 | if (referrer === '') {
|
---|
224 | request.referrer = 'no-referrer'
|
---|
225 | } else {
|
---|
226 | // 1. Let parsedReferrer be the result of parsing referrer with
|
---|
227 | // baseURL.
|
---|
228 | // 2. If parsedReferrer is failure, then throw a TypeError.
|
---|
229 | let parsedReferrer
|
---|
230 | try {
|
---|
231 | parsedReferrer = new URL(referrer, baseUrl)
|
---|
232 | } catch (err) {
|
---|
233 | throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err })
|
---|
234 | }
|
---|
235 |
|
---|
236 | // 3. If one of the following is true
|
---|
237 | // - parsedReferrer’s scheme is "about" and path is the string "client"
|
---|
238 | // - parsedReferrer’s origin is not same origin with origin
|
---|
239 | // then set request’s referrer to "client".
|
---|
240 | if (
|
---|
241 | (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') ||
|
---|
242 | (origin && !sameOrigin(parsedReferrer, this[kRealm].settingsObject.baseUrl))
|
---|
243 | ) {
|
---|
244 | request.referrer = 'client'
|
---|
245 | } else {
|
---|
246 | // 4. Otherwise, set request’s referrer to parsedReferrer.
|
---|
247 | request.referrer = parsedReferrer
|
---|
248 | }
|
---|
249 | }
|
---|
250 | }
|
---|
251 |
|
---|
252 | // 15. If init["referrerPolicy"] exists, then set request’s referrer policy
|
---|
253 | // to it.
|
---|
254 | if (init.referrerPolicy !== undefined) {
|
---|
255 | request.referrerPolicy = init.referrerPolicy
|
---|
256 | }
|
---|
257 |
|
---|
258 | // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
|
---|
259 | let mode
|
---|
260 | if (init.mode !== undefined) {
|
---|
261 | mode = init.mode
|
---|
262 | } else {
|
---|
263 | mode = fallbackMode
|
---|
264 | }
|
---|
265 |
|
---|
266 | // 17. If mode is "navigate", then throw a TypeError.
|
---|
267 | if (mode === 'navigate') {
|
---|
268 | throw webidl.errors.exception({
|
---|
269 | header: 'Request constructor',
|
---|
270 | message: 'invalid request mode navigate.'
|
---|
271 | })
|
---|
272 | }
|
---|
273 |
|
---|
274 | // 18. If mode is non-null, set request’s mode to mode.
|
---|
275 | if (mode != null) {
|
---|
276 | request.mode = mode
|
---|
277 | }
|
---|
278 |
|
---|
279 | // 19. If init["credentials"] exists, then set request’s credentials mode
|
---|
280 | // to it.
|
---|
281 | if (init.credentials !== undefined) {
|
---|
282 | request.credentials = init.credentials
|
---|
283 | }
|
---|
284 |
|
---|
285 | // 18. If init["cache"] exists, then set request’s cache mode to it.
|
---|
286 | if (init.cache !== undefined) {
|
---|
287 | request.cache = init.cache
|
---|
288 | }
|
---|
289 |
|
---|
290 | // 21. If request’s cache mode is "only-if-cached" and request’s mode is
|
---|
291 | // not "same-origin", then throw a TypeError.
|
---|
292 | if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
|
---|
293 | throw new TypeError(
|
---|
294 | "'only-if-cached' can be set only with 'same-origin' mode"
|
---|
295 | )
|
---|
296 | }
|
---|
297 |
|
---|
298 | // 22. If init["redirect"] exists, then set request’s redirect mode to it.
|
---|
299 | if (init.redirect !== undefined) {
|
---|
300 | request.redirect = init.redirect
|
---|
301 | }
|
---|
302 |
|
---|
303 | // 23. If init["integrity"] exists, then set request’s integrity metadata to it.
|
---|
304 | if (init.integrity != null) {
|
---|
305 | request.integrity = String(init.integrity)
|
---|
306 | }
|
---|
307 |
|
---|
308 | // 24. If init["keepalive"] exists, then set request’s keepalive to it.
|
---|
309 | if (init.keepalive !== undefined) {
|
---|
310 | request.keepalive = Boolean(init.keepalive)
|
---|
311 | }
|
---|
312 |
|
---|
313 | // 25. If init["method"] exists, then:
|
---|
314 | if (init.method !== undefined) {
|
---|
315 | // 1. Let method be init["method"].
|
---|
316 | let method = init.method
|
---|
317 |
|
---|
318 | // 2. If method is not a method or method is a forbidden method, then
|
---|
319 | // throw a TypeError.
|
---|
320 | if (!isValidHTTPToken(method)) {
|
---|
321 | throw new TypeError(`'${method}' is not a valid HTTP method.`)
|
---|
322 | }
|
---|
323 |
|
---|
324 | if (forbiddenMethodsSet.has(method.toUpperCase())) {
|
---|
325 | throw new TypeError(`'${method}' HTTP method is unsupported.`)
|
---|
326 | }
|
---|
327 |
|
---|
328 | // 3. Normalize method.
|
---|
329 | method = normalizeMethodRecord[method] ?? normalizeMethod(method)
|
---|
330 |
|
---|
331 | // 4. Set request’s method to method.
|
---|
332 | request.method = method
|
---|
333 | }
|
---|
334 |
|
---|
335 | // 26. If init["signal"] exists, then set signal to it.
|
---|
336 | if (init.signal !== undefined) {
|
---|
337 | signal = init.signal
|
---|
338 | }
|
---|
339 |
|
---|
340 | // 27. Set this’s request to request.
|
---|
341 | this[kState] = request
|
---|
342 |
|
---|
343 | // 28. Set this’s signal to a new AbortSignal object with this’s relevant
|
---|
344 | // Realm.
|
---|
345 | // TODO: could this be simplified with AbortSignal.any
|
---|
346 | // (https://dom.spec.whatwg.org/#dom-abortsignal-any)
|
---|
347 | const ac = new AbortController()
|
---|
348 | this[kSignal] = ac.signal
|
---|
349 | this[kSignal][kRealm] = this[kRealm]
|
---|
350 |
|
---|
351 | // 29. If signal is not null, then make this’s signal follow signal.
|
---|
352 | if (signal != null) {
|
---|
353 | if (
|
---|
354 | !signal ||
|
---|
355 | typeof signal.aborted !== 'boolean' ||
|
---|
356 | typeof signal.addEventListener !== 'function'
|
---|
357 | ) {
|
---|
358 | throw new TypeError(
|
---|
359 | "Failed to construct 'Request': member signal is not of type AbortSignal."
|
---|
360 | )
|
---|
361 | }
|
---|
362 |
|
---|
363 | if (signal.aborted) {
|
---|
364 | ac.abort(signal.reason)
|
---|
365 | } else {
|
---|
366 | // Keep a strong ref to ac while request object
|
---|
367 | // is alive. This is needed to prevent AbortController
|
---|
368 | // from being prematurely garbage collected.
|
---|
369 | // See, https://github.com/nodejs/undici/issues/1926.
|
---|
370 | this[kAbortController] = ac
|
---|
371 |
|
---|
372 | const acRef = new WeakRef(ac)
|
---|
373 | const abort = function () {
|
---|
374 | const ac = acRef.deref()
|
---|
375 | if (ac !== undefined) {
|
---|
376 | ac.abort(this.reason)
|
---|
377 | }
|
---|
378 | }
|
---|
379 |
|
---|
380 | // Third-party AbortControllers may not work with these.
|
---|
381 | // See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619.
|
---|
382 | try {
|
---|
383 | // If the max amount of listeners is equal to the default, increase it
|
---|
384 | // This is only available in node >= v19.9.0
|
---|
385 | if (typeof getMaxListeners === 'function' && getMaxListeners(signal) === defaultMaxListeners) {
|
---|
386 | setMaxListeners(100, signal)
|
---|
387 | } else if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
|
---|
388 | setMaxListeners(100, signal)
|
---|
389 | }
|
---|
390 | } catch {}
|
---|
391 |
|
---|
392 | util.addAbortListener(signal, abort)
|
---|
393 | requestFinalizer.register(ac, { signal, abort })
|
---|
394 | }
|
---|
395 | }
|
---|
396 |
|
---|
397 | // 30. Set this’s headers to a new Headers object with this’s relevant
|
---|
398 | // Realm, whose header list is request’s header list and guard is
|
---|
399 | // "request".
|
---|
400 | this[kHeaders] = new Headers(kConstruct)
|
---|
401 | this[kHeaders][kHeadersList] = request.headersList
|
---|
402 | this[kHeaders][kGuard] = 'request'
|
---|
403 | this[kHeaders][kRealm] = this[kRealm]
|
---|
404 |
|
---|
405 | // 31. If this’s request’s mode is "no-cors", then:
|
---|
406 | if (mode === 'no-cors') {
|
---|
407 | // 1. If this’s request’s method is not a CORS-safelisted method,
|
---|
408 | // then throw a TypeError.
|
---|
409 | if (!corsSafeListedMethodsSet.has(request.method)) {
|
---|
410 | throw new TypeError(
|
---|
411 | `'${request.method} is unsupported in no-cors mode.`
|
---|
412 | )
|
---|
413 | }
|
---|
414 |
|
---|
415 | // 2. Set this’s headers’s guard to "request-no-cors".
|
---|
416 | this[kHeaders][kGuard] = 'request-no-cors'
|
---|
417 | }
|
---|
418 |
|
---|
419 | // 32. If init is not empty, then:
|
---|
420 | if (initHasKey) {
|
---|
421 | /** @type {HeadersList} */
|
---|
422 | const headersList = this[kHeaders][kHeadersList]
|
---|
423 | // 1. Let headers be a copy of this’s headers and its associated header
|
---|
424 | // list.
|
---|
425 | // 2. If init["headers"] exists, then set headers to init["headers"].
|
---|
426 | const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList)
|
---|
427 |
|
---|
428 | // 3. Empty this’s headers’s header list.
|
---|
429 | headersList.clear()
|
---|
430 |
|
---|
431 | // 4. If headers is a Headers object, then for each header in its header
|
---|
432 | // list, append header’s name/header’s value to this’s headers.
|
---|
433 | if (headers instanceof HeadersList) {
|
---|
434 | for (const [key, val] of headers) {
|
---|
435 | headersList.append(key, val)
|
---|
436 | }
|
---|
437 | // Note: Copy the `set-cookie` meta-data.
|
---|
438 | headersList.cookies = headers.cookies
|
---|
439 | } else {
|
---|
440 | // 5. Otherwise, fill this’s headers with headers.
|
---|
441 | fillHeaders(this[kHeaders], headers)
|
---|
442 | }
|
---|
443 | }
|
---|
444 |
|
---|
445 | // 33. Let inputBody be input’s request’s body if input is a Request
|
---|
446 | // object; otherwise null.
|
---|
447 | const inputBody = input instanceof Request ? input[kState].body : null
|
---|
448 |
|
---|
449 | // 34. If either init["body"] exists and is non-null or inputBody is
|
---|
450 | // non-null, and request’s method is `GET` or `HEAD`, then throw a
|
---|
451 | // TypeError.
|
---|
452 | if (
|
---|
453 | (init.body != null || inputBody != null) &&
|
---|
454 | (request.method === 'GET' || request.method === 'HEAD')
|
---|
455 | ) {
|
---|
456 | throw new TypeError('Request with GET/HEAD method cannot have body.')
|
---|
457 | }
|
---|
458 |
|
---|
459 | // 35. Let initBody be null.
|
---|
460 | let initBody = null
|
---|
461 |
|
---|
462 | // 36. If init["body"] exists and is non-null, then:
|
---|
463 | if (init.body != null) {
|
---|
464 | // 1. Let Content-Type be null.
|
---|
465 | // 2. Set initBody and Content-Type to the result of extracting
|
---|
466 | // init["body"], with keepalive set to request’s keepalive.
|
---|
467 | const [extractedBody, contentType] = extractBody(
|
---|
468 | init.body,
|
---|
469 | request.keepalive
|
---|
470 | )
|
---|
471 | initBody = extractedBody
|
---|
472 |
|
---|
473 | // 3, If Content-Type is non-null and this’s headers’s header list does
|
---|
474 | // not contain `Content-Type`, then append `Content-Type`/Content-Type to
|
---|
475 | // this’s headers.
|
---|
476 | if (contentType && !this[kHeaders][kHeadersList].contains('content-type')) {
|
---|
477 | this[kHeaders].append('content-type', contentType)
|
---|
478 | }
|
---|
479 | }
|
---|
480 |
|
---|
481 | // 37. Let inputOrInitBody be initBody if it is non-null; otherwise
|
---|
482 | // inputBody.
|
---|
483 | const inputOrInitBody = initBody ?? inputBody
|
---|
484 |
|
---|
485 | // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is
|
---|
486 | // null, then:
|
---|
487 | if (inputOrInitBody != null && inputOrInitBody.source == null) {
|
---|
488 | // 1. If initBody is non-null and init["duplex"] does not exist,
|
---|
489 | // then throw a TypeError.
|
---|
490 | if (initBody != null && init.duplex == null) {
|
---|
491 | throw new TypeError('RequestInit: duplex option is required when sending a body.')
|
---|
492 | }
|
---|
493 |
|
---|
494 | // 2. If this’s request’s mode is neither "same-origin" nor "cors",
|
---|
495 | // then throw a TypeError.
|
---|
496 | if (request.mode !== 'same-origin' && request.mode !== 'cors') {
|
---|
497 | throw new TypeError(
|
---|
498 | 'If request is made from ReadableStream, mode should be "same-origin" or "cors"'
|
---|
499 | )
|
---|
500 | }
|
---|
501 |
|
---|
502 | // 3. Set this’s request’s use-CORS-preflight flag.
|
---|
503 | request.useCORSPreflightFlag = true
|
---|
504 | }
|
---|
505 |
|
---|
506 | // 39. Let finalBody be inputOrInitBody.
|
---|
507 | let finalBody = inputOrInitBody
|
---|
508 |
|
---|
509 | // 40. If initBody is null and inputBody is non-null, then:
|
---|
510 | if (initBody == null && inputBody != null) {
|
---|
511 | // 1. If input is unusable, then throw a TypeError.
|
---|
512 | if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) {
|
---|
513 | throw new TypeError(
|
---|
514 | 'Cannot construct a Request with a Request object that has already been used.'
|
---|
515 | )
|
---|
516 | }
|
---|
517 |
|
---|
518 | // 2. Set finalBody to the result of creating a proxy for inputBody.
|
---|
519 | if (!TransformStream) {
|
---|
520 | TransformStream = require('stream/web').TransformStream
|
---|
521 | }
|
---|
522 |
|
---|
523 | // https://streams.spec.whatwg.org/#readablestream-create-a-proxy
|
---|
524 | const identityTransform = new TransformStream()
|
---|
525 | inputBody.stream.pipeThrough(identityTransform)
|
---|
526 | finalBody = {
|
---|
527 | source: inputBody.source,
|
---|
528 | length: inputBody.length,
|
---|
529 | stream: identityTransform.readable
|
---|
530 | }
|
---|
531 | }
|
---|
532 |
|
---|
533 | // 41. Set this’s request’s body to finalBody.
|
---|
534 | this[kState].body = finalBody
|
---|
535 | }
|
---|
536 |
|
---|
537 | // Returns request’s HTTP method, which is "GET" by default.
|
---|
538 | get method () {
|
---|
539 | webidl.brandCheck(this, Request)
|
---|
540 |
|
---|
541 | // The method getter steps are to return this’s request’s method.
|
---|
542 | return this[kState].method
|
---|
543 | }
|
---|
544 |
|
---|
545 | // Returns the URL of request as a string.
|
---|
546 | get url () {
|
---|
547 | webidl.brandCheck(this, Request)
|
---|
548 |
|
---|
549 | // The url getter steps are to return this’s request’s URL, serialized.
|
---|
550 | return URLSerializer(this[kState].url)
|
---|
551 | }
|
---|
552 |
|
---|
553 | // Returns a Headers object consisting of the headers associated with request.
|
---|
554 | // Note that headers added in the network layer by the user agent will not
|
---|
555 | // be accounted for in this object, e.g., the "Host" header.
|
---|
556 | get headers () {
|
---|
557 | webidl.brandCheck(this, Request)
|
---|
558 |
|
---|
559 | // The headers getter steps are to return this’s headers.
|
---|
560 | return this[kHeaders]
|
---|
561 | }
|
---|
562 |
|
---|
563 | // Returns the kind of resource requested by request, e.g., "document"
|
---|
564 | // or "script".
|
---|
565 | get destination () {
|
---|
566 | webidl.brandCheck(this, Request)
|
---|
567 |
|
---|
568 | // The destination getter are to return this’s request’s destination.
|
---|
569 | return this[kState].destination
|
---|
570 | }
|
---|
571 |
|
---|
572 | // Returns the referrer of request. Its value can be a same-origin URL if
|
---|
573 | // explicitly set in init, the empty string to indicate no referrer, and
|
---|
574 | // "about:client" when defaulting to the global’s default. This is used
|
---|
575 | // during fetching to determine the value of the `Referer` header of the
|
---|
576 | // request being made.
|
---|
577 | get referrer () {
|
---|
578 | webidl.brandCheck(this, Request)
|
---|
579 |
|
---|
580 | // 1. If this’s request’s referrer is "no-referrer", then return the
|
---|
581 | // empty string.
|
---|
582 | if (this[kState].referrer === 'no-referrer') {
|
---|
583 | return ''
|
---|
584 | }
|
---|
585 |
|
---|
586 | // 2. If this’s request’s referrer is "client", then return
|
---|
587 | // "about:client".
|
---|
588 | if (this[kState].referrer === 'client') {
|
---|
589 | return 'about:client'
|
---|
590 | }
|
---|
591 |
|
---|
592 | // Return this’s request’s referrer, serialized.
|
---|
593 | return this[kState].referrer.toString()
|
---|
594 | }
|
---|
595 |
|
---|
596 | // Returns the referrer policy associated with request.
|
---|
597 | // This is used during fetching to compute the value of the request’s
|
---|
598 | // referrer.
|
---|
599 | get referrerPolicy () {
|
---|
600 | webidl.brandCheck(this, Request)
|
---|
601 |
|
---|
602 | // The referrerPolicy getter steps are to return this’s request’s referrer policy.
|
---|
603 | return this[kState].referrerPolicy
|
---|
604 | }
|
---|
605 |
|
---|
606 | // Returns the mode associated with request, which is a string indicating
|
---|
607 | // whether the request will use CORS, or will be restricted to same-origin
|
---|
608 | // URLs.
|
---|
609 | get mode () {
|
---|
610 | webidl.brandCheck(this, Request)
|
---|
611 |
|
---|
612 | // The mode getter steps are to return this’s request’s mode.
|
---|
613 | return this[kState].mode
|
---|
614 | }
|
---|
615 |
|
---|
616 | // Returns the credentials mode associated with request,
|
---|
617 | // which is a string indicating whether credentials will be sent with the
|
---|
618 | // request always, never, or only when sent to a same-origin URL.
|
---|
619 | get credentials () {
|
---|
620 | // The credentials getter steps are to return this’s request’s credentials mode.
|
---|
621 | return this[kState].credentials
|
---|
622 | }
|
---|
623 |
|
---|
624 | // Returns the cache mode associated with request,
|
---|
625 | // which is a string indicating how the request will
|
---|
626 | // interact with the browser’s cache when fetching.
|
---|
627 | get cache () {
|
---|
628 | webidl.brandCheck(this, Request)
|
---|
629 |
|
---|
630 | // The cache getter steps are to return this’s request’s cache mode.
|
---|
631 | return this[kState].cache
|
---|
632 | }
|
---|
633 |
|
---|
634 | // Returns the redirect mode associated with request,
|
---|
635 | // which is a string indicating how redirects for the
|
---|
636 | // request will be handled during fetching. A request
|
---|
637 | // will follow redirects by default.
|
---|
638 | get redirect () {
|
---|
639 | webidl.brandCheck(this, Request)
|
---|
640 |
|
---|
641 | // The redirect getter steps are to return this’s request’s redirect mode.
|
---|
642 | return this[kState].redirect
|
---|
643 | }
|
---|
644 |
|
---|
645 | // Returns request’s subresource integrity metadata, which is a
|
---|
646 | // cryptographic hash of the resource being fetched. Its value
|
---|
647 | // consists of multiple hashes separated by whitespace. [SRI]
|
---|
648 | get integrity () {
|
---|
649 | webidl.brandCheck(this, Request)
|
---|
650 |
|
---|
651 | // The integrity getter steps are to return this’s request’s integrity
|
---|
652 | // metadata.
|
---|
653 | return this[kState].integrity
|
---|
654 | }
|
---|
655 |
|
---|
656 | // Returns a boolean indicating whether or not request can outlive the
|
---|
657 | // global in which it was created.
|
---|
658 | get keepalive () {
|
---|
659 | webidl.brandCheck(this, Request)
|
---|
660 |
|
---|
661 | // The keepalive getter steps are to return this’s request’s keepalive.
|
---|
662 | return this[kState].keepalive
|
---|
663 | }
|
---|
664 |
|
---|
665 | // Returns a boolean indicating whether or not request is for a reload
|
---|
666 | // navigation.
|
---|
667 | get isReloadNavigation () {
|
---|
668 | webidl.brandCheck(this, Request)
|
---|
669 |
|
---|
670 | // The isReloadNavigation getter steps are to return true if this’s
|
---|
671 | // request’s reload-navigation flag is set; otherwise false.
|
---|
672 | return this[kState].reloadNavigation
|
---|
673 | }
|
---|
674 |
|
---|
675 | // Returns a boolean indicating whether or not request is for a history
|
---|
676 | // navigation (a.k.a. back-foward navigation).
|
---|
677 | get isHistoryNavigation () {
|
---|
678 | webidl.brandCheck(this, Request)
|
---|
679 |
|
---|
680 | // The isHistoryNavigation getter steps are to return true if this’s request’s
|
---|
681 | // history-navigation flag is set; otherwise false.
|
---|
682 | return this[kState].historyNavigation
|
---|
683 | }
|
---|
684 |
|
---|
685 | // Returns the signal associated with request, which is an AbortSignal
|
---|
686 | // object indicating whether or not request has been aborted, and its
|
---|
687 | // abort event handler.
|
---|
688 | get signal () {
|
---|
689 | webidl.brandCheck(this, Request)
|
---|
690 |
|
---|
691 | // The signal getter steps are to return this’s signal.
|
---|
692 | return this[kSignal]
|
---|
693 | }
|
---|
694 |
|
---|
695 | get body () {
|
---|
696 | webidl.brandCheck(this, Request)
|
---|
697 |
|
---|
698 | return this[kState].body ? this[kState].body.stream : null
|
---|
699 | }
|
---|
700 |
|
---|
701 | get bodyUsed () {
|
---|
702 | webidl.brandCheck(this, Request)
|
---|
703 |
|
---|
704 | return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
|
---|
705 | }
|
---|
706 |
|
---|
707 | get duplex () {
|
---|
708 | webidl.brandCheck(this, Request)
|
---|
709 |
|
---|
710 | return 'half'
|
---|
711 | }
|
---|
712 |
|
---|
713 | // Returns a clone of request.
|
---|
714 | clone () {
|
---|
715 | webidl.brandCheck(this, Request)
|
---|
716 |
|
---|
717 | // 1. If this is unusable, then throw a TypeError.
|
---|
718 | if (this.bodyUsed || this.body?.locked) {
|
---|
719 | throw new TypeError('unusable')
|
---|
720 | }
|
---|
721 |
|
---|
722 | // 2. Let clonedRequest be the result of cloning this’s request.
|
---|
723 | const clonedRequest = cloneRequest(this[kState])
|
---|
724 |
|
---|
725 | // 3. Let clonedRequestObject be the result of creating a Request object,
|
---|
726 | // given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
|
---|
727 | const clonedRequestObject = new Request(kConstruct)
|
---|
728 | clonedRequestObject[kState] = clonedRequest
|
---|
729 | clonedRequestObject[kRealm] = this[kRealm]
|
---|
730 | clonedRequestObject[kHeaders] = new Headers(kConstruct)
|
---|
731 | clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList
|
---|
732 | clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard]
|
---|
733 | clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm]
|
---|
734 |
|
---|
735 | // 4. Make clonedRequestObject’s signal follow this’s signal.
|
---|
736 | const ac = new AbortController()
|
---|
737 | if (this.signal.aborted) {
|
---|
738 | ac.abort(this.signal.reason)
|
---|
739 | } else {
|
---|
740 | util.addAbortListener(
|
---|
741 | this.signal,
|
---|
742 | () => {
|
---|
743 | ac.abort(this.signal.reason)
|
---|
744 | }
|
---|
745 | )
|
---|
746 | }
|
---|
747 | clonedRequestObject[kSignal] = ac.signal
|
---|
748 |
|
---|
749 | // 4. Return clonedRequestObject.
|
---|
750 | return clonedRequestObject
|
---|
751 | }
|
---|
752 | }
|
---|
753 |
|
---|
754 | mixinBody(Request)
|
---|
755 |
|
---|
756 | function makeRequest (init) {
|
---|
757 | // https://fetch.spec.whatwg.org/#requests
|
---|
758 | const request = {
|
---|
759 | method: 'GET',
|
---|
760 | localURLsOnly: false,
|
---|
761 | unsafeRequest: false,
|
---|
762 | body: null,
|
---|
763 | client: null,
|
---|
764 | reservedClient: null,
|
---|
765 | replacesClientId: '',
|
---|
766 | window: 'client',
|
---|
767 | keepalive: false,
|
---|
768 | serviceWorkers: 'all',
|
---|
769 | initiator: '',
|
---|
770 | destination: '',
|
---|
771 | priority: null,
|
---|
772 | origin: 'client',
|
---|
773 | policyContainer: 'client',
|
---|
774 | referrer: 'client',
|
---|
775 | referrerPolicy: '',
|
---|
776 | mode: 'no-cors',
|
---|
777 | useCORSPreflightFlag: false,
|
---|
778 | credentials: 'same-origin',
|
---|
779 | useCredentials: false,
|
---|
780 | cache: 'default',
|
---|
781 | redirect: 'follow',
|
---|
782 | integrity: '',
|
---|
783 | cryptoGraphicsNonceMetadata: '',
|
---|
784 | parserMetadata: '',
|
---|
785 | reloadNavigation: false,
|
---|
786 | historyNavigation: false,
|
---|
787 | userActivation: false,
|
---|
788 | taintedOrigin: false,
|
---|
789 | redirectCount: 0,
|
---|
790 | responseTainting: 'basic',
|
---|
791 | preventNoCacheCacheControlHeaderModification: false,
|
---|
792 | done: false,
|
---|
793 | timingAllowFailed: false,
|
---|
794 | ...init,
|
---|
795 | headersList: init.headersList
|
---|
796 | ? new HeadersList(init.headersList)
|
---|
797 | : new HeadersList()
|
---|
798 | }
|
---|
799 | request.url = request.urlList[0]
|
---|
800 | return request
|
---|
801 | }
|
---|
802 |
|
---|
803 | // https://fetch.spec.whatwg.org/#concept-request-clone
|
---|
804 | function cloneRequest (request) {
|
---|
805 | // To clone a request request, run these steps:
|
---|
806 |
|
---|
807 | // 1. Let newRequest be a copy of request, except for its body.
|
---|
808 | const newRequest = makeRequest({ ...request, body: null })
|
---|
809 |
|
---|
810 | // 2. If request’s body is non-null, set newRequest’s body to the
|
---|
811 | // result of cloning request’s body.
|
---|
812 | if (request.body != null) {
|
---|
813 | newRequest.body = cloneBody(request.body)
|
---|
814 | }
|
---|
815 |
|
---|
816 | // 3. Return newRequest.
|
---|
817 | return newRequest
|
---|
818 | }
|
---|
819 |
|
---|
820 | Object.defineProperties(Request.prototype, {
|
---|
821 | method: kEnumerableProperty,
|
---|
822 | url: kEnumerableProperty,
|
---|
823 | headers: kEnumerableProperty,
|
---|
824 | redirect: kEnumerableProperty,
|
---|
825 | clone: kEnumerableProperty,
|
---|
826 | signal: kEnumerableProperty,
|
---|
827 | duplex: kEnumerableProperty,
|
---|
828 | destination: kEnumerableProperty,
|
---|
829 | body: kEnumerableProperty,
|
---|
830 | bodyUsed: kEnumerableProperty,
|
---|
831 | isHistoryNavigation: kEnumerableProperty,
|
---|
832 | isReloadNavigation: kEnumerableProperty,
|
---|
833 | keepalive: kEnumerableProperty,
|
---|
834 | integrity: kEnumerableProperty,
|
---|
835 | cache: kEnumerableProperty,
|
---|
836 | credentials: kEnumerableProperty,
|
---|
837 | attribute: kEnumerableProperty,
|
---|
838 | referrerPolicy: kEnumerableProperty,
|
---|
839 | referrer: kEnumerableProperty,
|
---|
840 | mode: kEnumerableProperty,
|
---|
841 | [Symbol.toStringTag]: {
|
---|
842 | value: 'Request',
|
---|
843 | configurable: true
|
---|
844 | }
|
---|
845 | })
|
---|
846 |
|
---|
847 | webidl.converters.Request = webidl.interfaceConverter(
|
---|
848 | Request
|
---|
849 | )
|
---|
850 |
|
---|
851 | // https://fetch.spec.whatwg.org/#requestinfo
|
---|
852 | webidl.converters.RequestInfo = function (V) {
|
---|
853 | if (typeof V === 'string') {
|
---|
854 | return webidl.converters.USVString(V)
|
---|
855 | }
|
---|
856 |
|
---|
857 | if (V instanceof Request) {
|
---|
858 | return webidl.converters.Request(V)
|
---|
859 | }
|
---|
860 |
|
---|
861 | return webidl.converters.USVString(V)
|
---|
862 | }
|
---|
863 |
|
---|
864 | webidl.converters.AbortSignal = webidl.interfaceConverter(
|
---|
865 | AbortSignal
|
---|
866 | )
|
---|
867 |
|
---|
868 | // https://fetch.spec.whatwg.org/#requestinit
|
---|
869 | webidl.converters.RequestInit = webidl.dictionaryConverter([
|
---|
870 | {
|
---|
871 | key: 'method',
|
---|
872 | converter: webidl.converters.ByteString
|
---|
873 | },
|
---|
874 | {
|
---|
875 | key: 'headers',
|
---|
876 | converter: webidl.converters.HeadersInit
|
---|
877 | },
|
---|
878 | {
|
---|
879 | key: 'body',
|
---|
880 | converter: webidl.nullableConverter(
|
---|
881 | webidl.converters.BodyInit
|
---|
882 | )
|
---|
883 | },
|
---|
884 | {
|
---|
885 | key: 'referrer',
|
---|
886 | converter: webidl.converters.USVString
|
---|
887 | },
|
---|
888 | {
|
---|
889 | key: 'referrerPolicy',
|
---|
890 | converter: webidl.converters.DOMString,
|
---|
891 | // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
|
---|
892 | allowedValues: referrerPolicy
|
---|
893 | },
|
---|
894 | {
|
---|
895 | key: 'mode',
|
---|
896 | converter: webidl.converters.DOMString,
|
---|
897 | // https://fetch.spec.whatwg.org/#concept-request-mode
|
---|
898 | allowedValues: requestMode
|
---|
899 | },
|
---|
900 | {
|
---|
901 | key: 'credentials',
|
---|
902 | converter: webidl.converters.DOMString,
|
---|
903 | // https://fetch.spec.whatwg.org/#requestcredentials
|
---|
904 | allowedValues: requestCredentials
|
---|
905 | },
|
---|
906 | {
|
---|
907 | key: 'cache',
|
---|
908 | converter: webidl.converters.DOMString,
|
---|
909 | // https://fetch.spec.whatwg.org/#requestcache
|
---|
910 | allowedValues: requestCache
|
---|
911 | },
|
---|
912 | {
|
---|
913 | key: 'redirect',
|
---|
914 | converter: webidl.converters.DOMString,
|
---|
915 | // https://fetch.spec.whatwg.org/#requestredirect
|
---|
916 | allowedValues: requestRedirect
|
---|
917 | },
|
---|
918 | {
|
---|
919 | key: 'integrity',
|
---|
920 | converter: webidl.converters.DOMString
|
---|
921 | },
|
---|
922 | {
|
---|
923 | key: 'keepalive',
|
---|
924 | converter: webidl.converters.boolean
|
---|
925 | },
|
---|
926 | {
|
---|
927 | key: 'signal',
|
---|
928 | converter: webidl.nullableConverter(
|
---|
929 | (signal) => webidl.converters.AbortSignal(
|
---|
930 | signal,
|
---|
931 | { strict: false }
|
---|
932 | )
|
---|
933 | )
|
---|
934 | },
|
---|
935 | {
|
---|
936 | key: 'window',
|
---|
937 | converter: webidl.converters.any
|
---|
938 | },
|
---|
939 | {
|
---|
940 | key: 'duplex',
|
---|
941 | converter: webidl.converters.DOMString,
|
---|
942 | allowedValues: requestDuplex
|
---|
943 | }
|
---|
944 | ])
|
---|
945 |
|
---|
946 | module.exports = { Request, makeRequest }
|
---|