Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
util.js1633 linesDownload Raw Back to fetch
1'use strict'
2
3const { Transform } = require('node:stream')
4const zlib = require('node:zlib')
5const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require('./constants')
6const { getGlobalOrigin } = require('./global')
7const { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require('./data-url')
8const { performance } = require('node:perf_hooks')
9const { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
10const assert = require('node:assert')
11const { isUint8Array } = require('node:util/types')
12const { webidl } = require('./webidl')
13
14let supportedHashes = []
15
16// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
17/** @type {import('crypto')} */
18let crypto
19try {
20  crypto = require('node:crypto')
21  const possibleRelevantHashes = ['sha256', 'sha384', 'sha512']
22  supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash))
23/* c8 ignore next 3 */
24} catch {
25
26}
27
28function responseURL (response) {
29  // https://fetch.spec.whatwg.org/#responses
30  // A response has an associated URL. It is a pointer to the last URL
31  // in response’s URL list and null if response’s URL list is empty.
32  const urlList = response.urlList
33  const length = urlList.length
34  return length === 0 ? null : urlList[length - 1].toString()
35}
36
37// https://fetch.spec.whatwg.org/#concept-response-location-url
38function responseLocationURL (response, requestFragment) {
39  // 1. If response’s status is not a redirect status, then return null.
40  if (!redirectStatusSet.has(response.status)) {
41    return null
42  }
43
44  // 2. Let location be the result of extracting header list values given
45  // `Location` and response’s header list.
46  let location = response.headersList.get('location', true)
47
48  // 3. If location is a header value, then set location to the result of
49  //    parsing location with response’s URL.
50  if (location !== null && isValidHeaderValue(location)) {
51    if (!isValidEncodedURL(location)) {
52      // Some websites respond location header in UTF-8 form without encoding them as ASCII
53      // and major browsers redirect them to correctly UTF-8 encoded addresses.
54      // Here, we handle that behavior in the same way.
55      location = normalizeBinaryStringToUtf8(location)
56    }
57    location = new URL(location, responseURL(response))
58  }
59
60  // 4. If location is a URL whose fragment is null, then set location’s
61  // fragment to requestFragment.
62  if (location && !location.hash) {
63    location.hash = requestFragment
64  }
65
66  // 5. Return location.
67  return location
68}
69
70/**
71 * @see https://www.rfc-editor.org/rfc/rfc1738#section-2.2
72 * @param {string} url
73 * @returns {boolean}
74 */
75function isValidEncodedURL (url) {
76  for (let i = 0; i < url.length; ++i) {
77    const code = url.charCodeAt(i)
78
79    if (
80      code > 0x7E || // Non-US-ASCII + DEL
81      code < 0x20 // Control characters NUL - US
82    ) {
83      return false
84    }
85  }
86  return true
87}
88
89/**
90 * If string contains non-ASCII characters, assumes it's UTF-8 encoded and decodes it.
91 * Since UTF-8 is a superset of ASCII, this will work for ASCII strings as well.
92 * @param {string} value
93 * @returns {string}
94 */
95function normalizeBinaryStringToUtf8 (value) {
96  return Buffer.from(value, 'binary').toString('utf8')
97}
98
99/** @returns {URL} */
100function requestCurrentURL (request) {
101  return request.urlList[request.urlList.length - 1]
102}
103
104function requestBadPort (request) {
105  // 1. Let url be request’s current URL.
106  const url = requestCurrentURL(request)
107
108  // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
109  // then return blocked.
110  if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) {
111    return 'blocked'
112  }
113
114  // 3. Return allowed.
115  return 'allowed'
116}
117
118function isErrorLike (object) {
119  return object instanceof Error || (
120    object?.constructor?.name === 'Error' ||
121    object?.constructor?.name === 'DOMException'
122  )
123}
124
125// Check whether |statusText| is a ByteString and
126// matches the Reason-Phrase token production.
127// RFC 2616: https://tools.ietf.org/html/rfc2616
128// RFC 7230: https://tools.ietf.org/html/rfc7230
129// "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
130// https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
131function isValidReasonPhrase (statusText) {
132  for (let i = 0; i < statusText.length; ++i) {
133    const c = statusText.charCodeAt(i)
134    if (
135      !(
136        (
137          c === 0x09 || // HTAB
138          (c >= 0x20 && c <= 0x7e) || // SP / VCHAR
139          (c >= 0x80 && c <= 0xff)
140        ) // obs-text
141      )
142    ) {
143      return false
144    }
145  }
146  return true
147}
148
149/**
150 * @see https://fetch.spec.whatwg.org/#header-name
151 * @param {string} potentialValue
152 */
153const isValidHeaderName = isValidHTTPToken
154
155/**
156 * @see https://fetch.spec.whatwg.org/#header-value
157 * @param {string} potentialValue
158 */
159function isValidHeaderValue (potentialValue) {
160  // - Has no leading or trailing HTTP tab or space bytes.
161  // - Contains no 0x00 (NUL) or HTTP newline bytes.
162  return (
163    potentialValue[0] === '\t' ||
164    potentialValue[0] === ' ' ||
165    potentialValue[potentialValue.length - 1] === '\t' ||
166    potentialValue[potentialValue.length - 1] === ' ' ||
167    potentialValue.includes('\n') ||
168    potentialValue.includes('\r') ||
169    potentialValue.includes('\0')
170  ) === false
171}
172
173// https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
174function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
175  //  Given a request request and a response actualResponse, this algorithm
176  //  updates request’s referrer policy according to the Referrer-Policy
177  //  header (if any) in actualResponse.
178
179  // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
180  // from a Referrer-Policy header on actualResponse.
181
182  // 8.1 Parse a referrer policy from a Referrer-Policy header
183  // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
184  const { headersList } = actualResponse
185  // 2. Let policy be the empty string.
186  // 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.
187  // 4. Return policy.
188  const policyHeader = (headersList.get('referrer-policy', true) ?? '').split(',')
189
190  // Note: As the referrer-policy can contain multiple policies
191  // separated by comma, we need to loop through all of them
192  // and pick the first valid one.
193  // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
194  let policy = ''
195  if (policyHeader.length > 0) {
196    // The right-most policy takes precedence.
197    // The left-most policy is the fallback.
198    for (let i = policyHeader.length; i !== 0; i--) {
199      const token = policyHeader[i - 1].trim()
200      if (referrerPolicyTokens.has(token)) {
201        policy = token
202        break
203      }
204    }
205  }
206
207  // 2. If policy is not the empty string, then set request’s referrer policy to policy.
208  if (policy !== '') {
209    request.referrerPolicy = policy
210  }
211}
212
213// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check
214function crossOriginResourcePolicyCheck () {
215  // TODO
216  return 'allowed'
217}
218
219// https://fetch.spec.whatwg.org/#concept-cors-check
220function corsCheck () {
221  // TODO
222  return 'success'
223}
224
225// https://fetch.spec.whatwg.org/#concept-tao-check
226function TAOCheck () {
227  // TODO
228  return 'success'
229}
230
231function appendFetchMetadata (httpRequest) {
232  //  https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header
233  //  TODO
234
235  //  https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header
236
237  //  1. Assert: r’s url is a potentially trustworthy URL.
238  //  TODO
239
240  //  2. Let header be a Structured Header whose value is a token.
241  let header = null
242
243  //  3. Set header’s value to r’s mode.
244  header = httpRequest.mode
245
246  //  4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
247  httpRequest.headersList.set('sec-fetch-mode', header, true)
248
249  //  https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header
250  //  TODO
251
252  //  https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header
253  //  TODO
254}
255
256// https://fetch.spec.whatwg.org/#append-a-request-origin-header
257function appendRequestOriginHeader (request) {
258  // 1. Let serializedOrigin be the result of byte-serializing a request origin
259  //    with request.
260  // TODO: implement "byte-serializing a request origin"
261  let serializedOrigin = request.origin
262
263  // - "'client' is changed to an origin during fetching."
264  //   This doesn't happen in undici (in most cases) because undici, by default,
265  //   has no concept of origin.
266  // - request.origin can also be set to request.client.origin (client being
267  //   an environment settings object), which is undefined without using
268  //   setGlobalOrigin.
269  if (serializedOrigin === 'client' || serializedOrigin === undefined) {
270    return
271  }
272
273  // 2. If request’s response tainting is "cors" or request’s mode is "websocket",
274  //    then append (`Origin`, serializedOrigin) to request’s header list.
275  // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
276  if (request.responseTainting === 'cors' || request.mode === 'websocket') {
277    request.headersList.append('origin', serializedOrigin, true)
278  } else if (request.method !== 'GET' && request.method !== 'HEAD') {
279    // 1. Switch on request’s referrer policy:
280    switch (request.referrerPolicy) {
281      case 'no-referrer':
282        // Set serializedOrigin to `null`.
283        serializedOrigin = null
284        break
285      case 'no-referrer-when-downgrade':
286      case 'strict-origin':
287      case 'strict-origin-when-cross-origin':
288        // If request’s origin is a tuple origin, its scheme is "https", and
289        // request’s current URL’s scheme is not "https", then set
290        // serializedOrigin to `null`.
291        if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) {
292          serializedOrigin = null
293        }
294        break
295      case 'same-origin':
296        // If request’s origin is not same origin with request’s current URL’s
297        // origin, then set serializedOrigin to `null`.
298        if (!sameOrigin(request, requestCurrentURL(request))) {
299          serializedOrigin = null
300        }
301        break
302      default:
303        // Do nothing.
304    }
305
306    // 2. Append (`Origin`, serializedOrigin) to request’s header list.
307    request.headersList.append('origin', serializedOrigin, true)
308  }
309}
310
311// https://w3c.github.io/hr-time/#dfn-coarsen-time
312function coarsenTime (timestamp, crossOriginIsolatedCapability) {
313  // TODO
314  return timestamp
315}
316
317// https://fetch.spec.whatwg.org/#clamp-and-coarsen-connection-timing-info
318function clampAndCoarsenConnectionTimingInfo (connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
319  if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
320    return {
321      domainLookupStartTime: defaultStartTime,
322      domainLookupEndTime: defaultStartTime,
323      connectionStartTime: defaultStartTime,
324      connectionEndTime: defaultStartTime,
325      secureConnectionStartTime: defaultStartTime,
326      ALPNNegotiatedProtocol: connectionTimingInfo?.ALPNNegotiatedProtocol
327    }
328  }
329
330  return {
331    domainLookupStartTime: coarsenTime(connectionTimingInfo.domainLookupStartTime, crossOriginIsolatedCapability),
332    domainLookupEndTime: coarsenTime(connectionTimingInfo.domainLookupEndTime, crossOriginIsolatedCapability),
333    connectionStartTime: coarsenTime(connectionTimingInfo.connectionStartTime, crossOriginIsolatedCapability),
334    connectionEndTime: coarsenTime(connectionTimingInfo.connectionEndTime, crossOriginIsolatedCapability),
335    secureConnectionStartTime: coarsenTime(connectionTimingInfo.secureConnectionStartTime, crossOriginIsolatedCapability),
336    ALPNNegotiatedProtocol: connectionTimingInfo.ALPNNegotiatedProtocol
337  }
338}
339
340// https://w3c.github.io/hr-time/#dfn-coarsened-shared-current-time
341function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
342  return coarsenTime(performance.now(), crossOriginIsolatedCapability)
343}
344
345// https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
346function createOpaqueTimingInfo (timingInfo) {
347  return {
348    startTime: timingInfo.startTime ?? 0,
349    redirectStartTime: 0,
350    redirectEndTime: 0,
351    postRedirectStartTime: timingInfo.startTime ?? 0,
352    finalServiceWorkerStartTime: 0,
353    finalNetworkResponseStartTime: 0,
354    finalNetworkRequestStartTime: 0,
355    endTime: 0,
356    encodedBodySize: 0,
357    decodedBodySize: 0,
358    finalConnectionTimingInfo: null
359  }
360}
361
362// https://html.spec.whatwg.org/multipage/origin.html#policy-container
363function makePolicyContainer () {
364  // Note: the fetch spec doesn't make use of embedder policy or CSP list
365  return {
366    referrerPolicy: 'strict-origin-when-cross-origin'
367  }
368}
369
370// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
371function clonePolicyContainer (policyContainer) {
372  return {
373    referrerPolicy: policyContainer.referrerPolicy
374  }
375}
376
377// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
378function determineRequestsReferrer (request) {
379  // 1. Let policy be request's referrer policy.
380  const policy = request.referrerPolicy
381
382  // Note: policy cannot (shouldn't) be null or an empty string.
383  assert(policy)
384
385  // 2. Let environment be request’s client.
386
387  let referrerSource = null
388
389  // 3. Switch on request’s referrer:
390  if (request.referrer === 'client') {
391    // Note: node isn't a browser and doesn't implement document/iframes,
392    // so we bypass this step and replace it with our own.
393
394    const globalOrigin = getGlobalOrigin()
395
396    if (!globalOrigin || globalOrigin.origin === 'null') {
397      return 'no-referrer'
398    }
399
400    // note: we need to clone it as it's mutated
401    referrerSource = new URL(globalOrigin)
402  } else if (request.referrer instanceof URL) {
403    // Let referrerSource be request’s referrer.
404    referrerSource = request.referrer
405  }
406
407  // 4. Let request’s referrerURL be the result of stripping referrerSource for
408  //    use as a referrer.
409  let referrerURL = stripURLForReferrer(referrerSource)
410
411  // 5. Let referrerOrigin be the result of stripping referrerSource for use as
412  //    a referrer, with the origin-only flag set to true.
413  const referrerOrigin = stripURLForReferrer(referrerSource, true)
414
415  // 6. If the result of serializing referrerURL is a string whose length is
416  //    greater than 4096, set referrerURL to referrerOrigin.
417  if (referrerURL.toString().length > 4096) {
418    referrerURL = referrerOrigin
419  }
420
421  const areSameOrigin = sameOrigin(request, referrerURL)
422  const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) &&
423    !isURLPotentiallyTrustworthy(request.url)
424
425  // 8. Execute the switch statements corresponding to the value of policy:
426  switch (policy) {
427    case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true)
428    case 'unsafe-url': return referrerURL
429    case 'same-origin':
430      return areSameOrigin ? referrerOrigin : 'no-referrer'
431    case 'origin-when-cross-origin':
432      return areSameOrigin ? referrerURL : referrerOrigin
433    case 'strict-origin-when-cross-origin': {
434      const currentURL = requestCurrentURL(request)
435
436      // 1. If the origin of referrerURL and the origin of request’s current
437      //    URL are the same, then return referrerURL.
438      if (sameOrigin(referrerURL, currentURL)) {
439        return referrerURL
440      }
441
442      // 2. If referrerURL is a potentially trustworthy URL and request’s
443      //    current URL is not a potentially trustworthy URL, then return no
444      //    referrer.
445      if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
446        return 'no-referrer'
447      }
448
449      // 3. Return referrerOrigin.
450      return referrerOrigin
451    }
452    case 'strict-origin': // eslint-disable-line
453      /**
454         * 1. If referrerURL is a potentially trustworthy URL and
455         * request’s current URL is not a potentially trustworthy URL,
456         * then return no referrer.
457         * 2. Return referrerOrigin
458        */
459    case 'no-referrer-when-downgrade': // eslint-disable-line
460      /**
461       * 1. If referrerURL is a potentially trustworthy URL and
462       * request’s current URL is not a potentially trustworthy URL,
463       * then return no referrer.
464       * 2. Return referrerOrigin
465      */
466
467    default: // eslint-disable-line
468      return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin
469  }
470}
471
472/**
473 * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
474 * @param {URL} url
475 * @param {boolean|undefined} originOnly
476 */
477function stripURLForReferrer (url, originOnly) {
478  // 1. Assert: url is a URL.
479  assert(url instanceof URL)
480
481  url = new URL(url)
482
483  // 2. If url’s scheme is a local scheme, then return no referrer.
484  if (url.protocol === 'file:' || url.protocol === 'about:' || url.protocol === 'blank:') {
485    return 'no-referrer'
486  }
487
488  // 3. Set url’s username to the empty string.
489  url.username = ''
490
491  // 4. Set url’s password to the empty string.
492  url.password = ''
493
494  // 5. Set url’s fragment to null.
495  url.hash = ''
496
497  // 6. If the origin-only flag is true, then:
498  if (originOnly) {
499    // 1. Set url’s path to « the empty string ».
500    url.pathname = ''
501
502    // 2. Set url’s query to null.
503    url.search = ''
504  }
505
506  // 7. Return url.
507  return url
508}
509
510function isURLPotentiallyTrustworthy (url) {
511  if (!(url instanceof URL)) {
512    return false
513  }
514
515  // If child of about, return true
516  if (url.href === 'about:blank' || url.href === 'about:srcdoc') {
517    return true
518  }
519
520  // If scheme is data, return true
521  if (url.protocol === 'data:') return true
522
523  // If file, return true
524  if (url.protocol === 'file:') return true
525
526  return isOriginPotentiallyTrustworthy(url.origin)
527
528  function isOriginPotentiallyTrustworthy (origin) {
529    // If origin is explicitly null, return false
530    if (origin == null || origin === 'null') return false
531
532    const originAsURL = new URL(origin)
533
534    // If secure, return true
535    if (originAsURL.protocol === 'https:' || originAsURL.protocol === 'wss:') {
536      return true
537    }
538
539    // If localhost or variants, return true
540    if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) ||
541     (originAsURL.hostname === 'localhost' || originAsURL.hostname.includes('localhost.')) ||
542     (originAsURL.hostname.endsWith('.localhost'))) {
543      return true
544    }
545
546    // If any other, return false
547    return false
548  }
549}
550
551/**
552 * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
553 * @param {Uint8Array} bytes
554 * @param {string} metadataList
555 */
556function bytesMatch (bytes, metadataList) {
557  // If node is not built with OpenSSL support, we cannot check
558  // a request's integrity, so allow it by default (the spec will
559  // allow requests if an invalid hash is given, as precedence).
560  /* istanbul ignore if: only if node is built with --without-ssl */
561  if (crypto === undefined) {
562    return true
563  }
564
565  // 1. Let parsedMetadata be the result of parsing metadataList.
566  const parsedMetadata = parseMetadata(metadataList)
567
568  // 2. If parsedMetadata is no metadata, return true.
569  if (parsedMetadata === 'no metadata') {
570    return true
571  }
572
573  // 3. If response is not eligible for integrity validation, return false.
574  // TODO
575
576  // 4. If parsedMetadata is the empty set, return true.
577  if (parsedMetadata.length === 0) {
578    return true
579  }
580
581  // 5. Let metadata be the result of getting the strongest
582  //    metadata from parsedMetadata.
583  const strongest = getStrongestMetadata(parsedMetadata)
584  const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest)
585
586  // 6. For each item in metadata:
587  for (const item of metadata) {
588    // 1. Let algorithm be the alg component of item.
589    const algorithm = item.algo
590
591    // 2. Let expectedValue be the val component of item.
592    const expectedValue = item.hash
593
594    // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e
595    // "be liberal with padding". This is annoying, and it's not even in the spec.
596
597    // 3. Let actualValue be the result of applying algorithm to bytes.
598    let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
599
600    if (actualValue[actualValue.length - 1] === '=') {
601      if (actualValue[actualValue.length - 2] === '=') {
602        actualValue = actualValue.slice(0, -2)
603      } else {
604        actualValue = actualValue.slice(0, -1)
605      }
606    }
607
608    // 4. If actualValue is a case-sensitive match for expectedValue,
609    //    return true.
610    if (compareBase64Mixed(actualValue, expectedValue)) {
611      return true
612    }
613  }
614
615  // 7. Return false.
616  return false
617}
618
619// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
620// https://www.w3.org/TR/CSP2/#source-list-syntax
621// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
622const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-((?<hash>[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i
623
624/**
625 * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
626 * @param {string} metadata
627 */
628function parseMetadata (metadata) {
629  // 1. Let result be the empty set.
630  /** @type {{ algo: string, hash: string }[]} */
631  const result = []
632
633  // 2. Let empty be equal to true.
634  let empty = true
635
636  // 3. For each token returned by splitting metadata on spaces:
637  for (const token of metadata.split(' ')) {
638    // 1. Set empty to false.
639    empty = false
640
641    // 2. Parse token as a hash-with-options.
642    const parsedToken = parseHashWithOptions.exec(token)
643
644    // 3. If token does not parse, continue to the next token.
645    if (
646      parsedToken === null ||
647      parsedToken.groups === undefined ||
648      parsedToken.groups.algo === undefined
649    ) {
650      // Note: Chromium blocks the request at this point, but Firefox
651      // gives a warning that an invalid integrity was given. The
652      // correct behavior is to ignore these, and subsequently not
653      // check the integrity of the resource.
654      continue
655    }
656
657    // 4. Let algorithm be the hash-algo component of token.
658    const algorithm = parsedToken.groups.algo.toLowerCase()
659
660    // 5. If algorithm is a hash function recognized by the user
661    //    agent, add the parsed token to result.
662    if (supportedHashes.includes(algorithm)) {
663      result.push(parsedToken.groups)
664    }
665  }
666
667  // 4. Return no metadata if empty is true, otherwise return result.
668  if (empty === true) {
669    return 'no metadata'
670  }
671
672  return result
673}
674
675/**
676 * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList
677 */
678function getStrongestMetadata (metadataList) {
679  // Let algorithm be the algo component of the first item in metadataList.
680  // Can be sha256
681  let algorithm = metadataList[0].algo
682  // If the algorithm is sha512, then it is the strongest
683  // and we can return immediately
684  if (algorithm[3] === '5') {
685    return algorithm
686  }
687
688  for (let i = 1; i < metadataList.length; ++i) {
689    const metadata = metadataList[i]
690    // If the algorithm is sha512, then it is the strongest
691    // and we can break the loop immediately
692    if (metadata.algo[3] === '5') {
693      algorithm = 'sha512'
694      break
695    // If the algorithm is sha384, then a potential sha256 or sha384 is ignored
696    } else if (algorithm[3] === '3') {
697      continue
698    // algorithm is sha256, check if algorithm is sha384 and if so, set it as
699    // the strongest
700    } else if (metadata.algo[3] === '3') {
701      algorithm = 'sha384'
702    }
703  }
704  return algorithm
705}
706
707function filterMetadataListByAlgorithm (metadataList, algorithm) {
708  if (metadataList.length === 1) {
709    return metadataList
710  }
711
712  let pos = 0
713  for (let i = 0; i < metadataList.length; ++i) {
714    if (metadataList[i].algo === algorithm) {
715      metadataList[pos++] = metadataList[i]
716    }
717  }
718
719  metadataList.length = pos
720
721  return metadataList
722}
723
724/**
725 * Compares two base64 strings, allowing for base64url
726 * in the second string.
727 *
728* @param {string} actualValue always base64
729 * @param {string} expectedValue base64 or base64url
730 * @returns {boolean}
731 */
732function compareBase64Mixed (actualValue, expectedValue) {
733  if (actualValue.length !== expectedValue.length) {
734    return false
735  }
736  for (let i = 0; i < actualValue.length; ++i) {
737    if (actualValue[i] !== expectedValue[i]) {
738      if (
739        (actualValue[i] === '+' && expectedValue[i] === '-') ||
740        (actualValue[i] === '/' && expectedValue[i] === '_')
741      ) {
742        continue
743      }
744      return false
745    }
746  }
747
748  return true
749}
750
751// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
752function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
753  // TODO
754}
755
756/**
757 * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
758 * @param {URL} A
759 * @param {URL} B
760 */
761function sameOrigin (A, B) {
762  // 1. If A and B are the same opaque origin, then return true.
763  if (A.origin === B.origin && A.origin === 'null') {
764    return true
765  }
766
767  // 2. If A and B are both tuple origins and their schemes,
768  //    hosts, and port are identical, then return true.
769  if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
770    return true
771  }
772
773  // 3. Return false.
774  return false
775}
776
777function createDeferredPromise () {
778  let res
779  let rej
780  const promise = new Promise((resolve, reject) => {
781    res = resolve
782    rej = reject
783  })
784
785  return { promise, resolve: res, reject: rej }
786}
787
788function isAborted (fetchParams) {
789  return fetchParams.controller.state === 'aborted'
790}
791
792function isCancelled (fetchParams) {
793  return fetchParams.controller.state === 'aborted' ||
794    fetchParams.controller.state === 'terminated'
795}
796
797/**
798 * @see https://fetch.spec.whatwg.org/#concept-method-normalize
799 * @param {string} method
800 */
801function normalizeMethod (method) {
802  return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
803}
804
805// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
806function serializeJavascriptValueToJSONString (value) {
807  // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
808  const result = JSON.stringify(value)
809
810  // 2. If result is undefined, then throw a TypeError.
811  if (result === undefined) {
812    throw new TypeError('Value is not JSON serializable')
813  }
814
815  // 3. Assert: result is a string.
816  assert(typeof result === 'string')
817
818  // 4. Return result.
819  return result
820}
821
822// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
823const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
824
825/**
826 * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
827 * @param {string} name name of the instance
828 * @param {symbol} kInternalIterator
829 * @param {string | number} [keyIndex]
830 * @param {string | number} [valueIndex]
831 */
832function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) {
833  class FastIterableIterator {
834    /** @type {any} */
835    #target
836    /** @type {'key' | 'value' | 'key+value'} */
837    #kind
838    /** @type {number} */
839    #index
840
841    /**
842     * @see https://webidl.spec.whatwg.org/#dfn-default-iterator-object
843     * @param {unknown} target
844     * @param {'key' | 'value' | 'key+value'} kind
845     */
846    constructor (target, kind) {
847      this.#target = target
848      this.#kind = kind
849      this.#index = 0
850    }
851
852    next () {
853      // 1. Let interface be the interface for which the iterator prototype object exists.
854      // 2. Let thisValue be the this value.
855      // 3. Let object be ? ToObject(thisValue).
856      // 4. If object is a platform object, then perform a security
857      //    check, passing:
858      // 5. If object is not a default iterator object for interface,
859      //    then throw a TypeError.
860      if (typeof this !== 'object' || this === null || !(#target in this)) {
861        throw new TypeError(
862          `'next' called on an object that does not implement interface ${name} Iterator.`
863        )
864      }
865
866      // 6. Let index be object’s index.
867      // 7. Let kind be object’s kind.
868      // 8. Let values be object’s target's value pairs to iterate over.
869      const index = this.#index
870      const values = this.#target[kInternalIterator]
871
872      // 9. Let len be the length of values.
873      const len = values.length
874
875      // 10. If index is greater than or equal to len, then return
876      //     CreateIterResultObject(undefined, true).
877      if (index >= len) {
878        return {
879          value: undefined,
880          done: true
881        }
882      }
883
884      // 11. Let pair be the entry in values at index index.
885      const { [keyIndex]: key, [valueIndex]: value } = values[index]
886
887      // 12. Set object’s index to index + 1.
888      this.#index = index + 1
889
890      // 13. Return the iterator result for pair and kind.
891
892      // https://webidl.spec.whatwg.org/#iterator-result
893
894      // 1. Let result be a value determined by the value of kind:
895      let result
896      switch (this.#kind) {
897        case 'key':
898          // 1. Let idlKey be pair’s key.
899          // 2. Let key be the result of converting idlKey to an
900          //    ECMAScript value.
901          // 3. result is key.
902          result = key
903          break
904        case 'value':
905          // 1. Let idlValue be pair’s value.
906          // 2. Let value be the result of converting idlValue to
907          //    an ECMAScript value.
908          // 3. result is value.
909          result = value
910          break
911        case 'key+value':
912          // 1. Let idlKey be pair’s key.
913          // 2. Let idlValue be pair’s value.
914          // 3. Let key be the result of converting idlKey to an
915          //    ECMAScript value.
916          // 4. Let value be the result of converting idlValue to
917          //    an ECMAScript value.
918          // 5. Let array be ! ArrayCreate(2).
919          // 6. Call ! CreateDataProperty(array, "0", key).
920          // 7. Call ! CreateDataProperty(array, "1", value).
921          // 8. result is array.
922          result = [key, value]
923          break
924      }
925
926      // 2. Return CreateIterResultObject(result, false).
927      return {
928        value: result,
929        done: false
930      }
931    }
932  }
933
934  // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
935  // @ts-ignore
936  delete FastIterableIterator.prototype.constructor
937
938  Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype)
939
940  Object.defineProperties(FastIterableIterator.prototype, {
941    [Symbol.toStringTag]: {
942      writable: false,
943      enumerable: false,
944      configurable: true,
945      value: `${name} Iterator`
946    },
947    next: { writable: true, enumerable: true, configurable: true }
948  })
949
950  /**
951   * @param {unknown} target
952   * @param {'key' | 'value' | 'key+value'} kind
953   * @returns {IterableIterator<any>}
954   */
955  return function (target, kind) {
956    return new FastIterableIterator(target, kind)
957  }
958}
959
960/**
961 * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
962 * @param {string} name name of the instance
963 * @param {any} object class
964 * @param {symbol} kInternalIterator
965 * @param {string | number} [keyIndex]
966 * @param {string | number} [valueIndex]
967 */
968function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) {
969  const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex)
970
971  const properties = {
972    keys: {
973      writable: true,
974      enumerable: true,
975      configurable: true,
976      value: function keys () {
977        webidl.brandCheck(this, object)
978        return makeIterator(this, 'key')
979      }
980    },
981    values: {
982      writable: true,
983      enumerable: true,
984      configurable: true,
985      value: function values () {
986        webidl.brandCheck(this, object)
987        return makeIterator(this, 'value')
988      }
989    },
990    entries: {
991      writable: true,
992      enumerable: true,
993      configurable: true,
994      value: function entries () {
995        webidl.brandCheck(this, object)
996        return makeIterator(this, 'key+value')
997      }
998    },
999    forEach: {
1000      writable: true,
1001      enumerable: true,
1002      configurable: true,
1003      value: function forEach (callbackfn, thisArg = globalThis) {
1004        webidl.brandCheck(this, object)
1005        webidl.argumentLengthCheck(arguments, 1, `${name}.forEach`)
1006        if (typeof callbackfn !== 'function') {
1007          throw new TypeError(
1008            `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.`
1009          )
1010        }
1011        for (const { 0: key, 1: value } of makeIterator(this, 'key+value')) {
1012          callbackfn.call(thisArg, value, key, this)
1013        }
1014      }
1015    }
1016  }
1017
1018  return Object.defineProperties(object.prototype, {
1019    ...properties,
1020    [Symbol.iterator]: {
1021      writable: true,
1022      enumerable: false,
1023      configurable: true,
1024      value: properties.entries.value
1025    }
1026  })
1027}
1028
1029/**
1030 * @see https://fetch.spec.whatwg.org/#body-fully-read
1031 */
1032async function fullyReadBody (body, processBody, processBodyError) {
1033  // 1. If taskDestination is null, then set taskDestination to
1034  //    the result of starting a new parallel queue.
1035
1036  // 2. Let successSteps given a byte sequence bytes be to queue a
1037  //    fetch task to run processBody given bytes, with taskDestination.
1038  const successSteps = processBody
1039
1040  // 3. Let errorSteps be to queue a fetch task to run processBodyError,
1041  //    with taskDestination.
1042  const errorSteps = processBodyError
1043
1044  // 4. Let reader be the result of getting a reader for body’s stream.
1045  //    If that threw an exception, then run errorSteps with that
1046  //    exception and return.
1047  let reader
1048
1049  try {
1050    reader = body.stream.getReader()
1051  } catch (e) {
1052    errorSteps(e)
1053    return
1054  }
1055
1056  // 5. Read all bytes from reader, given successSteps and errorSteps.
1057  try {
1058    successSteps(await readAllBytes(reader))
1059  } catch (e) {
1060    errorSteps(e)
1061  }
1062}
1063
1064function isReadableStreamLike (stream) {
1065  return stream instanceof ReadableStream || (
1066    stream[Symbol.toStringTag] === 'ReadableStream' &&
1067    typeof stream.tee === 'function'
1068  )
1069}
1070
1071/**
1072 * @param {ReadableStreamController<Uint8Array>} controller
1073 */
1074function readableStreamClose (controller) {
1075  try {
1076    controller.close()
1077    controller.byobRequest?.respond(0)
1078  } catch (err) {
1079    // TODO: add comment explaining why this error occurs.
1080    if (!err.message.includes('Controller is already closed') && !err.message.includes('ReadableStream is already closed')) {
1081      throw err
1082    }
1083  }
1084}
1085
1086const invalidIsomorphicEncodeValueRegex = /[^\x00-\xFF]/ // eslint-disable-line
1087
1088/**
1089 * @see https://infra.spec.whatwg.org/#isomorphic-encode
1090 * @param {string} input
1091 */
1092function isomorphicEncode (input) {
1093  // 1. Assert: input contains no code points greater than U+00FF.
1094  assert(!invalidIsomorphicEncodeValueRegex.test(input))
1095
1096  // 2. Return a byte sequence whose length is equal to input’s code
1097  //    point length and whose bytes have the same values as the
1098  //    values of input’s code points, in the same order
1099  return input
1100}
1101
1102/**
1103 * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
1104 * @see https://streams.spec.whatwg.org/#read-loop
1105 * @param {ReadableStreamDefaultReader} reader
1106 */
1107async function readAllBytes (reader) {
1108  const bytes = []
1109  let byteLength = 0
1110
1111  while (true) {
1112    const { done, value: chunk } = await reader.read()
1113
1114    if (done) {
1115      // 1. Call successSteps with bytes.
1116      return Buffer.concat(bytes, byteLength)
1117    }
1118
1119    // 1. If chunk is not a Uint8Array object, call failureSteps
1120    //    with a TypeError and abort these steps.
1121    if (!isUint8Array(chunk)) {
1122      throw new TypeError('Received non-Uint8Array chunk')
1123    }
1124
1125    // 2. Append the bytes represented by chunk to bytes.
1126    bytes.push(chunk)
1127    byteLength += chunk.length
1128
1129    // 3. Read-loop given reader, bytes, successSteps, and failureSteps.
1130  }
1131}
1132
1133/**
1134 * @see https://fetch.spec.whatwg.org/#is-local
1135 * @param {URL} url
1136 */
1137function urlIsLocal (url) {
1138  assert('protocol' in url) // ensure it's a url object
1139
1140  const protocol = url.protocol
1141
1142  return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:'
1143}
1144
1145/**
1146 * @param {string|URL} url
1147 * @returns {boolean}
1148 */
1149function urlHasHttpsScheme (url) {
1150  return (
1151    (
1152      typeof url === 'string' &&
1153      url[5] === ':' &&
1154      url[0] === 'h' &&
1155      url[1] === 't' &&
1156      url[2] === 't' &&
1157      url[3] === 'p' &&
1158      url[4] === 's'
1159    ) ||
1160    url.protocol === 'https:'
1161  )
1162}
1163
1164/**
1165 * @see https://fetch.spec.whatwg.org/#http-scheme
1166 * @param {URL} url
1167 */
1168function urlIsHttpHttpsScheme (url) {
1169  assert('protocol' in url) // ensure it's a url object
1170
1171  const protocol = url.protocol
1172
1173  return protocol === 'http:' || protocol === 'https:'
1174}
1175
1176/**
1177 * @see https://fetch.spec.whatwg.org/#simple-range-header-value
1178 * @param {string} value
1179 * @param {boolean} allowWhitespace
1180 */
1181function simpleRangeHeaderValue (value, allowWhitespace) {
1182  // 1. Let data be the isomorphic decoding of value.
1183  // Note: isomorphic decoding takes a sequence of bytes (ie. a Uint8Array) and turns it into a string,
1184  // nothing more. We obviously don't need to do that if value is a string already.
1185  const data = value
1186
1187  // 2. If data does not start with "bytes", then return failure.
1188  if (!data.startsWith('bytes')) {
1189    return 'failure'
1190  }
1191
1192  // 3. Let position be a position variable for data, initially pointing at the 5th code point of data.
1193  const position = { position: 5 }
1194
1195  // 4. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
1196  //    from data given position.
1197  if (allowWhitespace) {
1198    collectASequenceOfCodePoints(
1199      (char) => char === '\t' || char === ' ',
1200      data,

Showing the first 1,200 of 1633 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai