1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033 |
- 'use strict'
- const { redirectStatus, badPorts, referrerPolicy: referrerPolicyTokens } = require('./constants')
- const { getGlobalOrigin } = require('./global')
- const { performance } = require('perf_hooks')
- const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
- const assert = require('assert')
- const { isUint8Array } = require('util/types')
- // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
- /** @type {import('crypto')|undefined} */
- let crypto
- try {
- crypto = require('crypto')
- } catch {
- }
- function responseURL (response) {
- // https://fetch.spec.whatwg.org/#responses
- // A response has an associated URL. It is a pointer to the last URL
- // in response’s URL list and null if response’s URL list is empty.
- const urlList = response.urlList
- const length = urlList.length
- return length === 0 ? null : urlList[length - 1].toString()
- }
- // https://fetch.spec.whatwg.org/#concept-response-location-url
- function responseLocationURL (response, requestFragment) {
- // 1. If response’s status is not a redirect status, then return null.
- if (!redirectStatus.includes(response.status)) {
- return null
- }
- // 2. Let location be the result of extracting header list values given
- // `Location` and response’s header list.
- let location = response.headersList.get('location')
- // 3. If location is a header value, then set location to the result of
- // parsing location with response’s URL.
- if (location !== null && isValidHeaderValue(location)) {
- location = new URL(location, responseURL(response))
- }
- // 4. If location is a URL whose fragment is null, then set location’s
- // fragment to requestFragment.
- if (location && !location.hash) {
- location.hash = requestFragment
- }
- // 5. Return location.
- return location
- }
- /** @returns {URL} */
- function requestCurrentURL (request) {
- return request.urlList[request.urlList.length - 1]
- }
- function requestBadPort (request) {
- // 1. Let url be request’s current URL.
- const url = requestCurrentURL(request)
- // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
- // then return blocked.
- if (urlIsHttpHttpsScheme(url) && badPorts.includes(url.port)) {
- return 'blocked'
- }
- // 3. Return allowed.
- return 'allowed'
- }
- function isErrorLike (object) {
- return object instanceof Error || (
- object?.constructor?.name === 'Error' ||
- object?.constructor?.name === 'DOMException'
- )
- }
- // Check whether |statusText| is a ByteString and
- // matches the Reason-Phrase token production.
- // RFC 2616: https://tools.ietf.org/html/rfc2616
- // RFC 7230: https://tools.ietf.org/html/rfc7230
- // "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
- // https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
- function isValidReasonPhrase (statusText) {
- for (let i = 0; i < statusText.length; ++i) {
- const c = statusText.charCodeAt(i)
- if (
- !(
- (
- c === 0x09 || // HTAB
- (c >= 0x20 && c <= 0x7e) || // SP / VCHAR
- (c >= 0x80 && c <= 0xff)
- ) // obs-text
- )
- ) {
- return false
- }
- }
- return true
- }
- function isTokenChar (c) {
- return !(
- c >= 0x7f ||
- c <= 0x20 ||
- c === '(' ||
- c === ')' ||
- c === '<' ||
- c === '>' ||
- c === '@' ||
- c === ',' ||
- c === ';' ||
- c === ':' ||
- c === '\\' ||
- c === '"' ||
- c === '/' ||
- c === '[' ||
- c === ']' ||
- c === '?' ||
- c === '=' ||
- c === '{' ||
- c === '}'
- )
- }
- // See RFC 7230, Section 3.2.6.
- // https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321
- function isValidHTTPToken (characters) {
- if (!characters || typeof characters !== 'string') {
- return false
- }
- for (let i = 0; i < characters.length; ++i) {
- const c = characters.charCodeAt(i)
- if (c > 0x7f || !isTokenChar(c)) {
- return false
- }
- }
- return true
- }
- // https://fetch.spec.whatwg.org/#header-name
- // https://github.com/chromium/chromium/blob/b3d37e6f94f87d59e44662d6078f6a12de845d17/net/http/http_util.cc#L342
- function isValidHeaderName (potentialValue) {
- if (potentialValue.length === 0) {
- return false
- }
- return isValidHTTPToken(potentialValue)
- }
- /**
- * @see https://fetch.spec.whatwg.org/#header-value
- * @param {string} potentialValue
- */
- function isValidHeaderValue (potentialValue) {
- // - Has no leading or trailing HTTP tab or space bytes.
- // - Contains no 0x00 (NUL) or HTTP newline bytes.
- if (
- potentialValue.startsWith('\t') ||
- potentialValue.startsWith(' ') ||
- potentialValue.endsWith('\t') ||
- potentialValue.endsWith(' ')
- ) {
- return false
- }
- if (
- potentialValue.includes('\0') ||
- potentialValue.includes('\r') ||
- potentialValue.includes('\n')
- ) {
- return false
- }
- return true
- }
- // https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
- function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
- // Given a request request and a response actualResponse, this algorithm
- // updates request’s referrer policy according to the Referrer-Policy
- // header (if any) in actualResponse.
- // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
- // from a Referrer-Policy header on actualResponse.
- // 8.1 Parse a referrer policy from a Referrer-Policy header
- // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
- const { headersList } = actualResponse
- // 2. Let policy be the empty string.
- // 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
- // 4. Return policy.
- const policyHeader = (headersList.get('referrer-policy') ?? '').split(',')
- // Note: As the referrer-policy can contain multiple policies
- // separated by comma, we need to loop through all of them
- // and pick the first valid one.
- // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
- let policy = ''
- if (policyHeader.length > 0) {
- // The right-most policy takes precedence.
- // The left-most policy is the fallback.
- for (let i = policyHeader.length; i !== 0; i--) {
- const token = policyHeader[i - 1].trim()
- if (referrerPolicyTokens.includes(token)) {
- policy = token
- break
- }
- }
- }
- // 2. If policy is not the empty string, then set request’s referrer policy to policy.
- if (policy !== '') {
- request.referrerPolicy = policy
- }
- }
- // 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.set('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) {
- // 1. Let serializedOrigin be the result of byte-serializing a request origin with request.
- let serializedOrigin = request.origin
- // 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list.
- if (request.responseTainting === 'cors' || request.mode === 'websocket') {
- if (serializedOrigin) {
- request.headersList.append('origin', serializedOrigin)
- }
- // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
- } else if (request.method !== 'GET' && request.method !== 'HEAD') {
- // 1. Switch on request’s referrer policy:
- switch (request.referrerPolicy) {
- case 'no-referrer':
- // Set serializedOrigin to `null`.
- serializedOrigin = null
- break
- case 'no-referrer-when-downgrade':
- case 'strict-origin':
- case 'strict-origin-when-cross-origin':
- // If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is not "https", then set serializedOrigin to `null`.
- if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) {
- serializedOrigin = null
- }
- break
- case 'same-origin':
- // If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin to `null`.
- if (!sameOrigin(request, requestCurrentURL(request))) {
- serializedOrigin = null
- }
- break
- default:
- // Do nothing.
- }
- if (serializedOrigin) {
- // 2. Append (`Origin`, serializedOrigin) to request’s header list.
- request.headersList.append('origin', serializedOrigin)
- }
- }
- }
- function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
- // TODO
- return performance.now()
- }
- // https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
- function createOpaqueTimingInfo (timingInfo) {
- return {
- startTime: timingInfo.startTime ?? 0,
- redirectStartTime: 0,
- redirectEndTime: 0,
- postRedirectStartTime: timingInfo.startTime ?? 0,
- finalServiceWorkerStartTime: 0,
- finalNetworkResponseStartTime: 0,
- finalNetworkRequestStartTime: 0,
- endTime: 0,
- encodedBodySize: 0,
- decodedBodySize: 0,
- finalConnectionTimingInfo: null
- }
- }
- // https://html.spec.whatwg.org/multipage/origin.html#policy-container
- function makePolicyContainer () {
- // Note: the fetch spec doesn't make use of embedder policy or CSP list
- return {
- referrerPolicy: 'strict-origin-when-cross-origin'
- }
- }
- // https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
- function clonePolicyContainer (policyContainer) {
- return {
- referrerPolicy: policyContainer.referrerPolicy
- }
- }
- // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
- function determineRequestsReferrer (request) {
- // 1. Let policy be request's referrer policy.
- const policy = request.referrerPolicy
- // Note: policy cannot (shouldn't) be null or an empty string.
- assert(policy)
- // 2. Let environment be request’s client.
- let referrerSource = null
- // 3. Switch on request’s referrer:
- if (request.referrer === 'client') {
- // Note: node isn't a browser and doesn't implement document/iframes,
- // so we bypass this step and replace it with our own.
- const globalOrigin = getGlobalOrigin()
- if (!globalOrigin || globalOrigin.origin === 'null') {
- return 'no-referrer'
- }
- // note: we need to clone it as it's mutated
- referrerSource = new URL(globalOrigin)
- } else if (request.referrer instanceof URL) {
- // Let referrerSource be request’s referrer.
- referrerSource = request.referrer
- }
- // 4. Let request’s referrerURL be the result of stripping referrerSource for
- // use as a referrer.
- let referrerURL = stripURLForReferrer(referrerSource)
- // 5. Let referrerOrigin be the result of stripping referrerSource for use as
- // a referrer, with the origin-only flag set to true.
- const referrerOrigin = stripURLForReferrer(referrerSource, true)
- // 6. If the result of serializing referrerURL is a string whose length is
- // greater than 4096, set referrerURL to referrerOrigin.
- if (referrerURL.toString().length > 4096) {
- referrerURL = referrerOrigin
- }
- const areSameOrigin = sameOrigin(request, referrerURL)
- const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) &&
- !isURLPotentiallyTrustworthy(request.url)
- // 8. Execute the switch statements corresponding to the value of policy:
- switch (policy) {
- case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true)
- case 'unsafe-url': return referrerURL
- case 'same-origin':
- return areSameOrigin ? referrerOrigin : 'no-referrer'
- case 'origin-when-cross-origin':
- return areSameOrigin ? referrerURL : referrerOrigin
- case 'strict-origin-when-cross-origin': {
- const currentURL = requestCurrentURL(request)
- // 1. If the origin of referrerURL and the origin of request’s current
- // URL are the same, then return referrerURL.
- if (sameOrigin(referrerURL, currentURL)) {
- return referrerURL
- }
- // 2. If referrerURL is a potentially trustworthy URL and request’s
- // current URL is not a potentially trustworthy URL, then return no
- // referrer.
- if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
- return 'no-referrer'
- }
- // 3. Return referrerOrigin.
- return referrerOrigin
- }
- case 'strict-origin': // eslint-disable-line
- /**
- * 1. If referrerURL is a potentially trustworthy URL and
- * request’s current URL is not a potentially trustworthy URL,
- * then return no referrer.
- * 2. Return referrerOrigin
- */
- case 'no-referrer-when-downgrade': // eslint-disable-line
- /**
- * 1. If referrerURL is a potentially trustworthy URL and
- * request’s current URL is not a potentially trustworthy URL,
- * then return no referrer.
- * 2. Return referrerOrigin
- */
- default: // eslint-disable-line
- return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin
- }
- }
- /**
- * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
- * @param {URL} url
- * @param {boolean|undefined} originOnly
- */
- function stripURLForReferrer (url, originOnly) {
- // 1. Assert: url is a URL.
- assert(url instanceof URL)
- // 2. If url’s scheme is a local scheme, then return no referrer.
- if (url.protocol === 'file:' || url.protocol === 'about:' || url.protocol === 'blank:') {
- return 'no-referrer'
- }
- // 3. Set url’s username to the empty string.
- url.username = ''
- // 4. Set url’s password to the empty string.
- url.password = ''
- // 5. Set url’s fragment to null.
- url.hash = ''
- // 6. If the origin-only flag is true, then:
- if (originOnly) {
- // 1. Set url’s path to « the empty string ».
- url.pathname = ''
- // 2. Set url’s query to null.
- url.search = ''
- }
- // 7. Return url.
- return url
- }
- function isURLPotentiallyTrustworthy (url) {
- if (!(url instanceof URL)) {
- return false
- }
- // If child of about, return true
- if (url.href === 'about:blank' || url.href === 'about:srcdoc') {
- return true
- }
- // If scheme is data, return true
- if (url.protocol === 'data:') return true
- // If file, return true
- if (url.protocol === 'file:') return true
- return isOriginPotentiallyTrustworthy(url.origin)
- function isOriginPotentiallyTrustworthy (origin) {
- // If origin is explicitly null, return false
- if (origin == null || origin === 'null') return false
- const originAsURL = new URL(origin)
- // If secure, return true
- if (originAsURL.protocol === 'https:' || originAsURL.protocol === 'wss:') {
- return true
- }
- // If localhost or variants, return true
- if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) ||
- (originAsURL.hostname === 'localhost' || originAsURL.hostname.includes('localhost.')) ||
- (originAsURL.hostname.endsWith('.localhost'))) {
- return true
- }
- // If any other, return false
- return false
- }
- }
- /**
- * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
- * @param {Uint8Array} bytes
- * @param {string} metadataList
- */
- function bytesMatch (bytes, metadataList) {
- // If node is not built with OpenSSL support, we cannot check
- // a request's integrity, so allow it by default (the spec will
- // allow requests if an invalid hash is given, as precedence).
- /* istanbul ignore if: only if node is built with --without-ssl */
- if (crypto === undefined) {
- return true
- }
- // 1. Let parsedMetadata be the result of parsing metadataList.
- const parsedMetadata = parseMetadata(metadataList)
- // 2. If parsedMetadata is no metadata, return true.
- if (parsedMetadata === 'no metadata') {
- return true
- }
- // 3. If parsedMetadata is the empty set, return true.
- if (parsedMetadata.length === 0) {
- return true
- }
- // 4. Let metadata be the result of getting the strongest
- // metadata from parsedMetadata.
- const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
- // get the strongest algorithm
- const strongest = list[0].algo
- // get all entries that use the strongest algorithm; ignore weaker
- const metadata = list.filter((item) => item.algo === strongest)
- // 5. For each item in metadata:
- for (const item of metadata) {
- // 1. Let algorithm be the alg component of item.
- const algorithm = item.algo
- // 2. Let expectedValue be the val component of item.
- const expectedValue = item.hash
- // 3. Let actualValue be the result of applying algorithm to bytes.
- const actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
- // 4. If actualValue is a case-sensitive match for expectedValue,
- // return true.
- if (actualValue === expectedValue) {
- return true
- }
- }
- // 6. Return false.
- return false
- }
- // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
- // https://www.w3.org/TR/CSP2/#source-list-syntax
- // https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
- const parseHashWithOptions = /((?<algo>sha256|sha384|sha512)-(?<hash>[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i
- /**
- * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
- * @param {string} metadata
- */
- function parseMetadata (metadata) {
- // 1. Let result be the empty set.
- /** @type {{ algo: string, hash: string }[]} */
- const result = []
- // 2. Let empty be equal to true.
- let empty = true
- const supportedHashes = crypto.getHashes()
- // 3. For each token returned by splitting metadata on spaces:
- for (const token of metadata.split(' ')) {
- // 1. Set empty to false.
- empty = false
- // 2. Parse token as a hash-with-options.
- const parsedToken = parseHashWithOptions.exec(token)
- // 3. If token does not parse, continue to the next token.
- if (parsedToken === null || parsedToken.groups === undefined) {
- // Note: Chromium blocks the request at this point, but Firefox
- // gives a warning that an invalid integrity was given. The
- // correct behavior is to ignore these, and subsequently not
- // check the integrity of the resource.
- continue
- }
- // 4. Let algorithm be the hash-algo component of token.
- const algorithm = parsedToken.groups.algo
- // 5. If algorithm is a hash function recognized by the user
- // agent, add the parsed token to result.
- if (supportedHashes.includes(algorithm.toLowerCase())) {
- result.push(parsedToken.groups)
- }
- }
- // 4. Return no metadata if empty is true, otherwise return result.
- if (empty === true) {
- return 'no metadata'
- }
- return result
- }
- // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
- function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
- // TODO
- }
- /**
- * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
- * @param {URL} A
- * @param {URL} B
- */
- function sameOrigin (A, B) {
- // 1. If A and B are the same opaque origin, then return true.
- if (A.origin === B.origin && A.origin === 'null') {
- return true
- }
- // 2. If A and B are both tuple origins and their schemes,
- // hosts, and port are identical, then return true.
- if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
- return true
- }
- // 3. Return false.
- return false
- }
- function createDeferredPromise () {
- let res
- let rej
- const promise = new Promise((resolve, reject) => {
- res = resolve
- rej = reject
- })
- return { promise, resolve: res, reject: rej }
- }
- function isAborted (fetchParams) {
- return fetchParams.controller.state === 'aborted'
- }
- function isCancelled (fetchParams) {
- return fetchParams.controller.state === 'aborted' ||
- fetchParams.controller.state === 'terminated'
- }
- // https://fetch.spec.whatwg.org/#concept-method-normalize
- function normalizeMethod (method) {
- return /^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$/i.test(method)
- ? method.toUpperCase()
- : method
- }
- // https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
- function serializeJavascriptValueToJSONString (value) {
- // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
- const result = JSON.stringify(value)
- // 2. If result is undefined, then throw a TypeError.
- if (result === undefined) {
- throw new TypeError('Value is not JSON serializable')
- }
- // 3. Assert: result is a string.
- assert(typeof result === 'string')
- // 4. Return result.
- return result
- }
- // https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
- const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
- /**
- * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
- * @param {() => unknown[]} iterator
- * @param {string} name name of the instance
- * @param {'key'|'value'|'key+value'} kind
- */
- function makeIterator (iterator, name, kind) {
- const object = {
- index: 0,
- kind,
- target: iterator
- }
- const i = {
- next () {
- // 1. Let interface be the interface for which the iterator prototype object exists.
- // 2. Let thisValue be the this value.
- // 3. Let object be ? ToObject(thisValue).
- // 4. If object is a platform object, then perform a security
- // check, passing:
- // 5. If object is not a default iterator object for interface,
- // then throw a TypeError.
- if (Object.getPrototypeOf(this) !== i) {
- throw new TypeError(
- `'next' called on an object that does not implement interface ${name} Iterator.`
- )
- }
- // 6. Let index be object’s index.
- // 7. Let kind be object’s kind.
- // 8. Let values be object’s target's value pairs to iterate over.
- const { index, kind, target } = object
- const values = target()
- // 9. Let len be the length of values.
- const len = values.length
- // 10. If index is greater than or equal to len, then return
- // CreateIterResultObject(undefined, true).
- if (index >= len) {
- return { value: undefined, done: true }
- }
- // 11. Let pair be the entry in values at index index.
- const pair = values[index]
- // 12. Set object’s index to index + 1.
- object.index = index + 1
- // 13. Return the iterator result for pair and kind.
- return iteratorResult(pair, kind)
- },
- // The class string of an iterator prototype object for a given interface is the
- // result of concatenating the identifier of the interface and the string " Iterator".
- [Symbol.toStringTag]: `${name} Iterator`
- }
- // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
- Object.setPrototypeOf(i, esIteratorPrototype)
- // esIteratorPrototype needs to be the prototype of i
- // which is the prototype of an empty object. Yes, it's confusing.
- return Object.setPrototypeOf({}, i)
- }
- // https://webidl.spec.whatwg.org/#iterator-result
- function iteratorResult (pair, kind) {
- let result
- // 1. Let result be a value determined by the value of kind:
- switch (kind) {
- case 'key': {
- // 1. Let idlKey be pair’s key.
- // 2. Let key be the result of converting idlKey to an
- // ECMAScript value.
- // 3. result is key.
- result = pair[0]
- break
- }
- case 'value': {
- // 1. Let idlValue be pair’s value.
- // 2. Let value be the result of converting idlValue to
- // an ECMAScript value.
- // 3. result is value.
- result = pair[1]
- break
- }
- case 'key+value': {
- // 1. Let idlKey be pair’s key.
- // 2. Let idlValue be pair’s value.
- // 3. Let key be the result of converting idlKey to an
- // ECMAScript value.
- // 4. Let value be the result of converting idlValue to
- // an ECMAScript value.
- // 5. Let array be ! ArrayCreate(2).
- // 6. Call ! CreateDataProperty(array, "0", key).
- // 7. Call ! CreateDataProperty(array, "1", value).
- // 8. result is array.
- result = pair
- break
- }
- }
- // 2. Return CreateIterResultObject(result, false).
- return { value: result, done: false }
- }
- /**
- * @see https://fetch.spec.whatwg.org/#body-fully-read
- */
- function fullyReadBody (body, processBody, processBodyError) {
- // 1. If taskDestination is null, then set taskDestination to
- // the result of starting a new parallel queue.
- // 2. Let successSteps given a byte sequence bytes be to queue a
- // fetch task to run processBody given bytes, with taskDestination.
- const successSteps = (bytes) => queueMicrotask(() => processBody(bytes))
- // 3. Let errorSteps be to queue a fetch task to run processBodyError,
- // with taskDestination.
- const errorSteps = (error) => queueMicrotask(() => processBodyError(error))
- // 4. Let reader be the result of getting a reader for body’s stream.
- // If that threw an exception, then run errorSteps with that
- // exception and return.
- let reader
- try {
- reader = body.stream.getReader()
- } catch (e) {
- errorSteps(e)
- return
- }
- // 5. Read all bytes from reader, given successSteps and errorSteps.
- readAllBytes(reader, successSteps, errorSteps)
- }
- /** @type {ReadableStream} */
- let ReadableStream = globalThis.ReadableStream
- function isReadableStreamLike (stream) {
- if (!ReadableStream) {
- ReadableStream = require('stream/web').ReadableStream
- }
- return stream instanceof ReadableStream || (
- stream[Symbol.toStringTag] === 'ReadableStream' &&
- typeof stream.tee === 'function'
- )
- }
- const MAXIMUM_ARGUMENT_LENGTH = 65535
- /**
- * @see https://infra.spec.whatwg.org/#isomorphic-decode
- * @param {number[]|Uint8Array} input
- */
- function isomorphicDecode (input) {
- // 1. To isomorphic decode a byte sequence input, return a string whose code point
- // length is equal to input’s length and whose code points have the same values
- // as the values of input’s bytes, in the same order.
- if (input.length < MAXIMUM_ARGUMENT_LENGTH) {
- return String.fromCharCode(...input)
- }
- return input.reduce((previous, current) => previous + String.fromCharCode(current), '')
- }
- /**
- * @param {ReadableStreamController<Uint8Array>} controller
- */
- function readableStreamClose (controller) {
- try {
- controller.close()
- } catch (err) {
- // TODO: add comment explaining why this error occurs.
- if (!err.message.includes('Controller is already closed')) {
- throw err
- }
- }
- }
- /**
- * @see https://infra.spec.whatwg.org/#isomorphic-encode
- * @param {string} input
- */
- function isomorphicEncode (input) {
- // 1. Assert: input contains no code points greater than U+00FF.
- for (let i = 0; i < input.length; i++) {
- assert(input.charCodeAt(i) <= 0xFF)
- }
- // 2. Return a byte sequence whose length is equal to input’s code
- // point length and whose bytes have the same values as the
- // values of input’s code points, in the same order
- return input
- }
- /**
- * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
- * @see https://streams.spec.whatwg.org/#read-loop
- * @param {ReadableStreamDefaultReader} reader
- * @param {(bytes: Uint8Array) => void} successSteps
- * @param {(error: Error) => void} failureSteps
- */
- async function readAllBytes (reader, successSteps, failureSteps) {
- const bytes = []
- let byteLength = 0
- while (true) {
- let done
- let chunk
- try {
- ({ done, value: chunk } = await reader.read())
- } catch (e) {
- // 1. Call failureSteps with e.
- failureSteps(e)
- return
- }
- if (done) {
- // 1. Call successSteps with bytes.
- successSteps(Buffer.concat(bytes, byteLength))
- return
- }
- // 1. If chunk is not a Uint8Array object, call failureSteps
- // with a TypeError and abort these steps.
- if (!isUint8Array(chunk)) {
- failureSteps(new TypeError('Received non-Uint8Array chunk'))
- return
- }
- // 2. Append the bytes represented by chunk to bytes.
- bytes.push(chunk)
- byteLength += chunk.length
- // 3. Read-loop given reader, bytes, successSteps, and failureSteps.
- }
- }
- /**
- * @see https://fetch.spec.whatwg.org/#is-local
- * @param {URL} url
- */
- function urlIsLocal (url) {
- assert('protocol' in url) // ensure it's a url object
- const protocol = url.protocol
- return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:'
- }
- /**
- * @param {string|URL} url
- */
- function urlHasHttpsScheme (url) {
- if (typeof url === 'string') {
- return url.startsWith('https:')
- }
- return url.protocol === 'https:'
- }
- /**
- * @see https://fetch.spec.whatwg.org/#http-scheme
- * @param {URL} url
- */
- function urlIsHttpHttpsScheme (url) {
- assert('protocol' in url) // ensure it's a url object
- const protocol = url.protocol
- return protocol === 'http:' || protocol === 'https:'
- }
- /**
- * Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0.
- */
- const hasOwn = Object.hasOwn || ((dict, key) => Object.prototype.hasOwnProperty.call(dict, key))
- module.exports = {
- isAborted,
- isCancelled,
- createDeferredPromise,
- ReadableStreamFrom,
- toUSVString,
- tryUpgradeRequestToAPotentiallyTrustworthyURL,
- coarsenedSharedCurrentTime,
- determineRequestsReferrer,
- makePolicyContainer,
- clonePolicyContainer,
- appendFetchMetadata,
- appendRequestOriginHeader,
- TAOCheck,
- corsCheck,
- crossOriginResourcePolicyCheck,
- createOpaqueTimingInfo,
- setRequestReferrerPolicyOnRedirect,
- isValidHTTPToken,
- requestBadPort,
- requestCurrentURL,
- responseURL,
- responseLocationURL,
- isBlobLike,
- isURLPotentiallyTrustworthy,
- isValidReasonPhrase,
- sameOrigin,
- normalizeMethod,
- serializeJavascriptValueToJSONString,
- makeIterator,
- isValidHeaderName,
- isValidHeaderValue,
- hasOwn,
- isErrorLike,
- fullyReadBody,
- bytesMatch,
- isReadableStreamLike,
- readableStreamClose,
- isomorphicEncode,
- isomorphicDecode,
- urlIsLocal,
- urlHasHttpsScheme,
- urlIsHttpHttpsScheme,
- readAllBytes
- }
|