| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -41,7 +41,7 @@ module.exports = { | |||
| 41 | 41 | kClient: Symbol('client'), | |
| 42 | 42 | kParser: Symbol('parser'), | |
| 43 | 43 | kOnDestroyed: Symbol('destroy callbacks'), | |
| 44 | - kPipelining: Symbol('pipelinig'), | ||
| 44 | + kPipelining: Symbol('pipelining'), | ||
| 45 | 45 | kSocket: Symbol('socket'), | |
| 46 | 46 | kHostHeader: Symbol('host header'), | |
| 47 | 47 | kConnector: Symbol('connector'), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,38 +48,38 @@ function parseURL (url) { | |||
| 48 | 48 | url = new URL(url) | |
| 49 | 49 | ||
| 50 | 50 | if (!/^https?:/.test(url.origin || url.protocol)) { | |
| 51 | - throw new InvalidArgumentError('invalid protocol') | ||
| 51 | + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') | ||
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | 54 | return url | |
| 55 | 55 | } | |
| 56 | 56 | ||
| 57 | 57 | if (!url || typeof url !== 'object') { | |
| 58 | - throw new InvalidArgumentError('invalid url') | ||
| 58 | + throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.') | ||
| 59 | 59 | } | |
| 60 | 60 | ||
| 61 | 61 | if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) { | |
| 62 | - throw new InvalidArgumentError('invalid port') | ||
| 62 | + throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.') | ||
| 63 | 63 | } | |
| 64 | 64 | ||
| 65 | 65 | if (url.path != null && typeof url.path !== 'string') { | |
| 66 | - throw new InvalidArgumentError('invalid path') | ||
| 66 | + throw new InvalidArgumentError('Invalid URL path: the path must be a string or null/undefined.') | ||
| 67 | 67 | } | |
| 68 | 68 | ||
| 69 | 69 | if (url.pathname != null && typeof url.pathname !== 'string') { | |
| 70 | - throw new InvalidArgumentError('invalid pathname') | ||
| 70 | + throw new InvalidArgumentError('Invalid URL pathname: the pathname must be a string or null/undefined.') | ||
| 71 | 71 | } | |
| 72 | 72 | ||
| 73 | 73 | if (url.hostname != null && typeof url.hostname !== 'string') { | |
| 74 | - throw new InvalidArgumentError('invalid hostname') | ||
| 74 | + throw new InvalidArgumentError('Invalid URL hostname: the hostname must be a string or null/undefined.') | ||
| 75 | 75 | } | |
| 76 | 76 | ||
| 77 | 77 | if (url.origin != null && typeof url.origin !== 'string') { | |
| 78 | - throw new InvalidArgumentError('invalid origin') | ||
| 78 | + throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.') | ||
| 79 | 79 | } | |
| 80 | 80 | ||
| 81 | 81 | if (!/^https?:/.test(url.origin || url.protocol)) { | |
| 82 | - throw new InvalidArgumentError('invalid protocol') | ||
| 82 | + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') | ||
| 83 | 83 | } | |
| 84 | 84 | ||
| 85 | 85 | if (!(url instanceof URL)) { | |
@@ -409,6 +409,21 @@ function throwIfAborted (signal) { | |||
| 409 | 409 | } | |
| 410 | 410 | } | |
| 411 | 411 | ||
| 412 | + const hasToWellFormed = !!String.prototype.toWellFormed | ||
| 413 | + | ||
| 414 | + /** | ||
| 415 | + * @param {string} val | ||
| 416 | + */ | ||
| 417 | + function toUSVString (val) { | ||
| 418 | + if (hasToWellFormed) { | ||
| 419 | + return `${val}`.toWellFormed() | ||
| 420 | + } else if (nodeUtil.toUSVString) { | ||
| 421 | + return nodeUtil.toUSVString(val) | ||
| 422 | + } | ||
| 423 | + | ||
| 424 | + return `${val}` | ||
| 425 | + } | ||
| 426 | + | ||
| 412 | 427 | const kEnumerableProperty = Object.create(null) | |
| 413 | 428 | kEnumerableProperty.enumerable = true | |
| 414 | 429 | ||
@@ -418,7 +433,7 @@ module.exports = { | |||
| 418 | 433 | isDisturbed, | |
| 419 | 434 | isErrored, | |
| 420 | 435 | isReadable, | |
| 421 | - toUSVString: nodeUtil.toUSVString || ((val) => `${val}`), | ||
| 436 | + toUSVString, | ||
| 422 | 437 | isReadableAborted, | |
| 423 | 438 | isBlobLike, | |
| 424 | 439 | parseOrigin, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,11 +48,17 @@ const requestCache = [ | |||
| 48 | 48 | 'only-if-cached' | |
| 49 | 49 | ] | |
| 50 | 50 | ||
| 51 | + // https://fetch.spec.whatwg.org/#request-body-header-name | ||
| 51 | 52 | const requestBodyHeader = [ | |
| 52 | 53 | 'content-encoding', | |
| 53 | 54 | 'content-language', | |
| 54 | 55 | 'content-location', | |
| 55 | - 'content-type' | ||
| 56 | + 'content-type', | ||
| 57 | + // See https://github.com/nodejs/undici/issues/2021 | ||
| 58 | + // 'Content-Length' is a forbidden header name, which is typically | ||
| 59 | + // removed in the Headers implementation. However, undici doesn't | ||
| 60 | + // filter out headers, so we add it here. | ||
| 61 | + 'content-length' | ||
| 56 | 62 | ] | |
| 57 | 63 | ||
| 58 | 64 | // https://fetch.spec.whatwg.org/#enumdef-requestduplex | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -61,14 +61,7 @@ class FormData { | |||
| 61 | 61 | ||
| 62 | 62 | // The delete(name) method steps are to remove all entries whose name | |
| 63 | 63 | // is name from this’s entry list. | |
| 64 | - const next = [] | ||
| 65 | - for (const entry of this[kState]) { | ||
| 66 | - if (entry.name !== name) { | ||
| 67 | - next.push(entry) | ||
| 68 | - } | ||
| 69 | - } | ||
| 70 | - | ||
| 71 | - this[kState] = next | ||
| 64 | + this[kState] = this[kState].filter(entry => entry.name !== name) | ||
| 72 | 65 | } | |
| 73 | 66 | ||
| 74 | 67 | get (name) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,7 +37,10 @@ const { | |||
| 37 | 37 | isErrorLike, | |
| 38 | 38 | fullyReadBody, | |
| 39 | 39 | readableStreamClose, | |
| 40 | - isomorphicEncode | ||
| 40 | + isomorphicEncode, | ||
| 41 | + urlIsLocal, | ||
| 42 | + urlIsHttpHttpsScheme, | ||
| 43 | + urlHasHttpsScheme | ||
| 41 | 44 | } = require('./util') | |
| 42 | 45 | const { kState, kHeaders, kGuard, kRealm, kHeadersCaseInsensitive } = require('./symbols') | |
| 43 | 46 | const assert = require('assert') | |
@@ -272,7 +275,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') { | |||
| 272 | 275 | let cacheState = response.cacheState | |
| 273 | 276 | ||
| 274 | 277 | // 6. If originalURL’s scheme is not an HTTP(S) scheme, then return. | |
| 275 | - if (!/^https?:/.test(originalURL.protocol)) { | ||
| 278 | + if (!urlIsHttpHttpsScheme(originalURL)) { | ||
| 276 | 279 | return | |
| 277 | 280 | } | |
| 278 | 281 | ||
@@ -530,10 +533,7 @@ async function mainFetch (fetchParams, recursive = false) { | |||
| 530 | 533 | ||
| 531 | 534 | // 3. If request’s local-URLs-only flag is set and request’s current URL is | |
| 532 | 535 | // not local, then set response to a network error. | |
| 533 | - if ( | ||
| 534 | - request.localURLsOnly && | ||
| 535 | - !/^(about|blob|data):/.test(requestCurrentURL(request).protocol) | ||
| 536 | - ) { | ||
| 536 | + if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) { | ||
| 537 | 537 | response = makeNetworkError('local URLs only') | |
| 538 | 538 | } | |
| 539 | 539 | ||
@@ -623,7 +623,7 @@ async function mainFetch (fetchParams, recursive = false) { | |||
| 623 | 623 | } | |
| 624 | 624 | ||
| 625 | 625 | // request’s current URL’s scheme is not an HTTP(S) scheme | |
| 626 | - if (!/^https?:/.test(requestCurrentURL(request).protocol)) { | ||
| 626 | + if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) { | ||
| 627 | 627 | // Return a network error. | |
| 628 | 628 | return makeNetworkError('URL scheme must be a HTTP(S) scheme') | |
| 629 | 629 | } | |
@@ -1130,7 +1130,7 @@ async function httpRedirectFetch (fetchParams, response) { | |||
| 1130 | 1130 | ||
| 1131 | 1131 | // 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network | |
| 1132 | 1132 | // error. | |
| 1133 | - if (!/^https?:/.test(locationURL.protocol)) { | ||
| 1133 | + if (!urlIsHttpHttpsScheme(locationURL)) { | ||
| 1134 | 1134 | return makeNetworkError('URL scheme must be a HTTP(S) scheme') | |
| 1135 | 1135 | } | |
| 1136 | 1136 | ||
@@ -1205,7 +1205,7 @@ async function httpRedirectFetch (fetchParams, response) { | |||
| 1205 | 1205 | // 14. If request’s body is non-null, then set request’s body to the first return | |
| 1206 | 1206 | // value of safely extracting request’s body’s source. | |
| 1207 | 1207 | if (request.body != null) { | |
| 1208 | - assert(request.body.source) | ||
| 1208 | + assert(request.body.source != null) | ||
| 1209 | 1209 | request.body = safelyExtractBody(request.body.source)[0] | |
| 1210 | 1210 | } | |
| 1211 | 1211 | ||
@@ -1399,7 +1399,7 @@ async function httpNetworkOrCacheFetch ( | |||
| 1399 | 1399 | // header if httpRequest’s header list contains that header’s name. | |
| 1400 | 1400 | // TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129 | |
| 1401 | 1401 | if (!httpRequest.headersList.contains('accept-encoding')) { | |
| 1402 | - if (/^https:/.test(requestCurrentURL(httpRequest).protocol)) { | ||
| 1402 | + if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) { | ||
| 1403 | 1403 | httpRequest.headersList.append('accept-encoding', 'br, gzip, deflate') | |
| 1404 | 1404 | } else { | |
| 1405 | 1405 | httpRequest.headersList.append('accept-encoding', 'gzip, deflate') | |
@@ -1845,6 +1845,7 @@ async function httpNetworkFetch ( | |||
| 1845 | 1845 | // 4. Set bytes to the result of handling content codings given | |
| 1846 | 1846 | // codings and bytes. | |
| 1847 | 1847 | let bytes | |
| 1848 | + let isFailure | ||
| 1848 | 1849 | try { | |
| 1849 | 1850 | const { done, value } = await fetchParams.controller.next() | |
| 1850 | 1851 | ||
@@ -1859,6 +1860,10 @@ async function httpNetworkFetch ( | |||
| 1859 | 1860 | bytes = undefined | |
| 1860 | 1861 | } else { | |
| 1861 | 1862 | bytes = err | |
| 1863 | + | ||
| 1864 | + // err may be propagated from the result of calling readablestream.cancel, | ||
| 1865 | + // which might not be an error. https://github.com/nodejs/undici/issues/2009 | ||
| 1866 | + isFailure = true | ||
| 1862 | 1867 | } | |
| 1863 | 1868 | } | |
| 1864 | 1869 | ||
@@ -1878,7 +1883,7 @@ async function httpNetworkFetch ( | |||
| 1878 | 1883 | timingInfo.decodedBodySize += bytes?.byteLength ?? 0 | |
| 1879 | 1884 | ||
| 1880 | 1885 | // 6. If bytes is failure, then terminate fetchParams’s controller. | |
| 1881 | - if (isErrorLike(bytes)) { | ||
| 1886 | + if (isFailure) { | ||
| 1882 | 1887 | fetchParams.controller.terminate(bytes) | |
| 1883 | 1888 | return | |
| 1884 | 1889 | } | |
@@ -1979,7 +1984,9 @@ async function httpNetworkFetch ( | |||
| 1979 | 1984 | const val = headersList[n + 1].toString('latin1') | |
| 1980 | 1985 | ||
| 1981 | 1986 | if (key.toLowerCase() === 'content-encoding') { | |
| 1982 | - codings = val.split(',').map((x) => x.trim()) | ||
| 1987 | + // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 | ||
| 1988 | + // "All content-coding values are case-insensitive..." | ||
| 1989 | + codings = val.toLowerCase().split(',').map((x) => x.trim()) | ||
| 1983 | 1990 | } else if (key.toLowerCase() === 'location') { | |
| 1984 | 1991 | location = val | |
| 1985 | 1992 | } | |
@@ -1998,9 +2005,10 @@ async function httpNetworkFetch ( | |||
| 1998 | 2005 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding | |
| 1999 | 2006 | if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) { | |
| 2000 | 2007 | for (const coding of codings) { | |
| 2001 | - if (/(x-)?gzip/.test(coding)) { | ||
| 2008 | + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2 | ||
| 2009 | + if (coding === 'x-gzip' || coding === 'gzip') { | ||
| 2002 | 2010 | decoders.push(zlib.createGunzip()) | |
| 2003 | - } else if (/(x-)?deflate/.test(coding)) { | ||
| 2011 | + } else if (coding === 'deflate') { | ||
| 2004 | 2012 | decoders.push(zlib.createInflate()) | |
| 2005 | 2013 | } else if (coding === 'br') { | |
| 2006 | 2014 | decoders.push(zlib.createBrotliDecompress()) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -34,6 +34,7 @@ const { setMaxListeners, getEventListeners, defaultMaxListeners } = require('eve | |||
| 34 | 34 | let TransformStream = globalThis.TransformStream | |
| 35 | 35 | ||
| 36 | 36 | const kInit = Symbol('init') | |
| 37 | + const kAbortController = Symbol('abortController') | ||
| 37 | 38 | ||
| 38 | 39 | const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { | |
| 39 | 40 | signal.removeEventListener('abort', abort) | |
@@ -128,12 +129,12 @@ class Request { | |||
| 128 | 129 | } | |
| 129 | 130 | ||
| 130 | 131 | // 10. If init["window"] exists and is non-null, then throw a TypeError. | |
| 131 | - if (init.window !== undefined && init.window != null) { | ||
| 132 | + if (init.window != null) { | ||
| 132 | 133 | throw new TypeError(`'window' option '${window}' must be null`) | |
| 133 | 134 | } | |
| 134 | 135 | ||
| 135 | 136 | // 11. If init["window"] exists, then set window to "no-window". | |
| 136 | - if (init.window !== undefined) { | ||
| 137 | + if ('window' in init) { | ||
| 137 | 138 | window = 'no-window' | |
| 138 | 139 | } | |
| 139 | 140 | ||
@@ -354,20 +355,30 @@ class Request { | |||
| 354 | 355 | if (signal.aborted) { | |
| 355 | 356 | ac.abort(signal.reason) | |
| 356 | 357 | } else { | |
| 358 | + // Keep a strong ref to ac while request object | ||
| 359 | + // is alive. This is needed to prevent AbortController | ||
| 360 | + // from being prematurely garbage collected. | ||
| 361 | + // See, https://github.com/nodejs/undici/issues/1926. | ||
| 362 | + this[kAbortController] = ac | ||
| 363 | + | ||
| 364 | + const acRef = new WeakRef(ac) | ||
| 357 | 365 | const abort = function () { | |
| 358 | - ac.abort(this.reason) | ||
| 366 | + const ac = acRef.deref() | ||
| 367 | + if (ac !== undefined) { | ||
| 368 | + ac.abort(this.reason) | ||
| 369 | + } | ||
| 359 | 370 | } | |
| 360 | 371 | ||
| 361 | 372 | // Third-party AbortControllers may not work with these. | |
| 362 | - // See https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619 | ||
| 373 | + // See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619. | ||
| 363 | 374 | try { | |
| 364 | 375 | if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) { | |
| 365 | 376 | setMaxListeners(100, signal) | |
| 366 | 377 | } | |
| 367 | 378 | } catch {} | |
| 368 | 379 | ||
| 369 | 380 | signal.addEventListener('abort', abort, { once: true }) | |
| 370 | - requestFinalizer.register(this, { signal, abort }) | ||
| 381 | + requestFinalizer.register(ac, { signal, abort }) | ||
| 371 | 382 | } | |
| 372 | 383 | } | |
| 373 | 384 | ||
@@ -427,7 +438,7 @@ class Request { | |||
| 427 | 438 | // non-null, and request’s method is `GET` or `HEAD`, then throw a | |
| 428 | 439 | // TypeError. | |
| 429 | 440 | if ( | |
| 430 | - ((init.body !== undefined && init.body != null) || inputBody != null) && | ||
| 441 | + (init.body != null || inputBody != null) && | ||
| 431 | 442 | (request.method === 'GET' || request.method === 'HEAD') | |
| 432 | 443 | ) { | |
| 433 | 444 | throw new TypeError('Request with GET/HEAD method cannot have body.') | |
@@ -437,7 +448,7 @@ class Request { | |||
| 437 | 448 | let initBody = null | |
| 438 | 449 | ||
| 439 | 450 | // 36. If init["body"] exists and is non-null, then: | |
| 440 | - if (init.body !== undefined && init.body != null) { | ||
| 451 | + if (init.body != null) { | ||
| 441 | 452 | // 1. Let Content-Type be null. | |
| 442 | 453 | // 2. Set initBody and Content-Type to the result of extracting | |
| 443 | 454 | // init["body"], with keepalive set to request’s keepalive. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -348,9 +348,7 @@ function makeNetworkError (reason) { | |||
| 348 | 348 | status: 0, | |
| 349 | 349 | error: isError | |
| 350 | 350 | ? reason | |
| 351 | - : new Error(reason ? String(reason) : reason, { | ||
| 352 | - cause: isError ? reason : undefined | ||
| 353 | - }), | ||
| 351 | + : new Error(reason ? String(reason) : reason), | ||
| 354 | 352 | aborted: reason && reason.name === 'AbortError' | |
| 355 | 353 | }) | |
| 356 | 354 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -64,7 +64,7 @@ function requestBadPort (request) { | |||
| 64 | 64 | ||
| 65 | 65 | // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port, | |
| 66 | 66 | // then return blocked. | |
| 67 | - if (/^https?:/.test(url.protocol) && badPorts.includes(url.port)) { | ||
| 67 | + if (urlIsHttpHttpsScheme(url) && badPorts.includes(url.port)) { | ||
| 68 | 68 | return 'blocked' | |
| 69 | 69 | } | |
| 70 | 70 | ||
@@ -285,7 +285,7 @@ function appendRequestOriginHeader (request) { | |||
| 285 | 285 | case 'strict-origin': | |
| 286 | 286 | case 'strict-origin-when-cross-origin': | |
| 287 | 287 | // 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`. | |
| 288 | - if (/^https:/.test(request.origin) && !/^https:/.test(requestCurrentURL(request))) { | ||
| 288 | + if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) { | ||
| 289 | 289 | serializedOrigin = null | |
| 290 | 290 | } | |
| 291 | 291 | break | |
@@ -944,6 +944,41 @@ async function readAllBytes (reader, successSteps, failureSteps) { | |||
| 944 | 944 | } | |
| 945 | 945 | } | |
| 946 | 946 | ||
| 947 | + /** | ||
| 948 | + * @see https://fetch.spec.whatwg.org/#is-local | ||
| 949 | + * @param {URL} url | ||
| 950 | + */ | ||
| 951 | + function urlIsLocal (url) { | ||
| 952 | + assert('protocol' in url) // ensure it's a url object | ||
| 953 | + | ||
| 954 | + const protocol = url.protocol | ||
| 955 | + | ||
| 956 | + return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:' | ||
| 957 | + } | ||
| 958 | + | ||
| 959 | + /** | ||
| 960 | + * @param {string|URL} url | ||
| 961 | + */ | ||
| 962 | + function urlHasHttpsScheme (url) { | ||
| 963 | + if (typeof url === 'string') { | ||
| 964 | + return url.startsWith('https:') | ||
| 965 | + } | ||
| 966 | + | ||
| 967 | + return url.protocol === 'https:' | ||
| 968 | + } | ||
| 969 | + | ||
| 970 | + /** | ||
| 971 | + * @see https://fetch.spec.whatwg.org/#http-scheme | ||
| 972 | + * @param {URL} url | ||
| 973 | + */ | ||
| 974 | + function urlIsHttpHttpsScheme (url) { | ||
| 975 | + assert('protocol' in url) // ensure it's a url object | ||
| 976 | + | ||
| 977 | + const protocol = url.protocol | ||
| 978 | + | ||
| 979 | + return protocol === 'http:' || protocol === 'https:' | ||
| 980 | + } | ||
| 981 | + | ||
| 947 | 982 | /** | |
| 948 | 983 | * Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0. | |
| 949 | 984 | */ | |
@@ -988,5 +1023,8 @@ module.exports = { | |||
| 988 | 1023 | isReadableStreamLike, | |
| 989 | 1024 | readableStreamClose, | |
| 990 | 1025 | isomorphicEncode, | |
| 991 | - isomorphicDecode | ||
| 1026 | + isomorphicDecode, | ||
| 1027 | + urlIsLocal, | ||
| 1028 | + urlHasHttpsScheme, | ||
| 1029 | + urlIsHttpHttpsScheme | ||
| 992 | 1030 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments