Comparing version 4.4.2 to 4.4.3
@@ -137,3 +137,3 @@ 'use strict' | ||
const { maxRedirections = this[kMaxRedirections] } = opts | ||
if (maxRedirections != null) { | ||
if (maxRedirections != null && maxRedirections !== 0) { | ||
opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting. | ||
@@ -140,0 +140,0 @@ handler = new RedirectHandler(this, maxRedirections, opts, handler) |
@@ -1528,2 +1528,6 @@ 'use strict' | ||
if (body.resume) { | ||
body.resume() | ||
} | ||
socket | ||
@@ -1530,0 +1534,0 @@ .on('drain', onDrain) |
@@ -197,11 +197,2 @@ 'use strict' | ||
class InvalidThisError extends TypeError { | ||
constructor (type) { | ||
super(`Value of "this" must be of type ${type}`) | ||
Error.captureStackTrace(this, InvalidThisError) | ||
this.name = 'InvalidThis' | ||
this.code = 'INVALID_THIS' | ||
} | ||
} | ||
module.exports = { | ||
@@ -227,4 +218,3 @@ AbortError, | ||
InvalidHTTPTokenError, | ||
HTTPInvalidHeaderValueError, | ||
InvalidThisError | ||
HTTPInvalidHeaderValueError | ||
} |
@@ -9,3 +9,2 @@ 'use strict' | ||
const { NotSupportedError } = require('../core/errors') | ||
const { ReadableStream } = require('stream/web') | ||
const { kBodyUsed } = require('../core/symbols') | ||
@@ -15,4 +14,10 @@ const assert = require('assert') | ||
let ReadableStream | ||
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract | ||
function extractBody (object, keepalive = false) { | ||
if (!ReadableStream) { | ||
ReadableStream = require('stream/web').ReadableStream | ||
} | ||
// 1. Let stream be object if object is a ReadableStream object. | ||
@@ -90,3 +95,4 @@ // Otherwise, let stream be a new ReadableStream, and set up stream. | ||
// If object’s type attribute is not the empty byte sequence, set Content-Type to its value. | ||
// If object’s type attribute is not the empty byte sequence, set | ||
// Content-Type to its value. | ||
if (object.type) { | ||
@@ -113,10 +119,6 @@ contentType = object.type | ||
} | ||
} else if (typeof object === 'string') { | ||
// scalar value string | ||
// TODO: How to check for "scalar value string"? | ||
source = object | ||
contentType = 'text/plain;charset=UTF-8' | ||
} else { | ||
// TODO: byte sequence? | ||
// TODO: FormData? | ||
// TODO: scalar value string? | ||
// TODO: else? | ||
@@ -152,3 +154,6 @@ source = String(object) | ||
// When running action is done, close stream. | ||
controller.close() | ||
queueMicrotask(() => { | ||
// See https://github.com/nodejs/node/issues/39758 | ||
controller.close() | ||
}) | ||
} | ||
@@ -159,3 +164,6 @@ ) | ||
controller.enqueue(source) | ||
controller.close() | ||
queueMicrotask(() => { | ||
// See https://github.com/nodejs/node/issues/39758 | ||
controller.close() | ||
}) | ||
} | ||
@@ -173,2 +181,6 @@ | ||
function safelyExtractBody (object, keepalive = false) { | ||
if (!ReadableStream) { | ||
ReadableStream = require('stream/web').ReadableStream | ||
} | ||
// To safely extract a body and a `Content-Type` value from | ||
@@ -268,2 +280,15 @@ // a byte sequence or BodyInit object object, run these steps: | ||
function cancelBody (body, reason) { | ||
try { | ||
if (body.stream) { | ||
body.stream.cancel(reason) | ||
} | ||
} catch (err) { | ||
// Will throw TypeError if body is not readable. | ||
if (err.name !== 'TypeError') { | ||
throw err | ||
} | ||
} | ||
} | ||
function mixinBody (prototype) { | ||
@@ -275,2 +300,3 @@ Object.assign(prototype, methods) | ||
module.exports = { | ||
cancelBody, | ||
extractBody, | ||
@@ -277,0 +303,0 @@ safelyExtractBody, |
@@ -73,3 +73,19 @@ 'use strict' | ||
const subresource = [ | ||
'audio', | ||
'audioworklet', | ||
'font', | ||
'image', | ||
'manifest', | ||
'paintworklet', | ||
'script', | ||
'style', | ||
'track', | ||
'video', | ||
'xslt', | ||
'' | ||
] | ||
module.exports = { | ||
subresource, | ||
forbiddenResponseHeaderNames, | ||
@@ -76,0 +92,0 @@ forbiddenMethods, |
@@ -11,4 +11,3 @@ // https://github.com/Ethan-Arrowood/undici-fetch | ||
InvalidHTTPTokenError, | ||
HTTPInvalidHeaderValueError, | ||
InvalidThisError | ||
HTTPInvalidHeaderValueError | ||
} = require('../core/errors') | ||
@@ -58,6 +57,2 @@ const { | ||
function isHeaders (object) { | ||
return kHeadersList in object | ||
} | ||
function fill (headers, object) { | ||
@@ -105,17 +100,5 @@ // To fill a Headers object headers with a given object object, run these steps: | ||
function validateArgumentLength (found, expected) { | ||
if (found !== expected) { | ||
throw new TypeError( | ||
`${expected} ${ | ||
expected > 1 ? 'arguments' : 'argument' | ||
} required, but only ${found} present` | ||
) | ||
} | ||
} | ||
// TODO: Composition over inheritence? Or helper methods? | ||
class HeadersList extends Array { | ||
append (...args) { | ||
validateArgumentLength(args.length, 2) | ||
const [name, value] = args | ||
append (name, value) { | ||
const normalizedName = normalizeAndValidateHeaderName(name) | ||
@@ -133,7 +116,3 @@ const normalizedValue = normalizeAndValidateHeaderValue(name, value) | ||
delete (...args) { | ||
validateArgumentLength(args.length, 1) | ||
const [name] = args | ||
delete (name) { | ||
const normalizedName = normalizeAndValidateHeaderName(name) | ||
@@ -148,7 +127,3 @@ | ||
get (...args) { | ||
validateArgumentLength(args.length, 1) | ||
const [name] = args | ||
get (name) { | ||
const normalizedName = normalizeAndValidateHeaderName(name) | ||
@@ -165,7 +140,3 @@ | ||
has (...args) { | ||
validateArgumentLength(args.length, 1) | ||
const [name] = args | ||
has (name) { | ||
const normalizedName = normalizeAndValidateHeaderName(name) | ||
@@ -178,7 +149,3 @@ | ||
set (...args) { | ||
validateArgumentLength(args.length, 2) | ||
const [name, value] = args | ||
set (name, value) { | ||
const normalizedName = normalizeAndValidateHeaderName(name) | ||
@@ -197,3 +164,12 @@ const normalizedValue = normalizeAndValidateHeaderValue(name, value) | ||
class Headers { | ||
constructor (init = {}) { | ||
constructor (...args) { | ||
if ( | ||
args[0] !== undefined && | ||
!(typeof args[0] === 'object' && args[0] !== null) && | ||
!Array.isArray(args[0]) | ||
) { | ||
throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(record<ByteString, ByteString> or sequence<sequence<ByteString>>") | ||
} | ||
const init = args.length >= 1 ? (args[0] ?? {}) : {} | ||
this[kHeadersList] = new HeadersList() | ||
@@ -207,6 +183,10 @@ | ||
// 2. If init is given, then fill this with init. | ||
fill(this[kHeadersList], init) | ||
fill(this, init) | ||
} | ||
get [Symbol.toStringTag] () { | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this.constructor.name | ||
@@ -216,2 +196,6 @@ } | ||
toString () { | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return Object.prototype.toString.call(this) | ||
@@ -221,7 +205,10 @@ } | ||
append (...args) { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Header') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
if (args.length < 2) { | ||
throw new TypeError(`Failed to execute 'append' on 'Headers': 2 arguments required, but only ${args.length} present.`) | ||
} | ||
const normalizedName = normalizeAndValidateHeaderName(args[0]) | ||
const normalizedName = normalizeAndValidateHeaderName(String(args[0])) | ||
@@ -244,11 +231,14 @@ if (this[kGuard] === 'immutable') { | ||
return this[kHeadersList].append(...args) | ||
return this[kHeadersList].append(String(args[0]), String(args[1])) | ||
} | ||
delete (...args) { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Header') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
if (args.length < 1) { | ||
throw new TypeError(`Failed to execute 'delete' on 'Headers': 1 argument required, but only ${args.length} present.`) | ||
} | ||
const normalizedName = normalizeAndValidateHeaderName(args[0]) | ||
const normalizedName = normalizeAndValidateHeaderName(String(args[0])) | ||
@@ -271,27 +261,36 @@ if (this[kGuard] === 'immutable') { | ||
return this[kHeadersList].delete(...args) | ||
return this[kHeadersList].delete(String(args[0])) | ||
} | ||
get (...args) { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Header') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
if (args.length < 1) { | ||
throw new TypeError(`Failed to execute 'get' on 'Headers': 1 argument required, but only ${args.length} present.`) | ||
} | ||
return this[kHeadersList].get(...args) | ||
return this[kHeadersList].get(String(args[0])) | ||
} | ||
has (...args) { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Header') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
if (args.length < 1) { | ||
throw new TypeError(`Failed to execute 'has' on 'Headers': 1 argument required, but only ${args.length} present.`) | ||
} | ||
return this[kHeadersList].has(...args) | ||
return this[kHeadersList].has(String(args[0])) | ||
} | ||
set (...args) { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Header') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
if (args.length < 2) { | ||
throw new TypeError(`Failed to execute 'set' on 'Headers': 2 arguments required, but only ${args.length} present.`) | ||
} | ||
const normalizedName = normalizeAndValidateHeaderName(args[0]) | ||
const normalizedName = normalizeAndValidateHeaderName(String(args[0])) | ||
@@ -314,8 +313,8 @@ if (this[kGuard] === 'immutable') { | ||
return this[kHeadersList].set(...args) | ||
return this[kHeadersList].set(String(args[0]), String(args[1])) | ||
} | ||
* keys () { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Headers') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
@@ -329,4 +328,4 @@ | ||
* values () { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Headers') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
@@ -340,4 +339,4 @@ | ||
* entries () { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Headers') | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
@@ -350,6 +349,14 @@ | ||
forEach (callback, thisArg) { | ||
if (!isHeaders(this)) { | ||
throw new InvalidThisError('Headers') | ||
forEach (...args) { | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
if (args.length < 1) { | ||
throw new TypeError(`Failed to execute 'forEach' on 'Headers': 1 argument required, but only ${args.length} present.`) | ||
} | ||
if (typeof args[0] !== 'function') { | ||
throw new TypeError('Failed to execute \'forEach\' on \'Headers\': parameter 1 is not of type \'Function\'.') | ||
} | ||
const callback = args[0] | ||
const thisArg = args[1] | ||
@@ -367,2 +374,6 @@ for (let index = 0; index < this[kHeadersList].length; index += 2) { | ||
[Symbol.for('nodejs.util.inspect.custom')] () { | ||
if (!(this instanceof Headers)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this[kHeadersList] | ||
@@ -369,0 +380,0 @@ } |
@@ -15,12 +15,25 @@ // https://github.com/Ethan-Arrowood/undici-fetch | ||
const { | ||
ServiceWorkerGlobalScope, | ||
Window, | ||
matchRequestIntegrity, | ||
makePolicyContainer, | ||
clonePolicyContainer, | ||
requestBadPort, | ||
TAOCheck, | ||
appendRequestOriginHeader, | ||
responseLocationURL, | ||
requestCurrentURL, | ||
setRequestReferrerPolicyOnRedirect, | ||
makeTimingInfo | ||
tryUpgradeRequestToAPotentiallyTrustworthyURL, | ||
makeTimingInfo, | ||
appendFetchMetadata, | ||
corsCheck, | ||
crossOriginResourcePolicyCheck, | ||
determineRequestsReferrer, | ||
coarsenedSharedCurrentTime | ||
} = require('./util') | ||
const { kState, kHeaders, kGuard } = require('./symbols') | ||
const { kState, kHeaders, kGuard, kRealm } = require('./symbols') | ||
const { AbortError } = require('../core/errors') | ||
const assert = require('assert') | ||
const { safelyExtractBody } = require('./body') | ||
const { safelyExtractBody, cancelBody } = require('./body') | ||
const { | ||
@@ -30,10 +43,11 @@ redirectStatus, | ||
safeMethods, | ||
requestBodyHeader | ||
requestBodyHeader, | ||
subresource | ||
} = require('./constants') | ||
const { kHeadersList } = require('../core/symbols') | ||
const { ReadableStream } = require('stream/web') | ||
const { performance } = require('perf_hooks') | ||
const EE = require('events') | ||
const { PassThrough, pipeline } = require('stream') | ||
let ReadableStream | ||
// https://fetch.spec.whatwg.org/#garbage-collection | ||
@@ -45,3 +59,13 @@ const registry = new FinalizationRegistry((abort) => { | ||
// https://fetch.spec.whatwg.org/#fetch-method | ||
async function fetch (resource, init) { | ||
async function fetch (...args) { | ||
if (args.length < 1) { | ||
throw new TypeError(`Failed to execute 'fetch' on 'Window': 1 argument required, but only ${args.length} present.`) | ||
} | ||
if (args.length >= 1 && typeof args[1] !== 'object' && args[1] !== undefined) { | ||
throw new TypeError("Failed to execute 'fetch' on 'Window': cannot convert to dictionary.") | ||
} | ||
const resource = args[0] | ||
const init = args.length >= 1 ? (args[1] ?? {}) : {} | ||
const context = Object.assign(new EE(), { | ||
@@ -89,7 +113,10 @@ dispatcher: this, | ||
// 5. Let globalObject be request’s client’s global object. | ||
// TODO | ||
// TODO: What if request.client is null? | ||
const globalObject = request.client?.globalObject | ||
// 6. If globalObject is a ServiceWorkerGlobalScope object, then set | ||
// request’s service-workers mode to "none". | ||
// TODO | ||
if (globalObject instanceof ServiceWorkerGlobalScope) { | ||
request.serviceWorkers = 'none' | ||
} | ||
@@ -100,3 +127,3 @@ // 7. Let responseObject be null. | ||
// 8. Let relevantRealm be this’s relevant Realm. | ||
// TODO | ||
const relevantRealm = null | ||
@@ -145,5 +172,5 @@ // 9. Let locallyAborted be false. | ||
if (response.status === 0) { | ||
const error = new TypeError('fetch failed') | ||
error.cause = response.error | ||
p.reject(error) | ||
p.reject( | ||
Object.assign(new TypeError('fetch failed'), { cause: response.error }) | ||
) | ||
return | ||
@@ -154,7 +181,8 @@ } | ||
// given response, "immutable", and relevantRealm. | ||
// TODO: relevantRealm | ||
responseObject = new Response() | ||
responseObject[kState] = response | ||
responseObject[kRealm] = relevantRealm | ||
responseObject[kHeaders][kHeadersList] = response.headersList | ||
responseObject[kHeaders][kGuard] = 'immutable' | ||
responseObject[kHeaders][kRealm] = relevantRealm | ||
@@ -165,7 +193,11 @@ // 5. Resolve p with responseObject. | ||
fetching.call(context, { | ||
request, | ||
processResponseDone: handleFetchDone, | ||
processResponse | ||
}) | ||
fetching | ||
.call(context, { | ||
request, | ||
processResponseDone: handleFetchDone, | ||
processResponse | ||
}) | ||
.catch((err) => { | ||
p.reject(err) | ||
}) | ||
@@ -178,3 +210,3 @@ // 13. Return p. | ||
// 1. If response’s URL list is null or empty, then return. | ||
if (response.urlList.length === 0) { | ||
if (!response.urlList?.length) { | ||
return | ||
@@ -214,4 +246,4 @@ } | ||
// TODO: given global’s relevant settings object’s cross-origin isolated | ||
// capability | ||
response.timingInfo.endTime = performance.now() | ||
// capability? | ||
response.timingInfo.endTime = coarsenedSharedCurrentTime() | ||
@@ -250,3 +282,3 @@ // 8. Set response’s timing info to timingInfo. | ||
if (request.body !== null) { | ||
cancelIfReadable(request.body.stream, error) | ||
cancelBody(request.body) | ||
} | ||
@@ -272,10 +304,18 @@ | ||
// 1. Let taskDestination be null. | ||
// TODO | ||
let taskDestination = null | ||
// 2. Let crossOriginIsolatedCapability be false. | ||
// TODO | ||
let crossOriginIsolatedCapability = false | ||
// 3. If request’s client is non-null, then: | ||
// TODO | ||
if (request.client !== null) { | ||
// 1. Set taskDestination to request’s client’s global object. | ||
taskDestination = request.client.globalObject | ||
// 2. Set crossOriginIsolatedCapability to request’s client’s cross-origin | ||
// isolated capability. | ||
crossOriginIsolatedCapability = | ||
request.client.crossOriginIsolatedCapability | ||
} | ||
// 4. If useParallelQueue is true, then set taskDestination to the result of | ||
@@ -288,4 +328,3 @@ // starting a new parallel queue. | ||
// crossOriginIsolatedCapability. | ||
// TODO: Coarsened shared current time given crossOriginIsolatedCapability? | ||
const currenTime = performance.now() | ||
const currenTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability) | ||
const timingInfo = makeTimingInfo({ | ||
@@ -310,3 +349,5 @@ startTime: currenTime, | ||
processResponseEndOfBody: null, | ||
processResponseDone | ||
processResponseDone, | ||
taskDestination, | ||
crossOriginIsolatedCapability | ||
} | ||
@@ -316,3 +357,5 @@ | ||
// first return value of safely extracting request’s body. | ||
// TODO | ||
// NOTE: Since fetching is only called from fetch, body should already be | ||
// extracted. | ||
assert(!request.body || request.body.stream) | ||
@@ -322,3 +365,9 @@ // 8. If request’s window is "client", then set request’s window to request’s | ||
// "no-window". | ||
// TODO | ||
if (request.window === 'client') { | ||
// TODO: What if request.client is null? | ||
request.window = | ||
request.client?.globalObject instanceof Window | ||
? request.client | ||
: 'no-window' | ||
} | ||
@@ -328,4 +377,4 @@ // 9. If request’s origin is "client", then set request’s origin to request’s | ||
if (request.origin === 'client') { | ||
// TODO: What is correct here? | ||
request.origin = requestCurrentURL(request).origin | ||
// TODO: What if request.client is null? | ||
request.origin = request.client?.origin | ||
} | ||
@@ -335,3 +384,13 @@ | ||
if (request.policyContainer === 'client') { | ||
// TODO | ||
// 1. If request’s client is non-null, then set request’s policy | ||
// container to a clone of request’s client’s policy container. [HTML] | ||
if (request.client !== null) { | ||
request.policyContainer = clonePolicyContainer( | ||
request.client.policyContainer | ||
) | ||
} else { | ||
// 2. Otherwise, set request’s policy container to a new policy | ||
// container. | ||
request.policyContainer = makePolicyContainer() | ||
} | ||
} | ||
@@ -370,9 +429,18 @@ | ||
// user-agent-defined object. | ||
// TODO | ||
if (request.priority === null) { | ||
// TODO | ||
} | ||
// 14. If request is a subresource request, then: | ||
// TODO | ||
if (subresource.includes(request.destination)) { | ||
// 1. Let record be a new fetch record consisting of request and this | ||
// instance of the fetch algorithm. | ||
// TODO | ||
// 2. Append record to request’s client’s fetch group list of fetch | ||
// records. | ||
// TODO | ||
} | ||
// 15. Run main fetch given fetchParams. | ||
mainFetch.call(this, fetchParams) | ||
return mainFetch.call(this, fetchParams) | ||
} | ||
@@ -391,3 +459,3 @@ | ||
// 3. If request’s local-URLs-only flag is set and request’s current URL is | ||
// not local, then set response to a network error. | ||
// not local, then set response to a network error. | ||
if ( | ||
@@ -404,3 +472,3 @@ request.localURLsOnly && | ||
// 5. Upgrade request to a potentially trustworthy URL, if appropriate. | ||
// TODO | ||
tryUpgradeRequestToAPotentiallyTrustworthyURL(request) | ||
@@ -419,3 +487,3 @@ // 6. If should request be blocked due to a bad port, should fetching request | ||
if (request.referrerPolicy === '') { | ||
// TODO | ||
request.referrerPolicy = request.policyContainer.referrerPolicy | ||
} | ||
@@ -426,3 +494,3 @@ | ||
if (request.referrer !== 'no-referrer') { | ||
// TODO | ||
request.referrer = determineRequestsReferrer(request) | ||
} | ||
@@ -445,71 +513,72 @@ | ||
// the steps corresponding to the first matching statement: | ||
// TODO | ||
response = await (async () => { | ||
// - request’s current URL’s origin is same origin with request’s origin, | ||
// and request’s response tainting is "basic" | ||
// - request’s current URL’s scheme is "data" | ||
// - request’s mode is "navigate" or "websocket" | ||
// 1. Set request’s response tainting to "basic". | ||
// 2. Return the result of running scheme fetch given fetchParams. | ||
// TODO | ||
if (response === null) { | ||
response = await (async () => { | ||
// - request’s current URL’s origin is same origin with request’s origin, | ||
// and request’s response tainting is "basic" | ||
// - request’s current URL’s scheme is "data" | ||
// - request’s mode is "navigate" or "websocket" | ||
// 1. Set request’s response tainting to "basic". | ||
// 2. Return the result of running scheme fetch given fetchParams. | ||
// TODO | ||
// request’s mode is "same-origin" | ||
if (request.mode === 'same-origin') { | ||
// 1. Return a network error. | ||
return makeNetworkError('request mode cannot be "same-origin"') | ||
} | ||
// request’s mode is "no-cors" | ||
if (request.mode === 'no-cors') { | ||
// 1. If request’s redirect mode is not "follow", then return a network | ||
// error. | ||
if (request.redirect !== 'follow') { | ||
return makeNetworkError( | ||
'redirect cmode cannot be "follow" for "no-cors" request' | ||
) | ||
// request’s mode is "same-origin" | ||
if (request.mode === 'same-origin') { | ||
// 1. Return a network error. | ||
return makeNetworkError('request mode cannot be "same-origin"') | ||
} | ||
// 2. Set request’s response tainting to "opaque". | ||
request.responseTainting = 'opaque' | ||
// request’s mode is "no-cors" | ||
if (request.mode === 'no-cors') { | ||
// 1. If request’s redirect mode is not "follow", then return a network | ||
// error. | ||
if (request.redirect !== 'follow') { | ||
return makeNetworkError( | ||
'redirect cmode cannot be "follow" for "no-cors" request' | ||
) | ||
} | ||
// 3. Let noCorsResponse be the result of running scheme fetch given | ||
// fetchParams. | ||
// TODO | ||
// 2. Set request’s response tainting to "opaque". | ||
request.responseTainting = 'opaque' | ||
// 4. If noCorsResponse is a filtered response or the CORB check with | ||
// request and noCorsResponse returns allowed, then return noCorsResponse. | ||
// TODO | ||
// 3. Let noCorsResponse be the result of running scheme fetch given | ||
// fetchParams. | ||
// TODO | ||
// 5. Return a new response whose status is noCorsResponse’s status. | ||
// TODO | ||
} | ||
// 4. If noCorsResponse is a filtered response or the CORB check with | ||
// request and noCorsResponse returns allowed, then return noCorsResponse. | ||
// TODO | ||
// request’s current URL’s scheme is not an HTTP(S) scheme | ||
if (!/^https?:/.test(requestCurrentURL(request).protocol)) { | ||
// Return a network error. | ||
return makeNetworkError('URL scheme must be a HTTP(S) scheme') | ||
} | ||
// 5. Return a new response whose status is noCorsResponse’s status. | ||
// TODO | ||
} | ||
// - request’s use-CORS-preflight flag is set | ||
// - request’s unsafe-request flag is set and either request’s method is | ||
// not a CORS-safelisted method or CORS-unsafe request-header names with | ||
// request’s header list is not empty | ||
// 1. Set request’s response tainting to "cors". | ||
// 2. Let corsWithPreflightResponse be the result of running HTTP fetch | ||
// given fetchParams and true. | ||
// 3. If corsWithPreflightResponse is a network error, then clear cache | ||
// entries using request. | ||
// 4. Return corsWithPreflightResponse. | ||
// TODO | ||
// request’s current URL’s scheme is not an HTTP(S) scheme | ||
if (!/^https?:/.test(requestCurrentURL(request).protocol)) { | ||
// Return a network error. | ||
return makeNetworkError('URL scheme must be a HTTP(S) scheme') | ||
} | ||
// Otherwise | ||
// 1. Set request’s response tainting to "cors". | ||
request.responseTainting = 'cors' | ||
// - request’s use-CORS-preflight flag is set | ||
// - request’s unsafe-request flag is set and either request’s method is | ||
// not a CORS-safelisted method or CORS-unsafe request-header names with | ||
// request’s header list is not empty | ||
// 1. Set request’s response tainting to "cors". | ||
// 2. Let corsWithPreflightResponse be the result of running HTTP fetch | ||
// given fetchParams and true. | ||
// 3. If corsWithPreflightResponse is a network error, then clear cache | ||
// entries using request. | ||
// 4. Return corsWithPreflightResponse. | ||
// TODO | ||
// 2. Return the result of running HTTP fetch given fetchParams. | ||
return await httpFetch | ||
.call(this, fetchParams) | ||
.catch((err) => makeNetworkError(err)) | ||
})() | ||
// Otherwise | ||
// 1. Set request’s response tainting to "cors". | ||
request.responseTainting = 'cors' | ||
// 2. Return the result of running HTTP fetch given fetchParams. | ||
return await httpFetch | ||
.call(this, fetchParams) | ||
.catch((err) => makeNetworkError(err)) | ||
})() | ||
} | ||
// 12. If recursive is true, then return response. | ||
@@ -621,3 +690,6 @@ if (recursive) { | ||
// then run processBodyError and abort these steps. [SRI] | ||
// TODO | ||
if (!matchRequestIntegrity(request, bytes)) { | ||
processBodyError('integrity mismatch') | ||
return | ||
} | ||
@@ -707,3 +779,5 @@ // 2. Set response’s body to the first return value of safely | ||
// 5. If request’s service-workers mode is "all", then: | ||
// TODO | ||
if (request.serviceWorkers === 'all') { | ||
// TODO | ||
} | ||
@@ -730,7 +804,14 @@ // 6. If response is null, then: | ||
// for request and response returns failure, then return a network error. | ||
// TODO | ||
if ( | ||
request.responseTainting === 'cors' && | ||
corsCheck(request, response) === 'failure' | ||
) { | ||
return makeNetworkError('cors failure') | ||
} | ||
// 5. If the TAO check for request and response returns failure, then set | ||
// request’s timing allow failed flag. | ||
// TODO | ||
if (TAOCheck(request, response) === 'failure') { | ||
request.timingAllowFailed = true | ||
} | ||
} | ||
@@ -742,3 +823,13 @@ | ||
// and actualResponse returns blocked, then return a network error. | ||
// TODO | ||
if ( | ||
(request.responseTainting === 'opaque' || response.type === 'opaque') && | ||
crossOriginResourcePolicyCheck( | ||
request.origin, | ||
request.client, | ||
request.destination, | ||
actualResponse | ||
) === 'blocked' | ||
) { | ||
return makeNetworkError('blocked') | ||
} | ||
@@ -760,3 +851,3 @@ // 8. If actualResponse’s status is a redirect status, then: | ||
// response is actualResponse. | ||
response = filterResponse(response, 'opaqueredirect') | ||
response = filterResponse(actualResponse, 'opaqueredirect') | ||
} else if (request.redirect === 'follow') { | ||
@@ -810,3 +901,3 @@ // Set response to the result of running HTTP-redirect fetch given | ||
// error. | ||
if (!/^https?:/.test(locationURL)) { | ||
if (!/^https?:/.test(locationURL.protocol)) { | ||
return makeNetworkError('URL scheme must be a HTTP(S) scheme') | ||
@@ -826,3 +917,7 @@ } | ||
// a network error. | ||
if (request.mode === 'cors' && request.origin !== locationURL.origin) { | ||
if ( | ||
request.mode === 'cors' && | ||
(locationURL.username || locationURL.password) && | ||
request.origin !== locationURL.origin | ||
) { | ||
return makeNetworkError('cross origin not allowed for request mode "cors"') | ||
@@ -876,3 +971,3 @@ } | ||
// 2. For each headerName of request-body-header name, delete headerName from | ||
// request’s header list. | ||
// request’s header list. | ||
for (const headerName of requestBodyHeader) { | ||
@@ -896,5 +991,4 @@ request.headersList.delete(headerName) | ||
// capability. | ||
// TODO: given fetchParams’s cross-origin isolated capability? | ||
timingInfo.redirectEndTime = timingInfo.postRedirectStartTime = | ||
performance.now() | ||
coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability) | ||
@@ -939,3 +1033,3 @@ // 17. If timingInfo’s redirect start time is 0, then set timingInfo’s | ||
// 5. Let storedResponse be null. | ||
// TODO | ||
// TODO: cache | ||
@@ -1020,30 +1114,7 @@ // 6. Let httpCache be null. | ||
// 11. Append a request `Origin` header for httpRequest. | ||
// TODO | ||
appendRequestOriginHeader(httpRequest) | ||
// 12. Append the Fetch metadata headers for httpRequest. [FETCH-METADATA] | ||
appendFetchMetadata(httpRequest) | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header | ||
// TODO | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header | ||
{ | ||
// 1. Assert: r’s url is a potentially trustworthy URL. | ||
// TODO | ||
// 2. Let header be a Structured Header whose value is a token. | ||
let header = null | ||
// 3. Set header’s value to r’s mode. | ||
header = request.mode | ||
// 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list. | ||
httpRequest.headersList.append('sec-fetch-mode', header) | ||
} | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header | ||
// TODO | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header | ||
// TODO | ||
// 13. If httpRequest’s header list does not contain `User-Agent`, then | ||
@@ -1107,2 +1178,9 @@ // user agents should append `User-Agent`/default `User-Agent` value to | ||
// TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129 | ||
if (!httpRequest.headersList.has('accept-encoding')) { | ||
if (/^https:/.test(requestCurrentURL(httpRequest).protocol)) { | ||
httpRequest.headersList.append('accept-encoding', 'br, gzip, deflate') | ||
} else { | ||
httpRequest.headersList.append('accept-encoding', 'gzip, deflate') | ||
} | ||
} | ||
@@ -1113,13 +1191,13 @@ // 19. If includeCredentials is true, then: | ||
// (see section 7 of [COOKIES]), then: | ||
// TODO | ||
// TODO: credentials | ||
// 2. If httpRequest’s header list does not contain `Authorization`, then: | ||
// TODO | ||
// TODO: credentials | ||
} | ||
// 20. If there’s a proxy-authentication entry, use it as appropriate. | ||
// TODO | ||
// TODO: proxy-authentication | ||
// 21. Set httpCache to the result of determining the HTTP cache | ||
// partition, given httpRequest. | ||
// TODO | ||
// TODO: cache | ||
@@ -1135,3 +1213,3 @@ // 22. If httpCache is null, then set httpRequest’s cache mode to | ||
if (httpRequest.mode !== 'no-store' && httpRequest.mode !== 'reload') { | ||
// TODO | ||
// TODO: cache | ||
} | ||
@@ -1168,3 +1246,3 @@ | ||
) { | ||
// TODO | ||
// TODO: cache | ||
} | ||
@@ -1175,3 +1253,3 @@ | ||
if (revalidatingFlag && forwardResponse.status === 304) { | ||
// TODO | ||
// TODO: cache | ||
} | ||
@@ -1186,3 +1264,3 @@ | ||
// "Storing Responses in Caches" chapter of HTTP Caching. [HTTP-CACHING] | ||
// TODO | ||
// TODO: cache | ||
} | ||
@@ -1274,3 +1352,2 @@ } | ||
// 16. If isAuthenticationFetch is true, then create an authentication entry | ||
// for request and the given realm. | ||
if (isAuthenticationFetch) { | ||
@@ -1342,3 +1419,3 @@ // TODO | ||
// given request. | ||
// TODO | ||
// TODO: cache | ||
const httpCache = null | ||
@@ -1356,3 +1433,12 @@ | ||
// 7. Switch on request’s mode: | ||
// TODO | ||
if (request.mode === 'websocket') { | ||
// Let connection be the result of obtaining a WebSocket connection, | ||
// given request’s current URL. | ||
// TODO | ||
} else { | ||
// Let connection be the result of obtaining a connection, given | ||
// networkPartitionKey, request’s current URL’s origin, | ||
// includeCredentials, and forceNewConnection. | ||
// TODO | ||
} | ||
@@ -1492,2 +1578,6 @@ // 8. Run these steps, but abort when the ongoing fetch is terminated: | ||
// highWaterMark, and sizeAlgorithm set to sizeAlgorithm. | ||
if (!ReadableStream) { | ||
ReadableStream = require('stream/web').ReadableStream | ||
} | ||
const stream = new ReadableStream( | ||
@@ -1625,3 +1715,4 @@ { | ||
} else { | ||
// TODO: What to do when coding is invalid or unsupported? | ||
decoders.length = 0 | ||
break | ||
} | ||
@@ -1775,13 +1866,2 @@ } | ||
function cancelIfReadable (stream, reason) { | ||
try { | ||
stream.cancel(reason) | ||
} catch (err) { | ||
// Will throw TypeError if body is not readable. | ||
if (err.name !== 'TypeError') { | ||
throw err | ||
} | ||
} | ||
} | ||
module.exports = fetch |
@@ -8,3 +8,3 @@ /* globals AbortController */ | ||
const util = require('../core/util') | ||
const { isValidHTTPToken } = require('./util') | ||
const { isValidHTTPToken, EnvironmentSettingsObject } = require('./util') | ||
const { | ||
@@ -20,3 +20,3 @@ forbiddenMethods, | ||
const { kEnumerableProperty } = util | ||
const { kHeaders, kSignal, kState, kGuard } = require('./symbols') | ||
const { kHeaders, kSignal, kState, kGuard, kRealm } = require('./symbols') | ||
const { kHeadersList } = require('../core/symbols') | ||
@@ -27,10 +27,24 @@ const assert = require('assert') | ||
const kInit = Symbol('init') | ||
// https://fetch.spec.whatwg.org/#request-class | ||
class Request { | ||
// https://fetch.spec.whatwg.org/#dom-request | ||
constructor (input, init = {}) { | ||
if (!(input instanceof Request)) { | ||
input = String(input) | ||
constructor (...args) { | ||
if (args[0] === kInit) { | ||
return | ||
} | ||
if (args.length < 1) { | ||
throw new TypeError(`Failed to construct 'Request': 1 argument required, but only ${args.length} present.`) | ||
} | ||
if (args.length >= 1 && typeof args[1] !== 'object' && args[1] !== undefined) { | ||
throw new TypeError("Failed to construct 'Request': cannot convert to dictionary.") | ||
} | ||
const input = args[0] instanceof Request ? args[0] : String(args[0]) | ||
const init = args.length >= 1 ? (args[1] ?? {}) : {} | ||
// TODO | ||
this[kRealm] = { settingsObject: {} } | ||
// 1. Let request be null. | ||
@@ -43,4 +57,3 @@ let request = null | ||
// 3. Let baseURL be this’s relevant settings object’s API base URL. | ||
// TODO: this’s relevant settings object’s API base URL? | ||
const baseUrl = undefined | ||
const baseUrl = this[kRealm].settingsObject.baseUrl | ||
@@ -90,3 +103,3 @@ // 4. Let signal be null. | ||
// 7. Let origin be this’s relevant settings object’s origin. | ||
// TODO | ||
const origin = this[kRealm].settingsObject.origin | ||
@@ -98,3 +111,8 @@ // 8. Let window be "client". | ||
// is same origin with origin, then set window to request’s window. | ||
// TODO | ||
if ( | ||
request.window instanceof EnvironmentSettingsObject && | ||
request.window.origin === origin | ||
) { | ||
window = request.window | ||
} | ||
@@ -112,3 +130,6 @@ // 10. If init["window"] exists and is non-null, then throw a TypeError. | ||
// 12. Set request to a new request with the following properties: | ||
request = makeRequest({ ...request, window }) | ||
request = makeRequest({ | ||
...request, | ||
window | ||
}) | ||
@@ -286,5 +307,5 @@ // 13. If init is not empty, then: | ||
// Realm. | ||
// TODO: relevant Realm? | ||
const ac = new AbortController() | ||
this[kSignal] = ac.signal | ||
this[kSignal][kRealm] = this[kRealm] | ||
@@ -320,6 +341,6 @@ // 29. If signal is not null, then make this’s signal follow signal. | ||
// "request". | ||
// TODO: relevant Realm? | ||
this[kHeaders] = new Headers() | ||
this[kHeaders][kGuard] = 'request' | ||
this[kHeaders][kHeadersList] = request.headersList | ||
this[kHeaders][kRealm] = this[kRealm] | ||
@@ -452,2 +473,6 @@ // 31. If this’s request’s mode is "no-cors", then: | ||
get [Symbol.toStringTag] () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this.constructor.name | ||
@@ -458,2 +483,6 @@ } | ||
get method () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The method getter steps are to return this’s request’s method. | ||
@@ -465,2 +494,6 @@ return this[kState].method | ||
get url () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The url getter steps are to return this’s request’s URL, serialized. | ||
@@ -474,2 +507,6 @@ return this[kState].url.toString() | ||
get headers () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The headers getter steps are to return this’s headers. | ||
@@ -482,4 +519,8 @@ return this[kHeaders] | ||
get destination () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The destination getter are to return this’s request’s destination. | ||
return '' | ||
return this[kState].destination | ||
} | ||
@@ -493,2 +534,6 @@ | ||
get referrer () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// 1. If this’s request’s referrer is "no-referrer", then return the | ||
@@ -514,2 +559,6 @@ // empty string. | ||
get referrerPolicy () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The referrerPolicy getter steps are to return this’s request’s referrer policy. | ||
@@ -523,2 +572,6 @@ return this[kState].referrerPolicy | ||
get mode () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The mode getter steps are to return this’s request’s mode. | ||
@@ -540,2 +593,6 @@ return this[kState].mode | ||
get cache () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The cache getter steps are to return this’s request’s cache mode. | ||
@@ -550,2 +607,6 @@ return this[kState].cache | ||
get redirect () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The redirect getter steps are to return this’s request’s redirect mode. | ||
@@ -559,2 +620,6 @@ return this[kState].redirect | ||
get integrity () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The integrity getter steps are to return this’s request’s integrity | ||
@@ -568,2 +633,6 @@ // metadata. | ||
get keepalive () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The keepalive getter steps are to return this’s request’s keepalive. | ||
@@ -576,2 +645,6 @@ return this[kState].keepalive | ||
get isReloadNavigation () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The isReloadNavigation getter steps are to return true if this’s | ||
@@ -585,2 +658,6 @@ // request’s reload-navigation flag is set; otherwise false. | ||
get isHistoryNavigation () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The isHistoryNavigation getter steps are to return true if this’s request’s | ||
@@ -595,2 +672,6 @@ // history-navigation flag is set; otherwise false. | ||
get signal () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The signal getter steps are to return this’s signal. | ||
@@ -602,2 +683,6 @@ return this[kSignal] | ||
clone () { | ||
if (!(this instanceof Request)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// 1. If this is unusable, then throw a TypeError. | ||
@@ -613,7 +698,9 @@ if (this.bodyUsed || (this.body && this.body.locked)) { | ||
// given clonedRequest, this’s headers’s guard, and this’s relevant Realm. | ||
// TODO: relevant Realm? | ||
const clonedRequestObject = new Request() | ||
const clonedRequestObject = new Request(kInit) | ||
clonedRequestObject[kState] = clonedRequest | ||
clonedRequestObject[kRealm] = this[kRealm] | ||
clonedRequestObject[kHeaders] = new Headers() | ||
clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList | ||
clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard] | ||
clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm] | ||
@@ -620,0 +707,0 @@ // 4. Make clonedRequestObject’s signal follow this’s signal. |
@@ -13,3 +13,3 @@ 'use strict' | ||
} = require('./constants') | ||
const { kState, kHeaders, kGuard } = require('./symbols') | ||
const { kState, kHeaders, kGuard, kRealm } = require('./symbols') | ||
const { kHeadersList } = require('../core/symbols') | ||
@@ -22,10 +22,14 @@ const assert = require('assert') | ||
static error () { | ||
// TODO | ||
const relevantRealm = { settingsObject: {} } | ||
// The static error() method steps are to return the result of creating a | ||
// Response object, given a new network error, "immutable", and this’s | ||
// relevant Realm. | ||
// TODO: relevant Realm? | ||
const responseObject = new Response() | ||
responseObject[kState] = makeNetworkError() | ||
responseObject[kRealm] = relevantRealm | ||
responseObject[kHeaders][kHeadersList] = responseObject[kState].headersList | ||
responseObject[kHeaders][kGuard] = 'immutable' | ||
responseObject[kHeaders][kRealm] = relevantRealm | ||
return responseObject | ||
@@ -35,3 +39,12 @@ } | ||
// Creates a redirect Response that redirects to url with status status. | ||
static redirect (url, status = 302) { | ||
static redirect (...args) { | ||
const relevantRealm = { settingsObject: {} } | ||
if (args.length < 1) { | ||
throw new TypeError(`Failed to execute 'redirect' on 'Response': 1 argument required, but only ${args.length} present.`) | ||
} | ||
const status = args.length >= 2 ? args[1] : 302 | ||
const url = String(args[0]) | ||
// 1. Let parsedURL be the result of parsing url with current settings | ||
@@ -45,5 +58,3 @@ // object’s API base URL. | ||
} catch (err) { | ||
const error = new TypeError('Failed to parse URL from ' + url) | ||
error.cause = err | ||
throw error | ||
throw Object.assign(new TypeError('Failed to parse URL from ' + url), { cause: err }) | ||
} | ||
@@ -58,5 +69,6 @@ | ||
// given a new response, "immutable", and this’s relevant Realm. | ||
// TODO: relevant Realm? | ||
const responseObject = new Response() | ||
responseObject[kRealm] = relevantRealm | ||
responseObject[kHeaders][kGuard] = 'immutable' | ||
responseObject[kHeaders][kRealm] = relevantRealm | ||
@@ -78,3 +90,13 @@ // 5. Set responseObject’s response’s status to status. | ||
// https://fetch.spec.whatwg.org/#dom-response | ||
constructor (body = null, init = {}) { | ||
constructor (...args) { | ||
if (args.length >= 1 && typeof args[1] !== 'object' && args[1] !== undefined) { | ||
throw new TypeError("Failed to construct 'Request': cannot convert to dictionary.") | ||
} | ||
const body = args.length >= 1 ? args[0] : null | ||
const init = args.length >= 2 ? (args[1] ?? {}) : {} | ||
// TODO | ||
this[kRealm] = { settingsObject: {} } | ||
// 1. If init["status"] is not in the range 200 to 599, inclusive, then | ||
@@ -110,6 +132,6 @@ // throw a RangeError. | ||
// is "response". | ||
// TODO: relevant Realm? | ||
this[kHeaders] = new Headers() | ||
this[kHeaders][kGuard] = 'response' | ||
this[kHeaders][kHeadersList] = this[kState].headersList | ||
this[kHeaders][kRealm] = this[kRealm] | ||
@@ -154,2 +176,6 @@ // 5. Set this’s response’s status to init["status"]. | ||
get [Symbol.toStringTag] () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this.constructor.name | ||
@@ -160,2 +186,6 @@ } | ||
get type () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The type getter steps are to return this’s response’s type. | ||
@@ -167,2 +197,6 @@ return this[kState].type | ||
get url () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The url getter steps are to return the empty string if this’s | ||
@@ -187,2 +221,6 @@ // response’s URL is null; otherwise this’s response’s URL, | ||
get redirected () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The redirected getter steps are to return true if this’s response’s URL | ||
@@ -195,2 +233,6 @@ // list has more than one item; otherwise false. | ||
get status () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The status getter steps are to return this’s response’s status. | ||
@@ -202,2 +244,6 @@ return this[kState].status | ||
get ok () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The ok getter steps are to return true if this’s response’s status is an | ||
@@ -210,2 +256,6 @@ // ok status; otherwise false. | ||
get statusText () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The statusText getter steps are to return this’s response’s status | ||
@@ -218,2 +268,6 @@ // message. | ||
get headers () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// The headers getter steps are to return this’s headers. | ||
@@ -225,2 +279,6 @@ return this[kHeaders] | ||
clone () { | ||
if (!(this instanceof Response)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
// 1. If this is unusable, then throw a TypeError. | ||
@@ -236,7 +294,8 @@ if (this.bodyUsed || (this.body && this.body.locked)) { | ||
// clonedResponse, this’s headers’s guard, and this’s relevant Realm. | ||
// TODO: relevant Realm? | ||
const clonedResponseObject = new Response() | ||
clonedResponseObject[kState] = clonedResponse | ||
clonedResponseObject[kRealm] = this[kRealm] | ||
clonedResponseObject[kHeaders][kHeadersList] = clonedResponse.headersList | ||
clonedResponseObject[kHeaders][kGuard] = this[kHeaders][kGuard] | ||
clonedResponseObject[kHeaders][kRealm] = this[kHeaders][kRealm] | ||
@@ -243,0 +302,0 @@ return clonedResponseObject |
@@ -8,3 +8,4 @@ 'use strict' | ||
kState: Symbol('state'), | ||
kGuard: Symbol('guard') | ||
kGuard: Symbol('guard'), | ||
kRealm: Symbol('realm') | ||
} |
@@ -7,2 +7,3 @@ 'use strict' | ||
const { finished } = require('stream') | ||
const { performance } = require('perf_hooks') | ||
@@ -182,3 +183,7 @@ let ReadableStream | ||
} else { | ||
controller.close() | ||
queueMicrotask(() => { | ||
// Must not use `process.nextTick()`. | ||
// See https://github.com/nodejs/node/issues/39758 | ||
controller.close() | ||
}) | ||
} | ||
@@ -224,2 +229,55 @@ }) | ||
// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check | ||
function crossOriginResourcePolicyCheck () { | ||
// TODO | ||
return 'allowed' | ||
} | ||
// https://fetch.spec.whatwg.org/#concept-cors-check | ||
function corsCheck () { | ||
// TODO | ||
return 'success' | ||
} | ||
// https://fetch.spec.whatwg.org/#concept-tao-check | ||
function TAOCheck () { | ||
// TODO | ||
return 'success' | ||
} | ||
function appendFetchMetadata (httpRequest) { | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header | ||
// TODO | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header | ||
// 1. Assert: r’s url is a potentially trustworthy URL. | ||
// TODO | ||
// 2. Let header be a Structured Header whose value is a token. | ||
let header = null | ||
// 3. Set header’s value to r’s mode. | ||
header = httpRequest.mode | ||
// 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list. | ||
httpRequest.headersList.append('sec-fetch-mode', header) | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header | ||
// TODO | ||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header | ||
// TODO | ||
} | ||
// https://fetch.spec.whatwg.org/#append-a-request-origin-header | ||
function appendRequestOriginHeader (request) { | ||
// TODO | ||
} | ||
function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) { | ||
// TODO | ||
return performance.now() | ||
} | ||
function makeTimingInfo (init) { | ||
@@ -242,3 +300,48 @@ return { | ||
// https://html.spec.whatwg.org/multipage/origin.html#policy-container | ||
function makePolicyContainer () { | ||
// TODO | ||
return {} | ||
} | ||
// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container | ||
function clonePolicyContainer () { | ||
// TODO | ||
return {} | ||
} | ||
// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer | ||
function determineRequestsReferrer (request) { | ||
// TODO | ||
return 'no-referrer' | ||
} | ||
function matchRequestIntegrity (request, bytes) { | ||
return false | ||
} | ||
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request | ||
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { | ||
// TODO | ||
} | ||
class ServiceWorkerGlobalScope {} // dummy | ||
class Window {} // dummy | ||
class EnvironmentSettingsObject {} // dummy | ||
module.exports = { | ||
ServiceWorkerGlobalScope, | ||
Window, | ||
EnvironmentSettingsObject, | ||
tryUpgradeRequestToAPotentiallyTrustworthyURL, | ||
coarsenedSharedCurrentTime, | ||
matchRequestIntegrity, | ||
determineRequestsReferrer, | ||
makePolicyContainer, | ||
clonePolicyContainer, | ||
appendFetchMetadata, | ||
appendRequestOriginHeader, | ||
TAOCheck, | ||
corsCheck, | ||
crossOriginResourcePolicyCheck, | ||
toWebReadable, | ||
@@ -245,0 +348,0 @@ makeTimingInfo, |
@@ -7,2 +7,3 @@ 'use strict' | ||
const { InvalidArgumentError } = require('../core/errors') | ||
const EE = require('events') | ||
@@ -55,4 +56,3 @@ const redirectableStatusCodes = [300, 301, 302, 303, 307, 308] | ||
this.opts.body[kBodyUsed] = false | ||
// TODO (fix): Don't mutate readable state... | ||
this.opts.body.on('data', function () { | ||
EE.prototype.on.call(this.opts.body, 'data', function () { | ||
this[kBodyUsed] = true | ||
@@ -59,0 +59,0 @@ }) |
{ | ||
"name": "undici", | ||
"version": "4.4.2", | ||
"version": "4.4.3", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -36,10 +36,10 @@ "homepage": "https://undici.nodejs.org", | ||
"lint:fix": "standard --fix | snazzy", | ||
"test": "tap test/*.js --no-coverage && mocha test/node-fetch && jest test/jest/test", | ||
"test": "npm run test:tap && npm run test:node-fetch && npm run test:jest", | ||
"test:node-fetch": "node scripts/test-node-fetch.js 16 && mocha test/node-fetch || echo Skipping", | ||
"test:jest": "jest test/jest/test", | ||
"test:tap": "tap test/*.js --no-coverage ", | ||
"test:tdd": "tap test/*.js -w --no-coverage-report", | ||
"test:tap": "tap test/*.js", | ||
"test:tdd": "tap test/*.js -w", | ||
"test:typescript": "tsd", | ||
"coverage": "standard | snazzy && tap test/*.js", | ||
"coverage:ci": "npm run coverage -- --coverage-report=lcovonly", | ||
"coverage": "nyc npm run test", | ||
"coverage:ci": "npm run coverage -- --reporter=lcovonly", | ||
"bench": "concurrently -k -s first npm:bench:server npm:bench:run", | ||
@@ -95,2 +95,5 @@ "bench:server": "node benchmarks/server.js", | ||
}, | ||
"tap": { | ||
"check-coverage": false | ||
}, | ||
"tsd": { | ||
@@ -97,0 +100,0 @@ "directory": "test/types", |
@@ -94,3 +94,3 @@ # undici | ||
* **url** `string | URL | object` | ||
* **url** `string | URL | UrlObject` | ||
* **options** [`RequestOptions`](./docs/api/Dispatcher.md#parameter-requestoptions) | ||
@@ -111,3 +111,3 @@ * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcherdispatcher) | ||
* **url** `string | URL | object` | ||
* **url** `string | URL | UrlObject` | ||
* **options** [`StreamOptions`](./docs/api/Dispatcher.md#parameter-streamoptions) | ||
@@ -129,3 +129,3 @@ * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcherdispatcher) | ||
* **url** `string | URL | object` | ||
* **url** `string | URL | UrlObject` | ||
* **options** [`PipelineOptions`](docs/api/Dispatcher.md#parameter-pipelineoptions) | ||
@@ -149,3 +149,3 @@ * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcherdispatcher) | ||
* **url** `string | URL | object` | ||
* **url** `string | URL | UrlObject` | ||
* **options** [`ConnectOptions`](docs/api/Dispatcher.md#parameter-connectoptions) | ||
@@ -166,8 +166,8 @@ * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcherdispatcher) | ||
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch | ||
https://fetch.spec.whatwg.org/#fetch-method | ||
* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch | ||
* https://fetch.spec.whatwg.org/#fetch-method | ||
Only supported on Node 16+. | ||
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant the Fetch Standard. We plan to ship breaking changes to this feature until it is out of experimental. | ||
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard. We plan to ship breaking changes to this feature until it is out of experimental. | ||
@@ -180,3 +180,3 @@ ### `undici.upgrade([url, options]): Promise` | ||
* **url** `string | URL | object` | ||
* **url** `string | URL | UrlObject` | ||
* **options** [`UpgradeOptions`](docs/api/Dispatcher.md#parameter-upgradeoptions) | ||
@@ -205,2 +205,12 @@ * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcherdispatcher) | ||
### `UrlObject` | ||
* **port** `string | number` (optional) | ||
* **path** `string` (optional) | ||
* **pathname** `string` (optional) | ||
* **hostname** `string` (optional) | ||
* **origin** `string` (optional) | ||
* **protocol** `string` (optional) | ||
* **search** `string` (optional) | ||
## Specification Compliance | ||
@@ -207,0 +217,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
580136
8989
259