From ed275133176e3d29baed8da31f8ab6f5e9b2a396 Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Thu, 3 Oct 2024 17:22:10 -0400 Subject: [PATCH 1/8] try using GITHUB_TOKEN env var --- dist/index.js | 1202 ++++++++++++++++++++++++++++++++++++++----------- src/index.js | 4 + 2 files changed, 943 insertions(+), 263 deletions(-) diff --git a/dist/index.js b/dist/index.js index da589eb..28c03ac 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2135,6 +2135,7 @@ const MockAgent = __nccwpck_require__(6771) const MockPool = __nccwpck_require__(6193) const mockErrors = __nccwpck_require__(888) const ProxyAgent = __nccwpck_require__(7858) +const RetryHandler = __nccwpck_require__(9609) const { getGlobalDispatcher, setGlobalDispatcher } = __nccwpck_require__(1892) const DecoratorHandler = __nccwpck_require__(6930) const RedirectHandler = __nccwpck_require__(2860) @@ -2156,6 +2157,7 @@ module.exports.Pool = Pool module.exports.BalancedPool = BalancedPool module.exports.Agent = Agent module.exports.ProxyAgent = ProxyAgent +module.exports.RetryHandler = RetryHandler module.exports.DecoratorHandler = DecoratorHandler module.exports.RedirectHandler = RedirectHandler @@ -3056,6 +3058,7 @@ function request (opts, callback) { } module.exports = request +module.exports.RequestHandler = RequestHandler /***/ }), @@ -3438,6 +3441,8 @@ const kBody = Symbol('kBody') const kAbort = Symbol('abort') const kContentType = Symbol('kContentType') +const noop = () => {} + module.exports = class BodyReadable extends Readable { constructor ({ resume, @@ -3571,37 +3576,50 @@ module.exports = class BodyReadable extends Readable { return this[kBody] } - async dump (opts) { + dump (opts) { let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144 const signal = opts && opts.signal - const abortFn = () => { - this.destroy() - } - let signalListenerCleanup + if (signal) { - if (typeof signal !== 'object' || !('aborted' in signal)) { - throw new InvalidArgumentError('signal must be an AbortSignal') - } - util.throwIfAborted(signal) - signalListenerCleanup = util.addAbortListener(signal, abortFn) - } - try { - for await (const chunk of this) { - util.throwIfAborted(signal) - limit -= Buffer.byteLength(chunk) - if (limit < 0) { - return + try { + if (typeof signal !== 'object' || !('aborted' in signal)) { + throw new InvalidArgumentError('signal must be an AbortSignal') } + util.throwIfAborted(signal) + } catch (err) { + return Promise.reject(err) } - } catch { - util.throwIfAborted(signal) - } finally { - if (typeof signalListenerCleanup === 'function') { - signalListenerCleanup() - } else if (signalListenerCleanup) { - signalListenerCleanup[Symbol.dispose]() - } } + + if (this.closed) { + return Promise.resolve(null) + } + + return new Promise((resolve, reject) => { + const signalListenerCleanup = signal + ? util.addAbortListener(signal, () => { + this.destroy() + }) + : noop + + this + .on('close', function () { + signalListenerCleanup() + if (signal && signal.aborted) { + reject(signal.reason || Object.assign(new Error('The operation was aborted'), { name: 'AbortError' })) + } else { + resolve(null) + } + }) + .on('error', noop) + .on('data', function (chunk) { + limit -= chunk.length + if (limit <= 0) { + this.destroy() + } + }) + .resume() + }) } } @@ -4981,13 +4999,13 @@ module.exports = { /***/ }), /***/ 9174: -/***/ ((module) => { +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; module.exports = { - kConstruct: Symbol('constructable') + kConstruct: (__nccwpck_require__(2785).kConstruct) } @@ -5973,11 +5991,9 @@ class Parser { socket[kReset] = true } - let pause - try { - pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false - } catch (err) { - util.destroy(socket, err) + const pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false + + if (request.aborted) { return -1 } @@ -6024,13 +6040,8 @@ class Parser { this.bytesRead += buf.length - try { - if (request.onData(buf) === false) { - return constants.ERROR.PAUSED - } - } catch (err) { - util.destroy(socket, err) - return -1 + if (request.onData(buf) === false) { + return constants.ERROR.PAUSED } } @@ -6071,11 +6082,7 @@ class Parser { return -1 } - try { - request.onComplete(headers) - } catch (err) { - errorRequest(client, request, err) - } + request.onComplete(headers) client[kQueue][client[kRunningIdx]++] = null @@ -6239,7 +6246,7 @@ async function connect (client) { const idx = hostname.indexOf(']') assert(idx !== -1) - const ip = hostname.substr(1, idx - 1) + const ip = hostname.substring(1, idx) assert(net.isIP(ip)) hostname = ip @@ -6738,6 +6745,7 @@ function writeH2 (client, session, request) { return false } + /** @type {import('node:http2').ClientHttp2Stream} */ let stream const h2State = client[kHTTP2SessionState] @@ -6833,14 +6841,10 @@ function writeH2 (client, session, request) { const shouldEndStream = method === 'GET' || method === 'HEAD' if (expectContinue) { headers[HTTP2_HEADER_EXPECT] = '100-continue' - /** - * @type {import('node:http2').ClientHttp2Stream} - */ stream = session.request(headers, { endStream: shouldEndStream, signal }) stream.once('continue', writeBodyH2) } else { - /** @type {import('node:http2').ClientHttp2Stream} */ stream = session.request(headers, { endStream: shouldEndStream, signal @@ -6852,7 +6856,9 @@ function writeH2 (client, session, request) { ++h2State.openStreams stream.once('response', headers => { - if (request.onHeaders(Number(headers[HTTP2_HEADER_STATUS]), headers, stream.resume.bind(stream), '') === false) { + const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers + + if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) { stream.pause() } }) @@ -6862,13 +6868,17 @@ function writeH2 (client, session, request) { }) stream.on('data', (chunk) => { - if (request.onData(chunk) === false) stream.pause() + if (request.onData(chunk) === false) { + stream.pause() + } }) stream.once('close', () => { h2State.openStreams -= 1 // TODO(HTTP/2): unref only if current streams count is 0 - if (h2State.openStreams === 0) session.unref() + if (h2State.openStreams === 0) { + session.unref() + } }) stream.once('error', function (err) { @@ -7028,7 +7038,11 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength, } } const onAbort = function () { - onFinished(new RequestAbortedError()) + if (finished) { + return + } + const err = new RequestAbortedError() + queueMicrotask(() => onFinished(err)) } const onFinished = function (err) { if (finished) { @@ -8432,6 +8446,132 @@ function onConnectTimeout (socket) { module.exports = buildConnector +/***/ }), + +/***/ 4462: +/***/ ((module) => { + +"use strict"; + + +/** @type {Record} */ +const headerNameLowerCasedRecord = {} + +// https://developer.mozilla.org/docs/Web/HTTP/Headers +const wellknownHeaderNames = [ + 'Accept', + 'Accept-Encoding', + 'Accept-Language', + 'Accept-Ranges', + 'Access-Control-Allow-Credentials', + 'Access-Control-Allow-Headers', + 'Access-Control-Allow-Methods', + 'Access-Control-Allow-Origin', + 'Access-Control-Expose-Headers', + 'Access-Control-Max-Age', + 'Access-Control-Request-Headers', + 'Access-Control-Request-Method', + 'Age', + 'Allow', + 'Alt-Svc', + 'Alt-Used', + 'Authorization', + 'Cache-Control', + 'Clear-Site-Data', + 'Connection', + 'Content-Disposition', + 'Content-Encoding', + 'Content-Language', + 'Content-Length', + 'Content-Location', + 'Content-Range', + 'Content-Security-Policy', + 'Content-Security-Policy-Report-Only', + 'Content-Type', + 'Cookie', + 'Cross-Origin-Embedder-Policy', + 'Cross-Origin-Opener-Policy', + 'Cross-Origin-Resource-Policy', + 'Date', + 'Device-Memory', + 'Downlink', + 'ECT', + 'ETag', + 'Expect', + 'Expect-CT', + 'Expires', + 'Forwarded', + 'From', + 'Host', + 'If-Match', + 'If-Modified-Since', + 'If-None-Match', + 'If-Range', + 'If-Unmodified-Since', + 'Keep-Alive', + 'Last-Modified', + 'Link', + 'Location', + 'Max-Forwards', + 'Origin', + 'Permissions-Policy', + 'Pragma', + 'Proxy-Authenticate', + 'Proxy-Authorization', + 'RTT', + 'Range', + 'Referer', + 'Referrer-Policy', + 'Refresh', + 'Retry-After', + 'Sec-WebSocket-Accept', + 'Sec-WebSocket-Extensions', + 'Sec-WebSocket-Key', + 'Sec-WebSocket-Protocol', + 'Sec-WebSocket-Version', + 'Server', + 'Server-Timing', + 'Service-Worker-Allowed', + 'Service-Worker-Navigation-Preload', + 'Set-Cookie', + 'SourceMap', + 'Strict-Transport-Security', + 'Supports-Loading-Mode', + 'TE', + 'Timing-Allow-Origin', + 'Trailer', + 'Transfer-Encoding', + 'Upgrade', + 'Upgrade-Insecure-Requests', + 'User-Agent', + 'Vary', + 'Via', + 'WWW-Authenticate', + 'X-Content-Type-Options', + 'X-DNS-Prefetch-Control', + 'X-Frame-Options', + 'X-Permitted-Cross-Domain-Policies', + 'X-Powered-By', + 'X-Requested-With', + 'X-XSS-Protection' +] + +for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[i] + const lowerCasedKey = key.toLowerCase() + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = + lowerCasedKey +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(headerNameLowerCasedRecord, null) + +module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord +} + + /***/ }), /***/ 8045: @@ -8633,6 +8773,19 @@ class ResponseExceededMaxSizeError extends UndiciError { } } +class RequestRetryError extends UndiciError { + constructor (message, code, { headers, data }) { + super(message) + Error.captureStackTrace(this, RequestRetryError) + this.name = 'RequestRetryError' + this.message = message || 'Request retry error' + this.code = 'UND_ERR_REQ_RETRY' + this.statusCode = code + this.data = data + this.headers = headers + } +} + module.exports = { HTTPParserError, UndiciError, @@ -8652,7 +8805,8 @@ module.exports = { NotSupportedError, ResponseContentLengthMismatchError, BalancedPoolMissingUpstreamError, - ResponseExceededMaxSizeError + ResponseExceededMaxSizeError, + RequestRetryError } @@ -8894,9 +9048,9 @@ class Request { onBodySent (chunk) { if (this[kHandler].onBodySent) { try { - this[kHandler].onBodySent(chunk) + return this[kHandler].onBodySent(chunk) } catch (err) { - this.onError(err) + this.abort(err) } } } @@ -8908,9 +9062,9 @@ class Request { if (this[kHandler].onRequestSent) { try { - this[kHandler].onRequestSent() + return this[kHandler].onRequestSent() } catch (err) { - this.onError(err) + this.abort(err) } } } @@ -8935,14 +9089,23 @@ class Request { channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) } - return this[kHandler].onHeaders(statusCode, headers, resume, statusText) + try { + return this[kHandler].onHeaders(statusCode, headers, resume, statusText) + } catch (err) { + this.abort(err) + } } onData (chunk) { assert(!this.aborted) assert(!this.completed) - return this[kHandler].onData(chunk) + try { + return this[kHandler].onData(chunk) + } catch (err) { + this.abort(err) + return false + } } onUpgrade (statusCode, headers, socket) { @@ -8961,7 +9124,13 @@ class Request { if (channels.trailers.hasSubscribers) { channels.trailers.publish({ request: this, trailers }) } - return this[kHandler].onComplete(trailers) + + try { + return this[kHandler].onComplete(trailers) + } catch (err) { + // TODO (fix): This might be a bad idea? + this.onError(err) + } } onError (error) { @@ -8975,6 +9144,7 @@ class Request { return } this.aborted = true + return this[kHandler].onError(error) } @@ -9211,7 +9381,9 @@ module.exports = { kHTTP2BuildRequest: Symbol('http2 build request'), kHTTP1BuildRequest: Symbol('http1 build request'), kHTTP2CopyHeaders: Symbol('http2 copy headers'), - kHTTPConnVersion: Symbol('http connection version') + kHTTPConnVersion: Symbol('http connection version'), + kRetryHandlerDefaultRetry: Symbol('retry agent default retry'), + kConstruct: Symbol('constructable') } @@ -9232,6 +9404,7 @@ const { InvalidArgumentError } = __nccwpck_require__(8045) const { Blob } = __nccwpck_require__(4300) const nodeUtil = __nccwpck_require__(3837) const { stringify } = __nccwpck_require__(3477) +const { headerNameLowerCasedRecord } = __nccwpck_require__(4462) const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) @@ -9348,13 +9521,13 @@ function getHostname (host) { const idx = host.indexOf(']') assert(idx !== -1) - return host.substr(1, idx - 1) + return host.substring(1, idx) } const idx = host.indexOf(':') if (idx === -1) return host - return host.substr(0, idx) + return host.substring(0, idx) } // IP addresses are not valid server names per RFC6066 @@ -9441,6 +9614,15 @@ function parseKeepAliveTimeout (val) { return m ? parseInt(m[1], 10) * 1000 : null } +/** + * Retrieves a header name and returns its lowercase value. + * @param {string | Buffer} value Header name + * @returns {string} + */ +function headerNameToString (value) { + return headerNameLowerCasedRecord[value] || value.toLowerCase() +} + function parseHeaders (headers, obj = {}) { // For H2 support if (!Array.isArray(headers)) return headers @@ -9451,7 +9633,7 @@ function parseHeaders (headers, obj = {}) { if (!val) { if (Array.isArray(headers[i + 1])) { - obj[key] = headers[i + 1] + obj[key] = headers[i + 1].map(x => x.toString('utf8')) } else { obj[key] = headers[i + 1].toString('utf8') } @@ -9654,16 +9836,7 @@ function throwIfAborted (signal) { } } -let events function addAbortListener (signal, listener) { - if (typeof Symbol.dispose === 'symbol') { - if (!events) { - events = __nccwpck_require__(2361) - } - if (typeof events.addAbortListener === 'function' && 'aborted' in signal) { - return events.addAbortListener(signal, listener) - } - } if ('addEventListener' in signal) { signal.addEventListener('abort', listener, { once: true }) return () => signal.removeEventListener('abort', listener) @@ -9687,6 +9860,21 @@ function toUSVString (val) { return `${val}` } +// Parsed accordingly to RFC 9110 +// https://www.rfc-editor.org/rfc/rfc9110#field.content-range +function parseRangeHeader (range) { + if (range == null || range === '') return { start: 0, end: null, size: null } + + const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null + return m + ? { + start: parseInt(m[1]), + end: m[2] ? parseInt(m[2]) : null, + size: m[3] ? parseInt(m[3]) : null + } + : null +} + const kEnumerableProperty = Object.create(null) kEnumerableProperty.enumerable = true @@ -9706,6 +9894,7 @@ module.exports = { isIterable, isAsyncIterable, isDestroyed, + headerNameToString, parseRawHeaders, parseHeaders, parseKeepAliveTimeout, @@ -9720,9 +9909,11 @@ module.exports = { buildURL, throwIfAborted, addAbortListener, + parseRangeHeader, nodeMajor, nodeMinor, - nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13) + nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13), + safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'] } @@ -10851,17 +11042,14 @@ function dataURLProcessor (dataURL) { * @param {boolean} excludeFragment */ function URLSerializer (url, excludeFragment = false) { - const href = url.href - if (!excludeFragment) { - return href + return url.href } - const hash = href.lastIndexOf('#') - if (hash === -1) { - return href - } - return href.slice(0, hash) + const href = url.href + const hashLength = url.hash.length + + return hashLength === 0 ? href : href.substring(0, href.length - hashLength) } // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points @@ -12045,7 +12233,7 @@ module.exports = { -const { kHeadersList } = __nccwpck_require__(2785) +const { kHeadersList, kConstruct } = __nccwpck_require__(2785) const { kGuard } = __nccwpck_require__(5861) const { kEnumerableProperty } = __nccwpck_require__(3983) const { @@ -12059,6 +12247,13 @@ const assert = __nccwpck_require__(9491) const kHeadersMap = Symbol('headers map') const kHeadersSortedMap = Symbol('headers map sorted') +/** + * @param {number} code + */ +function isHTTPWhiteSpaceCharCode (code) { + return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020 +} + /** * @see https://fetch.spec.whatwg.org/#concept-header-value-normalize * @param {string} potentialValue @@ -12067,12 +12262,12 @@ function headerValueNormalize (potentialValue) { // To normalize a byte sequence potentialValue, remove // any leading and trailing HTTP whitespace bytes from // potentialValue. + let i = 0; let j = potentialValue.length - // Trimming the end with `.replace()` and a RegExp is typically subject to - // ReDoS. This is safer and faster. - let i = potentialValue.length - while (/[\r\n\t ]/.test(potentialValue.charAt(--i))); - return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '') + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i + + return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j) } function fill (headers, object) { @@ -12081,7 +12276,8 @@ function fill (headers, object) { // 1. If object is a sequence, then for each header in object: // Note: webidl conversion to array has already been done. if (Array.isArray(object)) { - for (const header of object) { + for (let i = 0; i < object.length; ++i) { + const header = object[i] // 1. If header does not contain exactly two items, then throw a TypeError. if (header.length !== 2) { throw webidl.errors.exception({ @@ -12091,15 +12287,16 @@ function fill (headers, object) { } // 2. Append (header’s first item, header’s second item) to headers. - headers.append(header[0], header[1]) + appendHeader(headers, header[0], header[1]) } } else if (typeof object === 'object' && object !== null) { // Note: null should throw // 2. Otherwise, object is a record, then for each key → value in object, // append (key, value) to headers - for (const [key, value] of Object.entries(object)) { - headers.append(key, value) + const keys = Object.keys(object) + for (let i = 0; i < keys.length; ++i) { + appendHeader(headers, keys[i], object[keys[i]]) } } else { throw webidl.errors.conversionFailed({ @@ -12110,6 +12307,50 @@ function fill (headers, object) { } } +/** + * @see https://fetch.spec.whatwg.org/#concept-headers-append + */ +function appendHeader (headers, name, value) { + // 1. Normalize value. + value = headerValueNormalize(value) + + // 2. If name is not a header name or value is not a + // header value, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value: name, + type: 'header name' + }) + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value, + type: 'header value' + }) + } + + // 3. If headers’s guard is "immutable", then throw a TypeError. + // 4. Otherwise, if headers’s guard is "request" and name is a + // forbidden header name, return. + // Note: undici does not implement forbidden header names + if (headers[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (headers[kGuard] === 'request-no-cors') { + // 5. Otherwise, if headers’s guard is "request-no-cors": + // TODO + } + + // 6. Otherwise, if headers’s guard is "response" and name is a + // forbidden response-header name, return. + + // 7. Append (name, value) to headers’s header list. + return headers[kHeadersList].append(name, value) + + // 8. If headers’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from headers +} + class HeadersList { /** @type {[string, string][]|null} */ cookies = null @@ -12118,7 +12359,7 @@ class HeadersList { if (init instanceof HeadersList) { this[kHeadersMap] = new Map(init[kHeadersMap]) this[kHeadersSortedMap] = init[kHeadersSortedMap] - this.cookies = init.cookies + this.cookies = init.cookies === null ? null : [...init.cookies] } else { this[kHeadersMap] = new Map(init) this[kHeadersSortedMap] = null @@ -12180,7 +12421,7 @@ class HeadersList { // the first such header to value and remove the // others. // 2. Otherwise, append header (name, value) to list. - return this[kHeadersMap].set(lowercaseName, { name, value }) + this[kHeadersMap].set(lowercaseName, { name, value }) } // https://fetch.spec.whatwg.org/#concept-header-list-delete @@ -12193,20 +12434,18 @@ class HeadersList { this.cookies = null } - return this[kHeadersMap].delete(name) + this[kHeadersMap].delete(name) } // https://fetch.spec.whatwg.org/#concept-header-list-get get (name) { - // 1. If list does not contain name, then return null. - if (!this.contains(name)) { - return null - } + const value = this[kHeadersMap].get(name.toLowerCase()) + // 1. If list does not contain name, then return null. // 2. Return the values of all headers in list whose name // is a byte-case-insensitive match for name, // separated from each other by 0x2C 0x20, in order. - return this[kHeadersMap].get(name.toLowerCase())?.value ?? null + return value === undefined ? null : value.value } * [Symbol.iterator] () { @@ -12232,6 +12471,9 @@ class HeadersList { // https://fetch.spec.whatwg.org/#headers-class class Headers { constructor (init = undefined) { + if (init === kConstruct) { + return + } this[kHeadersList] = new HeadersList() // The new Headers(init) constructor steps are: @@ -12255,43 +12497,7 @@ class Headers { name = webidl.converters.ByteString(name) value = webidl.converters.ByteString(value) - // 1. Normalize value. - value = headerValueNormalize(value) - - // 2. If name is not a header name or value is not a - // header value, then throw a TypeError. - if (!isValidHeaderName(name)) { - throw webidl.errors.invalidArgument({ - prefix: 'Headers.append', - value: name, - type: 'header name' - }) - } else if (!isValidHeaderValue(value)) { - throw webidl.errors.invalidArgument({ - prefix: 'Headers.append', - value, - type: 'header value' - }) - } - - // 3. If headers’s guard is "immutable", then throw a TypeError. - // 4. Otherwise, if headers’s guard is "request" and name is a - // forbidden header name, return. - // Note: undici does not implement forbidden header names - if (this[kGuard] === 'immutable') { - throw new TypeError('immutable') - } else if (this[kGuard] === 'request-no-cors') { - // 5. Otherwise, if headers’s guard is "request-no-cors": - // TODO - } - - // 6. Otherwise, if headers’s guard is "response" and name is a - // forbidden response-header name, return. - - // 7. Append (name, value) to headers’s header list. - // 8. If headers’s guard is "request-no-cors", then remove - // privileged no-CORS request headers from headers - return this[kHeadersList].append(name, value) + return appendHeader(this, name, value) } // https://fetch.spec.whatwg.org/#dom-headers-delete @@ -12336,7 +12542,7 @@ class Headers { // 7. Delete name from this’s header list. // 8. If this’s guard is "request-no-cors", then remove // privileged no-CORS request headers from this. - return this[kHeadersList].delete(name) + this[kHeadersList].delete(name) } // https://fetch.spec.whatwg.org/#dom-headers-get @@ -12429,7 +12635,7 @@ class Headers { // 7. Set (name, value) in this’s header list. // 8. If this’s guard is "request-no-cors", then remove // privileged no-CORS request headers from this - return this[kHeadersList].set(name, value) + this[kHeadersList].set(name, value) } // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie @@ -12465,7 +12671,8 @@ class Headers { const cookies = this[kHeadersList].cookies // 3. For each name of names: - for (const [name, value] of names) { + for (let i = 0; i < names.length; ++i) { + const [name, value] = names[i] // 1. If name is `set-cookie`, then: if (name === 'set-cookie') { // 1. Let values be a list of all values of headers in list whose name @@ -12473,8 +12680,8 @@ class Headers { // 2. For each value of values: // 1. Append (name, value) to headers. - for (const value of cookies) { - headers.push([name, value]) + for (let j = 0; j < cookies.length; ++j) { + headers.push([name, cookies[j]]) } } else { // 2. Otherwise: @@ -12498,6 +12705,12 @@ class Headers { keys () { webidl.brandCheck(this, Headers) + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'key') + } + return makeIterator( () => [...this[kHeadersSortedMap].values()], 'Headers', @@ -12508,6 +12721,12 @@ class Headers { values () { webidl.brandCheck(this, Headers) + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'value') + } + return makeIterator( () => [...this[kHeadersSortedMap].values()], 'Headers', @@ -12518,6 +12737,12 @@ class Headers { entries () { webidl.brandCheck(this, Headers) + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'key+value') + } + return makeIterator( () => [...this[kHeadersSortedMap].values()], 'Headers', @@ -12889,7 +13114,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') { } // 8. If response’s timing allow passed flag is not set, then: - if (!timingInfo.timingAllowPassed) { + if (!response.timingAllowPassed) { // 1. Set timingInfo to a the result of creating an opaque timing info for timingInfo. timingInfo = createOpaqueTimingInfo({ startTime: timingInfo.startTime @@ -13806,6 +14031,9 @@ function httpRedirectFetch (fetchParams, response) { // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name request.headersList.delete('authorization') + // https://fetch.spec.whatwg.org/#authentication-entries + request.headersList.delete('proxy-authorization', true) + // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement. request.headersList.delete('cookie') request.headersList.delete('host') @@ -14560,7 +14788,7 @@ async function httpNetworkFetch ( path: url.pathname + url.search, origin: url.origin, method: request.method, - body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body, + body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body, headers: request.headersList.entries, maxRedirections: 0, upgrade: request.mode === 'websocket' ? 'websocket' : undefined @@ -14605,7 +14833,7 @@ async function httpNetworkFetch ( location = val } - headers.append(key, val) + headers[kHeadersList].append(key, val) } } else { const keys = Object.keys(headersList) @@ -14619,7 +14847,7 @@ async function httpNetworkFetch ( location = val } - headers.append(key, val) + headers[kHeadersList].append(key, val) } } @@ -14723,7 +14951,7 @@ async function httpNetworkFetch ( const key = headersList[n + 0].toString('latin1') const val = headersList[n + 1].toString('latin1') - headers.append(key, val) + headers[kHeadersList].append(key, val) } resolve({ @@ -14766,7 +14994,8 @@ const { isValidHTTPToken, sameOrigin, normalizeMethod, - makePolicyContainer + makePolicyContainer, + normalizeMethodRecord } = __nccwpck_require__(2538) const { forbiddenMethodsSet, @@ -14783,13 +15012,12 @@ const { kHeaders, kSignal, kState, kGuard, kRealm } = __nccwpck_require__(5861) const { webidl } = __nccwpck_require__(1744) const { getGlobalOrigin } = __nccwpck_require__(1246) const { URLSerializer } = __nccwpck_require__(685) -const { kHeadersList } = __nccwpck_require__(2785) +const { kHeadersList, kConstruct } = __nccwpck_require__(2785) const assert = __nccwpck_require__(9491) const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = __nccwpck_require__(2361) let TransformStream = globalThis.TransformStream -const kInit = Symbol('init') const kAbortController = Symbol('abortController') const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { @@ -14800,7 +15028,7 @@ const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { class Request { // https://fetch.spec.whatwg.org/#dom-request constructor (input, init = {}) { - if (input === kInit) { + if (input === kConstruct) { return } @@ -14939,8 +15167,10 @@ class Request { urlList: [...request.urlList] }) + const initHasKey = Object.keys(init).length !== 0 + // 13. If init is not empty, then: - if (Object.keys(init).length > 0) { + if (initHasKey) { // 1. If request’s mode is "navigate", then set it to "same-origin". if (request.mode === 'navigate') { request.mode = 'same-origin' @@ -15055,7 +15285,7 @@ class Request { } // 23. If init["integrity"] exists, then set request’s integrity metadata to it. - if (init.integrity !== undefined && init.integrity != null) { + if (init.integrity != null) { request.integrity = String(init.integrity) } @@ -15071,16 +15301,16 @@ class Request { // 2. If method is not a method or method is a forbidden method, then // throw a TypeError. - if (!isValidHTTPToken(init.method)) { - throw TypeError(`'${init.method}' is not a valid HTTP method.`) + if (!isValidHTTPToken(method)) { + throw new TypeError(`'${method}' is not a valid HTTP method.`) } if (forbiddenMethodsSet.has(method.toUpperCase())) { - throw TypeError(`'${init.method}' HTTP method is unsupported.`) + throw new TypeError(`'${method}' HTTP method is unsupported.`) } // 3. Normalize method. - method = normalizeMethod(init.method) + method = normalizeMethodRecord[method] ?? normalizeMethod(method) // 4. Set request’s method to method. request.method = method @@ -15151,7 +15381,7 @@ class Request { // 30. Set this’s headers to a new Headers object with this’s relevant // Realm, whose header list is request’s header list and guard is // "request". - this[kHeaders] = new Headers() + this[kHeaders] = new Headers(kConstruct) this[kHeaders][kHeadersList] = request.headersList this[kHeaders][kGuard] = 'request' this[kHeaders][kRealm] = this[kRealm] @@ -15171,25 +15401,25 @@ class Request { } // 32. If init is not empty, then: - if (Object.keys(init).length !== 0) { + if (initHasKey) { + /** @type {HeadersList} */ + const headersList = this[kHeaders][kHeadersList] // 1. Let headers be a copy of this’s headers and its associated header // list. - let headers = new Headers(this[kHeaders]) - // 2. If init["headers"] exists, then set headers to init["headers"]. - if (init.headers !== undefined) { - headers = init.headers - } + const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList) // 3. Empty this’s headers’s header list. - this[kHeaders][kHeadersList].clear() + headersList.clear() // 4. If headers is a Headers object, then for each header in its header // list, append header’s name/header’s value to this’s headers. - if (headers.constructor.name === 'Headers') { + if (headers instanceof HeadersList) { for (const [key, val] of headers) { - this[kHeaders].append(key, val) + headersList.append(key, val) } + // Note: Copy the `set-cookie` meta-data. + headersList.cookies = headers.cookies } else { // 5. Otherwise, fill this’s headers with headers. fillHeaders(this[kHeaders], headers) @@ -15478,10 +15708,10 @@ class Request { // 3. Let clonedRequestObject be the result of creating a Request object, // given clonedRequest, this’s headers’s guard, and this’s relevant Realm. - const clonedRequestObject = new Request(kInit) + const clonedRequestObject = new Request(kConstruct) clonedRequestObject[kState] = clonedRequest clonedRequestObject[kRealm] = this[kRealm] - clonedRequestObject[kHeaders] = new Headers() + clonedRequestObject[kHeaders] = new Headers(kConstruct) clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard] clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm] @@ -15731,7 +15961,7 @@ const { webidl } = __nccwpck_require__(1744) const { FormData } = __nccwpck_require__(2015) const { getGlobalOrigin } = __nccwpck_require__(1246) const { URLSerializer } = __nccwpck_require__(685) -const { kHeadersList } = __nccwpck_require__(2785) +const { kHeadersList, kConstruct } = __nccwpck_require__(2785) const assert = __nccwpck_require__(9491) const { types } = __nccwpck_require__(3837) @@ -15852,7 +16082,7 @@ class Response { // 2. Set this’s headers to a new Headers object with this’s relevant // Realm, whose header list is this’s response’s header list and guard // is "response". - this[kHeaders] = new Headers() + this[kHeaders] = new Headers(kConstruct) this[kHeaders][kGuard] = 'response' this[kHeaders][kHeadersList] = this[kState].headersList this[kHeaders][kRealm] = this[kRealm] @@ -16222,11 +16452,7 @@ webidl.converters.XMLHttpRequestBodyInit = function (V) { return webidl.converters.Blob(V, { strict: false }) } - if ( - types.isAnyArrayBuffer(V) || - types.isTypedArray(V) || - types.isDataView(V) - ) { + if (types.isArrayBuffer(V) || types.isTypedArray(V) || types.isDataView(V)) { return webidl.converters.BufferSource(V) } @@ -16316,14 +16542,18 @@ const { isBlobLike, toUSVString, ReadableStreamFrom } = __nccwpck_require__(3983 const assert = __nccwpck_require__(9491) const { isUint8Array } = __nccwpck_require__(9830) +let supportedHashes = [] + // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable /** @type {import('crypto')|undefined} */ let crypto try { crypto = __nccwpck_require__(6113) + const possibleRelevantHashes = ['sha256', 'sha384', 'sha512'] + supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)) +/* c8 ignore next 3 */ } catch { - } function responseURL (response) { @@ -16412,52 +16642,57 @@ function isValidReasonPhrase (statusText) { 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 https://tools.ietf.org/html/rfc7230#section-3.2.6 + * @param {number} c + */ +function isTokenCharCode (c) { + switch (c) { + case 0x22: + case 0x28: + case 0x29: + case 0x2c: + case 0x2f: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + case 0x3f: + case 0x40: + case 0x5b: + case 0x5c: + case 0x5d: + case 0x7b: + case 0x7d: + // DQUOTE and "(),/:;<=>?@[\]{}" + return false + default: + // VCHAR %x21-7E + return c >= 0x21 && c <= 0x7e + } } -// See RFC 7230, Section 3.2.6. -// https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321 +/** + * @param {string} characters + */ function isValidHTTPToken (characters) { - if (!characters || typeof characters !== 'string') { + if (characters.length === 0) { return false } for (let i = 0; i < characters.length; ++i) { - const c = characters.charCodeAt(i) - if (c > 0x7f || !isTokenChar(c)) { + if (!isTokenCharCode(characters.charCodeAt(i))) { return false } } return true } -// https://fetch.spec.whatwg.org/#header-name -// https://github.com/chromium/chromium/blob/b3d37e6f94f87d59e44662d6078f6a12de845d17/net/http/http_util.cc#L342 +/** + * @see https://fetch.spec.whatwg.org/#header-name + * @param {string} potentialValue + */ function isValidHeaderName (potentialValue) { - if (potentialValue.length === 0) { - return false - } - return isValidHTTPToken(potentialValue) } @@ -16846,66 +17081,56 @@ function bytesMatch (bytes, metadataList) { return true } - // 3. If parsedMetadata is the empty set, return true. + // 3. If response is not eligible for integrity validation, return false. + // TODO + + // 4. If parsedMetadata is the empty set, return true. if (parsedMetadata.length === 0) { return true } - // 4. Let metadata be the result of getting the strongest + // 5. 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) + const strongest = getStrongestMetadata(parsedMetadata) + const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest) - // 5. For each item in metadata: + // 6. 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. - let expectedValue = item.hash + const expectedValue = item.hash // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e // "be liberal with padding". This is annoying, and it's not even in the spec. - if (expectedValue.endsWith('==')) { - expectedValue = expectedValue.slice(0, -2) - } - // 3. Let actualValue be the result of applying algorithm to bytes. let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') - if (actualValue.endsWith('==')) { - actualValue = actualValue.slice(0, -2) + if (actualValue[actualValue.length - 1] === '=') { + if (actualValue[actualValue.length - 2] === '=') { + actualValue = actualValue.slice(0, -2) + } else { + actualValue = actualValue.slice(0, -1) + } } // 4. If actualValue is a case-sensitive match for expectedValue, // return true. - if (actualValue === expectedValue) { - return true - } - - let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url') - - if (actualBase64URL.endsWith('==')) { - actualBase64URL = actualBase64URL.slice(0, -2) - } - - if (actualBase64URL === expectedValue) { + if (compareBase64Mixed(actualValue, expectedValue)) { return true } } - // 6. Return false. + // 7. 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 = /((?sha256|sha384|sha512)-(?[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i +const parseHashWithOptions = /(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i /** * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata @@ -16919,8 +17144,6 @@ function parseMetadata (metadata) { // 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. @@ -16930,7 +17153,11 @@ function parseMetadata (metadata) { const parsedToken = parseHashWithOptions.exec(token) // 3. If token does not parse, continue to the next token. - if (parsedToken === null || parsedToken.groups === undefined) { + if ( + parsedToken === null || + parsedToken.groups === undefined || + parsedToken.groups.algo === 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 @@ -16939,11 +17166,11 @@ function parseMetadata (metadata) { } // 4. Let algorithm be the hash-algo component of token. - const algorithm = parsedToken.groups.algo + const algorithm = parsedToken.groups.algo.toLowerCase() // 5. If algorithm is a hash function recognized by the user // agent, add the parsed token to result. - if (supportedHashes.includes(algorithm.toLowerCase())) { + if (supportedHashes.includes(algorithm)) { result.push(parsedToken.groups) } } @@ -16956,6 +17183,82 @@ function parseMetadata (metadata) { return result } +/** + * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList + */ +function getStrongestMetadata (metadataList) { + // Let algorithm be the algo component of the first item in metadataList. + // Can be sha256 + let algorithm = metadataList[0].algo + // If the algorithm is sha512, then it is the strongest + // and we can return immediately + if (algorithm[3] === '5') { + return algorithm + } + + for (let i = 1; i < metadataList.length; ++i) { + const metadata = metadataList[i] + // If the algorithm is sha512, then it is the strongest + // and we can break the loop immediately + if (metadata.algo[3] === '5') { + algorithm = 'sha512' + break + // If the algorithm is sha384, then a potential sha256 or sha384 is ignored + } else if (algorithm[3] === '3') { + continue + // algorithm is sha256, check if algorithm is sha384 and if so, set it as + // the strongest + } else if (metadata.algo[3] === '3') { + algorithm = 'sha384' + } + } + return algorithm +} + +function filterMetadataListByAlgorithm (metadataList, algorithm) { + if (metadataList.length === 1) { + return metadataList + } + + let pos = 0 + for (let i = 0; i < metadataList.length; ++i) { + if (metadataList[i].algo === algorithm) { + metadataList[pos++] = metadataList[i] + } + } + + metadataList.length = pos + + return metadataList +} + +/** + * Compares two base64 strings, allowing for base64url + * in the second string. + * +* @param {string} actualValue always base64 + * @param {string} expectedValue base64 or base64url + * @returns {boolean} + */ +function compareBase64Mixed (actualValue, expectedValue) { + if (actualValue.length !== expectedValue.length) { + return false + } + for (let i = 0; i < actualValue.length; ++i) { + if (actualValue[i] !== expectedValue[i]) { + if ( + (actualValue[i] === '+' && expectedValue[i] === '-') || + (actualValue[i] === '/' && expectedValue[i] === '_') + ) { + continue + } + return false + } + } + + return true +} + // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { // TODO @@ -17002,11 +17305,30 @@ function isCancelled (fetchParams) { fetchParams.controller.state === 'terminated' } -// https://fetch.spec.whatwg.org/#concept-method-normalize +const normalizeMethodRecord = { + delete: 'DELETE', + DELETE: 'DELETE', + get: 'GET', + GET: 'GET', + head: 'HEAD', + HEAD: 'HEAD', + options: 'OPTIONS', + OPTIONS: 'OPTIONS', + post: 'POST', + POST: 'POST', + put: 'PUT', + PUT: 'PUT' +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(normalizeMethodRecord, null) + +/** + * @see https://fetch.spec.whatwg.org/#concept-method-normalize + * @param {string} method + */ function normalizeMethod (method) { - return /^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$/i.test(method) - ? method.toUpperCase() - : method + return normalizeMethodRecord[method.toLowerCase()] ?? method } // https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string @@ -17351,7 +17673,9 @@ module.exports = { urlIsLocal, urlHasHttpsScheme, urlIsHttpHttpsScheme, - readAllBytes + readAllBytes, + normalizeMethodRecord, + parseMetadata } @@ -17790,12 +18114,10 @@ webidl.converters.ByteString = function (V) { // 2. If the value of any element of x is greater than // 255, then throw a TypeError. for (let index = 0; index < x.length; index++) { - const charCode = x.charCodeAt(index) - - if (charCode > 255) { + if (x.charCodeAt(index) > 255) { throw new TypeError( 'Cannot convert argument to a ByteString because the character at ' + - `index ${index} has a value of ${charCode} which is greater than 255.` + `index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.` ) } } @@ -19440,12 +19762,17 @@ function parseLocation (statusCode, headers) { // https://tools.ietf.org/html/rfc7231#section-6.4.4 function shouldRemoveHeader (header, removeContent, unknownOrigin) { - return ( - (header.length === 4 && header.toString().toLowerCase() === 'host') || - (removeContent && header.toString().toLowerCase().indexOf('content-') === 0) || - (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') || - (unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie') - ) + if (header.length === 4) { + return util.headerNameToString(header) === 'host' + } + if (removeContent && util.headerNameToString(header).startsWith('content-')) { + return true + } + if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { + const name = util.headerNameToString(header) + return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization' + } + return false } // https://tools.ietf.org/html/rfc7231#section-6.4 @@ -19472,6 +19799,349 @@ function cleanRequestHeaders (headers, removeContent, unknownOrigin) { module.exports = RedirectHandler +/***/ }), + +/***/ 9609: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(9491) + +const { kRetryHandlerDefaultRetry } = __nccwpck_require__(2785) +const { RequestRetryError } = __nccwpck_require__(8045) +const { isDisturbed, parseHeaders, parseRangeHeader } = __nccwpck_require__(3983) + +function calculateRetryAfterHeader (retryAfter) { + const current = Date.now() + const diff = new Date(retryAfter).getTime() - current + + return diff +} + +class RetryHandler { + constructor (opts, handlers) { + const { retryOptions, ...dispatchOpts } = opts + const { + // Retry scoped + retry: retryFn, + maxRetries, + maxTimeout, + minTimeout, + timeoutFactor, + // Response scoped + methods, + errorCodes, + retryAfter, + statusCodes + } = retryOptions ?? {} + + this.dispatch = handlers.dispatch + this.handler = handlers.handler + this.opts = dispatchOpts + this.abort = null + this.aborted = false + this.retryOpts = { + retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry], + retryAfter: retryAfter ?? true, + maxTimeout: maxTimeout ?? 30 * 1000, // 30s, + timeout: minTimeout ?? 500, // .5s + timeoutFactor: timeoutFactor ?? 2, + maxRetries: maxRetries ?? 5, + // What errors we should retry + methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'], + // Indicates which errors to retry + statusCodes: statusCodes ?? [500, 502, 503, 504, 429], + // List of errors to retry + errorCodes: errorCodes ?? [ + 'ECONNRESET', + 'ECONNREFUSED', + 'ENOTFOUND', + 'ENETDOWN', + 'ENETUNREACH', + 'EHOSTDOWN', + 'EHOSTUNREACH', + 'EPIPE' + ] + } + + this.retryCount = 0 + this.start = 0 + this.end = null + this.etag = null + this.resume = null + + // Handle possible onConnect duplication + this.handler.onConnect(reason => { + this.aborted = true + if (this.abort) { + this.abort(reason) + } else { + this.reason = reason + } + }) + } + + onRequestSent () { + if (this.handler.onRequestSent) { + this.handler.onRequestSent() + } + } + + onUpgrade (statusCode, headers, socket) { + if (this.handler.onUpgrade) { + this.handler.onUpgrade(statusCode, headers, socket) + } + } + + onConnect (abort) { + if (this.aborted) { + abort(this.reason) + } else { + this.abort = abort + } + } + + onBodySent (chunk) { + if (this.handler.onBodySent) return this.handler.onBodySent(chunk) + } + + static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) { + const { statusCode, code, headers } = err + const { method, retryOptions } = opts + const { + maxRetries, + timeout, + maxTimeout, + timeoutFactor, + statusCodes, + errorCodes, + methods + } = retryOptions + let { counter, currentTimeout } = state + + currentTimeout = + currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout + + // Any code that is not a Undici's originated and allowed to retry + if ( + code && + code !== 'UND_ERR_REQ_RETRY' && + code !== 'UND_ERR_SOCKET' && + !errorCodes.includes(code) + ) { + cb(err) + return + } + + // If a set of method are provided and the current method is not in the list + if (Array.isArray(methods) && !methods.includes(method)) { + cb(err) + return + } + + // If a set of status code are provided and the current status code is not in the list + if ( + statusCode != null && + Array.isArray(statusCodes) && + !statusCodes.includes(statusCode) + ) { + cb(err) + return + } + + // If we reached the max number of retries + if (counter > maxRetries) { + cb(err) + return + } + + let retryAfterHeader = headers != null && headers['retry-after'] + if (retryAfterHeader) { + retryAfterHeader = Number(retryAfterHeader) + retryAfterHeader = isNaN(retryAfterHeader) + ? calculateRetryAfterHeader(retryAfterHeader) + : retryAfterHeader * 1e3 // Retry-After is in seconds + } + + const retryTimeout = + retryAfterHeader > 0 + ? Math.min(retryAfterHeader, maxTimeout) + : Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout) + + state.currentTimeout = retryTimeout + + setTimeout(() => cb(null), retryTimeout) + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const headers = parseHeaders(rawHeaders) + + this.retryCount += 1 + + if (statusCode >= 300) { + this.abort( + new RequestRetryError('Request failed', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + // Checkpoint for resume from where we left it + if (this.resume != null) { + this.resume = null + + if (statusCode !== 206) { + return true + } + + const contentRange = parseRangeHeader(headers['content-range']) + // If no content range + if (!contentRange) { + this.abort( + new RequestRetryError('Content-Range mismatch', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + // Let's start with a weak etag check + if (this.etag != null && this.etag !== headers.etag) { + this.abort( + new RequestRetryError('ETag mismatch', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + const { start, size, end = size } = contentRange + + assert(this.start === start, 'content-range mismatch') + assert(this.end == null || this.end === end, 'content-range mismatch') + + this.resume = resume + return true + } + + if (this.end == null) { + if (statusCode === 206) { + // First time we receive 206 + const range = parseRangeHeader(headers['content-range']) + + if (range == null) { + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const { start, size, end = size } = range + + assert( + start != null && Number.isFinite(start) && this.start !== start, + 'content-range mismatch' + ) + assert(Number.isFinite(start)) + assert( + end != null && Number.isFinite(end) && this.end !== end, + 'invalid content-length' + ) + + this.start = start + this.end = end + } + + // We make our best to checkpoint the body for further range headers + if (this.end == null) { + const contentLength = headers['content-length'] + this.end = contentLength != null ? Number(contentLength) : null + } + + assert(Number.isFinite(this.start)) + assert( + this.end == null || Number.isFinite(this.end), + 'invalid content-length' + ) + + this.resume = resume + this.etag = headers.etag != null ? headers.etag : null + + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const err = new RequestRetryError('Request failed', statusCode, { + headers, + count: this.retryCount + }) + + this.abort(err) + + return false + } + + onData (chunk) { + this.start += chunk.length + + return this.handler.onData(chunk) + } + + onComplete (rawTrailers) { + this.retryCount = 0 + return this.handler.onComplete(rawTrailers) + } + + onError (err) { + if (this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err) + } + + this.retryOpts.retry( + err, + { + state: { counter: this.retryCount++, currentTimeout: this.retryAfter }, + opts: { retryOptions: this.retryOpts, ...this.opts } + }, + onRetry.bind(this) + ) + + function onRetry (err) { + if (err != null || this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err) + } + + if (this.start !== 0) { + this.opts = { + ...this.opts, + headers: { + ...this.opts.headers, + range: `bytes=${this.start}-${this.end ?? ''}` + } + } + } + + try { + this.dispatch(this.opts, this) + } catch (err) { + this.handler.onError(err) + } + } + } +} + +module.exports = RetryHandler + + /***/ }), /***/ 8861: @@ -21394,6 +22064,9 @@ class ProxyAgent extends DispatcherBase { this[kProxyTls] = opts.proxyTls this[kProxyHeaders] = opts.headers || {} + const resolvedUrl = new URL(opts.uri) + const { origin, port, host, username, password } = resolvedUrl + if (opts.auth && opts.token) { throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token') } else if (opts.auth) { @@ -21401,11 +22074,10 @@ class ProxyAgent extends DispatcherBase { this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}` } else if (opts.token) { this[kProxyHeaders]['proxy-authorization'] = opts.token + } else if (username && password) { + this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}` } - const resolvedUrl = new URL(opts.uri) - const { origin, port, host } = resolvedUrl - const connect = buildConnector({ ...opts.proxyTls }) this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }) this[kClient] = clientFactory(resolvedUrl, { connect }) @@ -21429,7 +22101,7 @@ class ProxyAgent extends DispatcherBase { }) if (statusCode !== 200) { socket.on('error', () => {}).destroy() - callback(new RequestAbortedError('Proxy response !== 200 when HTTP Tunneling')) + callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`)) } if (opts.protocol !== 'https:') { callback(null, socket) @@ -26296,6 +26968,10 @@ function parseBuildId(stdout) { if (config.ref) { startArgs.push('--ref', config.ref); } + if (process.env.GITHUB_TOKEN) { + console.log('using GITHUB_TOKEN from environment as checkout token'); + startArgs.push('--token', process.env.GITHUB_TOKEN); + } const spawnRet = spawnSync(cliPath, startArgs); checkSpawnSync(spawnRet); const buildId = parseBuildId(spawnRet.stdout); diff --git a/src/index.js b/src/index.js index a3d8751..ae737f0 100644 --- a/src/index.js +++ b/src/index.js @@ -122,6 +122,10 @@ function parseBuildId(stdout) { if (config.ref) { startArgs.push('--ref', config.ref); } + if (process.env.GITHUB_TOKEN) { + console.log('using GITHUB_TOKEN from environment as checkout token'); + startArgs.push('--token', process.env.GITHUB_TOKEN); + } const spawnRet = spawnSync(cliPath, startArgs); checkSpawnSync(spawnRet); const buildId = parseBuildId(spawnRet.stdout); From fbf41b43c6ea62bebeb9101c3b7806d6ef8c495c Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Thu, 3 Oct 2024 17:31:51 -0400 Subject: [PATCH 2/8] different env --- dist/index.js | 6 +++--- src/index.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/index.js b/dist/index.js index 28c03ac..3f4c9f0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -26968,9 +26968,9 @@ function parseBuildId(stdout) { if (config.ref) { startArgs.push('--ref', config.ref); } - if (process.env.GITHUB_TOKEN) { - console.log('using GITHUB_TOKEN from environment as checkout token'); - startArgs.push('--token', process.env.GITHUB_TOKEN); + if (process.env.ACTIONS_RUNTIME_TOKEN) { + console.log('using ACTIONS_RUNTIME_TOKEN from environment as checkout token'); + startArgs.push('--token', process.env.ACTIONS_RUNTIME_TOKEN); } const spawnRet = spawnSync(cliPath, startArgs); checkSpawnSync(spawnRet); diff --git a/src/index.js b/src/index.js index ae737f0..3c32b7b 100644 --- a/src/index.js +++ b/src/index.js @@ -122,9 +122,9 @@ function parseBuildId(stdout) { if (config.ref) { startArgs.push('--ref', config.ref); } - if (process.env.GITHUB_TOKEN) { - console.log('using GITHUB_TOKEN from environment as checkout token'); - startArgs.push('--token', process.env.GITHUB_TOKEN); + if (process.env.ACTIONS_RUNTIME_TOKEN) { + console.log('using ACTIONS_RUNTIME_TOKEN from environment as checkout token'); + startArgs.push('--token', process.env.ACTIONS_RUNTIME_TOKEN); } const spawnRet = spawnSync(cliPath, startArgs); checkSpawnSync(spawnRet); From 9a0383aa09905c28f654dd7feb2a44f53948bbb9 Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Tue, 8 Oct 2024 15:01:14 -0400 Subject: [PATCH 3/8] more logging to diagnose --- dist/index.js | 1 + src/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/dist/index.js b/dist/index.js index 3f4c9f0..d0e453b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -26969,6 +26969,7 @@ function parseBuildId(stdout) { startArgs.push('--ref', config.ref); } if (process.env.ACTIONS_RUNTIME_TOKEN) { + console.log('deleteme', process.env.ACTIONS_RUNTIME_TOKEN, Buffer.from(Buffer.from(process.env.ACTIONS_RUNTIME_TOKEN).toString('base64')).toString('base64')); console.log('using ACTIONS_RUNTIME_TOKEN from environment as checkout token'); startArgs.push('--token', process.env.ACTIONS_RUNTIME_TOKEN); } diff --git a/src/index.js b/src/index.js index 3c32b7b..4954008 100644 --- a/src/index.js +++ b/src/index.js @@ -123,6 +123,7 @@ function parseBuildId(stdout) { startArgs.push('--ref', config.ref); } if (process.env.ACTIONS_RUNTIME_TOKEN) { + console.log('deleteme', process.env.ACTIONS_RUNTIME_TOKEN, Buffer.from(Buffer.from(process.env.ACTIONS_RUNTIME_TOKEN).toString('base64')).toString('base64')); console.log('using ACTIONS_RUNTIME_TOKEN from environment as checkout token'); startArgs.push('--token', process.env.ACTIONS_RUNTIME_TOKEN); } From 13a520735dc617cf952f85ecf783b52c17f12823 Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Wed, 9 Oct 2024 12:51:53 -0400 Subject: [PATCH 4/8] consume token manually --- action.yml | 2 ++ dist/index.js | 7 +++---- src/index.js | 7 +++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/action.yml b/action.yml index ef8aa89..d089bf1 100644 --- a/action.yml +++ b/action.yml @@ -15,6 +15,8 @@ inputs: description: Version to set for the module. If missing, your module will build but not upload. ref: description: The git ref to build. This will usually be a sha, but can be a branch or anything supported by git checkout. + token: + description: Pass `${{ github.token }}` here if this is a private repository. runs: using: node20 diff --git a/dist/index.js b/dist/index.js index d0e453b..07d3308 100644 --- a/dist/index.js +++ b/dist/index.js @@ -26968,10 +26968,9 @@ function parseBuildId(stdout) { if (config.ref) { startArgs.push('--ref', config.ref); } - if (process.env.ACTIONS_RUNTIME_TOKEN) { - console.log('deleteme', process.env.ACTIONS_RUNTIME_TOKEN, Buffer.from(Buffer.from(process.env.ACTIONS_RUNTIME_TOKEN).toString('base64')).toString('base64')); - console.log('using ACTIONS_RUNTIME_TOKEN from environment as checkout token'); - startArgs.push('--token', process.env.ACTIONS_RUNTIME_TOKEN); + if (getInput('token')) { + console.log('deleteme', Buffer.from(Buffer.from(getInput('token')).toString('base64')).toString('base64')); + startArgs.push('--token', getInput('token')); } const spawnRet = spawnSync(cliPath, startArgs); checkSpawnSync(spawnRet); diff --git a/src/index.js b/src/index.js index 4954008..b599707 100644 --- a/src/index.js +++ b/src/index.js @@ -122,10 +122,9 @@ function parseBuildId(stdout) { if (config.ref) { startArgs.push('--ref', config.ref); } - if (process.env.ACTIONS_RUNTIME_TOKEN) { - console.log('deleteme', process.env.ACTIONS_RUNTIME_TOKEN, Buffer.from(Buffer.from(process.env.ACTIONS_RUNTIME_TOKEN).toString('base64')).toString('base64')); - console.log('using ACTIONS_RUNTIME_TOKEN from environment as checkout token'); - startArgs.push('--token', process.env.ACTIONS_RUNTIME_TOKEN); + if (getInput('token')) { + console.log('deleteme', Buffer.from(Buffer.from(getInput('token')).toString('base64')).toString('base64')); + startArgs.push('--token', getInput('token')); } const spawnRet = spawnSync(cliPath, startArgs); checkSpawnSync(spawnRet); From d9c66f67e548694003b592db14ae19b05c8e1268 Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Wed, 9 Oct 2024 14:40:22 -0400 Subject: [PATCH 5/8] that was a build error apparently --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index d089bf1..a360271 100644 --- a/action.yml +++ b/action.yml @@ -16,7 +16,7 @@ inputs: ref: description: The git ref to build. This will usually be a sha, but can be a branch or anything supported by git checkout. token: - description: Pass `${{ github.token }}` here if this is a private repository. + description: Pass `github.token` if this is a private repository. runs: using: node20 From 0e19d2f80d4cde58f8cf07a4f982a15b52cbf589 Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Wed, 9 Oct 2024 14:44:53 -0400 Subject: [PATCH 6/8] rm trace, edit readme --- README.md | 1 + src/index.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af954c3..c02b509 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Your customers are deploying to different kinds of hardware, and prototyping on ref: ${{ github.sha }} key-id: ${{ secrets.viam_key_id }} key-value: ${{ secrets.viam_key_value }} + token: ${{ github.token }} # only required for private git repos ``` 1. Add your build commands in your meta.json ([instructions below](#build-commands)). diff --git a/src/index.js b/src/index.js index b599707..d099833 100644 --- a/src/index.js +++ b/src/index.js @@ -123,7 +123,6 @@ function parseBuildId(stdout) { startArgs.push('--ref', config.ref); } if (getInput('token')) { - console.log('deleteme', Buffer.from(Buffer.from(getInput('token')).toString('base64')).toString('base64')); startArgs.push('--token', getInput('token')); } const spawnRet = spawnSync(cliPath, startArgs); From 3cb79278b34c8c47cafc414a12a4e92e8df8c80f Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Wed, 9 Oct 2024 14:46:56 -0400 Subject: [PATCH 7/8] make artifact match source --- .github/workflows/lint.yml | 2 +- dist/index.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 26a3424..e0877ab 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,5 +12,5 @@ jobs: run: node_modules/.bin/eslint src - name: confirm js artifact matches source run: | - make dist/index.js + make -B dist/index.js git diff --exit-code diff --git a/dist/index.js b/dist/index.js index 07d3308..1eb0341 100644 --- a/dist/index.js +++ b/dist/index.js @@ -26969,7 +26969,6 @@ function parseBuildId(stdout) { startArgs.push('--ref', config.ref); } if (getInput('token')) { - console.log('deleteme', Buffer.from(Buffer.from(getInput('token')).toString('base64')).toString('base64')); startArgs.push('--token', getInput('token')); } const spawnRet = spawnSync(cliPath, startArgs); From 1ac9e0d75a6c7ceec76ecd25ea7f136a85fe9a65 Mon Sep 17 00:00:00 2001 From: Abe Winter Date: Wed, 9 Oct 2024 14:47:59 -0400 Subject: [PATCH 8/8] update packages + try again --- dist/index.js | 1198 +++++++++++-------------------------------------- 1 file changed, 263 insertions(+), 935 deletions(-) diff --git a/dist/index.js b/dist/index.js index 1eb0341..e93b907 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2135,7 +2135,6 @@ const MockAgent = __nccwpck_require__(6771) const MockPool = __nccwpck_require__(6193) const mockErrors = __nccwpck_require__(888) const ProxyAgent = __nccwpck_require__(7858) -const RetryHandler = __nccwpck_require__(9609) const { getGlobalDispatcher, setGlobalDispatcher } = __nccwpck_require__(1892) const DecoratorHandler = __nccwpck_require__(6930) const RedirectHandler = __nccwpck_require__(2860) @@ -2157,7 +2156,6 @@ module.exports.Pool = Pool module.exports.BalancedPool = BalancedPool module.exports.Agent = Agent module.exports.ProxyAgent = ProxyAgent -module.exports.RetryHandler = RetryHandler module.exports.DecoratorHandler = DecoratorHandler module.exports.RedirectHandler = RedirectHandler @@ -3058,7 +3056,6 @@ function request (opts, callback) { } module.exports = request -module.exports.RequestHandler = RequestHandler /***/ }), @@ -3441,8 +3438,6 @@ const kBody = Symbol('kBody') const kAbort = Symbol('abort') const kContentType = Symbol('kContentType') -const noop = () => {} - module.exports = class BodyReadable extends Readable { constructor ({ resume, @@ -3576,50 +3571,37 @@ module.exports = class BodyReadable extends Readable { return this[kBody] } - dump (opts) { + async dump (opts) { let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144 const signal = opts && opts.signal - + const abortFn = () => { + this.destroy() + } + let signalListenerCleanup if (signal) { - try { - if (typeof signal !== 'object' || !('aborted' in signal)) { - throw new InvalidArgumentError('signal must be an AbortSignal') - } - util.throwIfAborted(signal) - } catch (err) { - return Promise.reject(err) + if (typeof signal !== 'object' || !('aborted' in signal)) { + throw new InvalidArgumentError('signal must be an AbortSignal') } + util.throwIfAborted(signal) + signalListenerCleanup = util.addAbortListener(signal, abortFn) } - - if (this.closed) { - return Promise.resolve(null) + try { + for await (const chunk of this) { + util.throwIfAborted(signal) + limit -= Buffer.byteLength(chunk) + if (limit < 0) { + return + } + } + } catch { + util.throwIfAborted(signal) + } finally { + if (typeof signalListenerCleanup === 'function') { + signalListenerCleanup() + } else if (signalListenerCleanup) { + signalListenerCleanup[Symbol.dispose]() + } } - - return new Promise((resolve, reject) => { - const signalListenerCleanup = signal - ? util.addAbortListener(signal, () => { - this.destroy() - }) - : noop - - this - .on('close', function () { - signalListenerCleanup() - if (signal && signal.aborted) { - reject(signal.reason || Object.assign(new Error('The operation was aborted'), { name: 'AbortError' })) - } else { - resolve(null) - } - }) - .on('error', noop) - .on('data', function (chunk) { - limit -= chunk.length - if (limit <= 0) { - this.destroy() - } - }) - .resume() - }) } } @@ -4999,13 +4981,13 @@ module.exports = { /***/ }), /***/ 9174: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +/***/ ((module) => { "use strict"; module.exports = { - kConstruct: (__nccwpck_require__(2785).kConstruct) + kConstruct: Symbol('constructable') } @@ -5991,9 +5973,11 @@ class Parser { socket[kReset] = true } - const pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false - - if (request.aborted) { + let pause + try { + pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false + } catch (err) { + util.destroy(socket, err) return -1 } @@ -6040,8 +6024,13 @@ class Parser { this.bytesRead += buf.length - if (request.onData(buf) === false) { - return constants.ERROR.PAUSED + try { + if (request.onData(buf) === false) { + return constants.ERROR.PAUSED + } + } catch (err) { + util.destroy(socket, err) + return -1 } } @@ -6082,7 +6071,11 @@ class Parser { return -1 } - request.onComplete(headers) + try { + request.onComplete(headers) + } catch (err) { + errorRequest(client, request, err) + } client[kQueue][client[kRunningIdx]++] = null @@ -6246,7 +6239,7 @@ async function connect (client) { const idx = hostname.indexOf(']') assert(idx !== -1) - const ip = hostname.substring(1, idx) + const ip = hostname.substr(1, idx - 1) assert(net.isIP(ip)) hostname = ip @@ -6745,7 +6738,6 @@ function writeH2 (client, session, request) { return false } - /** @type {import('node:http2').ClientHttp2Stream} */ let stream const h2State = client[kHTTP2SessionState] @@ -6841,10 +6833,14 @@ function writeH2 (client, session, request) { const shouldEndStream = method === 'GET' || method === 'HEAD' if (expectContinue) { headers[HTTP2_HEADER_EXPECT] = '100-continue' + /** + * @type {import('node:http2').ClientHttp2Stream} + */ stream = session.request(headers, { endStream: shouldEndStream, signal }) stream.once('continue', writeBodyH2) } else { + /** @type {import('node:http2').ClientHttp2Stream} */ stream = session.request(headers, { endStream: shouldEndStream, signal @@ -6856,9 +6852,7 @@ function writeH2 (client, session, request) { ++h2State.openStreams stream.once('response', headers => { - const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers - - if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) { + if (request.onHeaders(Number(headers[HTTP2_HEADER_STATUS]), headers, stream.resume.bind(stream), '') === false) { stream.pause() } }) @@ -6868,17 +6862,13 @@ function writeH2 (client, session, request) { }) stream.on('data', (chunk) => { - if (request.onData(chunk) === false) { - stream.pause() - } + if (request.onData(chunk) === false) stream.pause() }) stream.once('close', () => { h2State.openStreams -= 1 // TODO(HTTP/2): unref only if current streams count is 0 - if (h2State.openStreams === 0) { - session.unref() - } + if (h2State.openStreams === 0) session.unref() }) stream.once('error', function (err) { @@ -7038,11 +7028,7 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength, } } const onAbort = function () { - if (finished) { - return - } - const err = new RequestAbortedError() - queueMicrotask(() => onFinished(err)) + onFinished(new RequestAbortedError()) } const onFinished = function (err) { if (finished) { @@ -8446,132 +8432,6 @@ function onConnectTimeout (socket) { module.exports = buildConnector -/***/ }), - -/***/ 4462: -/***/ ((module) => { - -"use strict"; - - -/** @type {Record} */ -const headerNameLowerCasedRecord = {} - -// https://developer.mozilla.org/docs/Web/HTTP/Headers -const wellknownHeaderNames = [ - 'Accept', - 'Accept-Encoding', - 'Accept-Language', - 'Accept-Ranges', - 'Access-Control-Allow-Credentials', - 'Access-Control-Allow-Headers', - 'Access-Control-Allow-Methods', - 'Access-Control-Allow-Origin', - 'Access-Control-Expose-Headers', - 'Access-Control-Max-Age', - 'Access-Control-Request-Headers', - 'Access-Control-Request-Method', - 'Age', - 'Allow', - 'Alt-Svc', - 'Alt-Used', - 'Authorization', - 'Cache-Control', - 'Clear-Site-Data', - 'Connection', - 'Content-Disposition', - 'Content-Encoding', - 'Content-Language', - 'Content-Length', - 'Content-Location', - 'Content-Range', - 'Content-Security-Policy', - 'Content-Security-Policy-Report-Only', - 'Content-Type', - 'Cookie', - 'Cross-Origin-Embedder-Policy', - 'Cross-Origin-Opener-Policy', - 'Cross-Origin-Resource-Policy', - 'Date', - 'Device-Memory', - 'Downlink', - 'ECT', - 'ETag', - 'Expect', - 'Expect-CT', - 'Expires', - 'Forwarded', - 'From', - 'Host', - 'If-Match', - 'If-Modified-Since', - 'If-None-Match', - 'If-Range', - 'If-Unmodified-Since', - 'Keep-Alive', - 'Last-Modified', - 'Link', - 'Location', - 'Max-Forwards', - 'Origin', - 'Permissions-Policy', - 'Pragma', - 'Proxy-Authenticate', - 'Proxy-Authorization', - 'RTT', - 'Range', - 'Referer', - 'Referrer-Policy', - 'Refresh', - 'Retry-After', - 'Sec-WebSocket-Accept', - 'Sec-WebSocket-Extensions', - 'Sec-WebSocket-Key', - 'Sec-WebSocket-Protocol', - 'Sec-WebSocket-Version', - 'Server', - 'Server-Timing', - 'Service-Worker-Allowed', - 'Service-Worker-Navigation-Preload', - 'Set-Cookie', - 'SourceMap', - 'Strict-Transport-Security', - 'Supports-Loading-Mode', - 'TE', - 'Timing-Allow-Origin', - 'Trailer', - 'Transfer-Encoding', - 'Upgrade', - 'Upgrade-Insecure-Requests', - 'User-Agent', - 'Vary', - 'Via', - 'WWW-Authenticate', - 'X-Content-Type-Options', - 'X-DNS-Prefetch-Control', - 'X-Frame-Options', - 'X-Permitted-Cross-Domain-Policies', - 'X-Powered-By', - 'X-Requested-With', - 'X-XSS-Protection' -] - -for (let i = 0; i < wellknownHeaderNames.length; ++i) { - const key = wellknownHeaderNames[i] - const lowerCasedKey = key.toLowerCase() - headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = - lowerCasedKey -} - -// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. -Object.setPrototypeOf(headerNameLowerCasedRecord, null) - -module.exports = { - wellknownHeaderNames, - headerNameLowerCasedRecord -} - - /***/ }), /***/ 8045: @@ -8773,19 +8633,6 @@ class ResponseExceededMaxSizeError extends UndiciError { } } -class RequestRetryError extends UndiciError { - constructor (message, code, { headers, data }) { - super(message) - Error.captureStackTrace(this, RequestRetryError) - this.name = 'RequestRetryError' - this.message = message || 'Request retry error' - this.code = 'UND_ERR_REQ_RETRY' - this.statusCode = code - this.data = data - this.headers = headers - } -} - module.exports = { HTTPParserError, UndiciError, @@ -8805,8 +8652,7 @@ module.exports = { NotSupportedError, ResponseContentLengthMismatchError, BalancedPoolMissingUpstreamError, - ResponseExceededMaxSizeError, - RequestRetryError + ResponseExceededMaxSizeError } @@ -9048,9 +8894,9 @@ class Request { onBodySent (chunk) { if (this[kHandler].onBodySent) { try { - return this[kHandler].onBodySent(chunk) + this[kHandler].onBodySent(chunk) } catch (err) { - this.abort(err) + this.onError(err) } } } @@ -9062,9 +8908,9 @@ class Request { if (this[kHandler].onRequestSent) { try { - return this[kHandler].onRequestSent() + this[kHandler].onRequestSent() } catch (err) { - this.abort(err) + this.onError(err) } } } @@ -9089,23 +8935,14 @@ class Request { channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) } - try { - return this[kHandler].onHeaders(statusCode, headers, resume, statusText) - } catch (err) { - this.abort(err) - } + return this[kHandler].onHeaders(statusCode, headers, resume, statusText) } onData (chunk) { assert(!this.aborted) assert(!this.completed) - try { - return this[kHandler].onData(chunk) - } catch (err) { - this.abort(err) - return false - } + return this[kHandler].onData(chunk) } onUpgrade (statusCode, headers, socket) { @@ -9124,13 +8961,7 @@ class Request { if (channels.trailers.hasSubscribers) { channels.trailers.publish({ request: this, trailers }) } - - try { - return this[kHandler].onComplete(trailers) - } catch (err) { - // TODO (fix): This might be a bad idea? - this.onError(err) - } + return this[kHandler].onComplete(trailers) } onError (error) { @@ -9144,7 +8975,6 @@ class Request { return } this.aborted = true - return this[kHandler].onError(error) } @@ -9381,9 +9211,7 @@ module.exports = { kHTTP2BuildRequest: Symbol('http2 build request'), kHTTP1BuildRequest: Symbol('http1 build request'), kHTTP2CopyHeaders: Symbol('http2 copy headers'), - kHTTPConnVersion: Symbol('http connection version'), - kRetryHandlerDefaultRetry: Symbol('retry agent default retry'), - kConstruct: Symbol('constructable') + kHTTPConnVersion: Symbol('http connection version') } @@ -9404,7 +9232,6 @@ const { InvalidArgumentError } = __nccwpck_require__(8045) const { Blob } = __nccwpck_require__(4300) const nodeUtil = __nccwpck_require__(3837) const { stringify } = __nccwpck_require__(3477) -const { headerNameLowerCasedRecord } = __nccwpck_require__(4462) const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) @@ -9521,13 +9348,13 @@ function getHostname (host) { const idx = host.indexOf(']') assert(idx !== -1) - return host.substring(1, idx) + return host.substr(1, idx - 1) } const idx = host.indexOf(':') if (idx === -1) return host - return host.substring(0, idx) + return host.substr(0, idx) } // IP addresses are not valid server names per RFC6066 @@ -9614,15 +9441,6 @@ function parseKeepAliveTimeout (val) { return m ? parseInt(m[1], 10) * 1000 : null } -/** - * Retrieves a header name and returns its lowercase value. - * @param {string | Buffer} value Header name - * @returns {string} - */ -function headerNameToString (value) { - return headerNameLowerCasedRecord[value] || value.toLowerCase() -} - function parseHeaders (headers, obj = {}) { // For H2 support if (!Array.isArray(headers)) return headers @@ -9633,7 +9451,7 @@ function parseHeaders (headers, obj = {}) { if (!val) { if (Array.isArray(headers[i + 1])) { - obj[key] = headers[i + 1].map(x => x.toString('utf8')) + obj[key] = headers[i + 1] } else { obj[key] = headers[i + 1].toString('utf8') } @@ -9836,7 +9654,16 @@ function throwIfAborted (signal) { } } +let events function addAbortListener (signal, listener) { + if (typeof Symbol.dispose === 'symbol') { + if (!events) { + events = __nccwpck_require__(2361) + } + if (typeof events.addAbortListener === 'function' && 'aborted' in signal) { + return events.addAbortListener(signal, listener) + } + } if ('addEventListener' in signal) { signal.addEventListener('abort', listener, { once: true }) return () => signal.removeEventListener('abort', listener) @@ -9860,21 +9687,6 @@ function toUSVString (val) { return `${val}` } -// Parsed accordingly to RFC 9110 -// https://www.rfc-editor.org/rfc/rfc9110#field.content-range -function parseRangeHeader (range) { - if (range == null || range === '') return { start: 0, end: null, size: null } - - const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null - return m - ? { - start: parseInt(m[1]), - end: m[2] ? parseInt(m[2]) : null, - size: m[3] ? parseInt(m[3]) : null - } - : null -} - const kEnumerableProperty = Object.create(null) kEnumerableProperty.enumerable = true @@ -9894,7 +9706,6 @@ module.exports = { isIterable, isAsyncIterable, isDestroyed, - headerNameToString, parseRawHeaders, parseHeaders, parseKeepAliveTimeout, @@ -9909,11 +9720,9 @@ module.exports = { buildURL, throwIfAborted, addAbortListener, - parseRangeHeader, nodeMajor, nodeMinor, - nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13), - safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'] + nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13) } @@ -11042,14 +10851,17 @@ function dataURLProcessor (dataURL) { * @param {boolean} excludeFragment */ function URLSerializer (url, excludeFragment = false) { + const href = url.href + if (!excludeFragment) { - return url.href + return href } - const href = url.href - const hashLength = url.hash.length - - return hashLength === 0 ? href : href.substring(0, href.length - hashLength) + const hash = href.lastIndexOf('#') + if (hash === -1) { + return href + } + return href.slice(0, hash) } // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points @@ -12233,7 +12045,7 @@ module.exports = { -const { kHeadersList, kConstruct } = __nccwpck_require__(2785) +const { kHeadersList } = __nccwpck_require__(2785) const { kGuard } = __nccwpck_require__(5861) const { kEnumerableProperty } = __nccwpck_require__(3983) const { @@ -12247,13 +12059,6 @@ const assert = __nccwpck_require__(9491) const kHeadersMap = Symbol('headers map') const kHeadersSortedMap = Symbol('headers map sorted') -/** - * @param {number} code - */ -function isHTTPWhiteSpaceCharCode (code) { - return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020 -} - /** * @see https://fetch.spec.whatwg.org/#concept-header-value-normalize * @param {string} potentialValue @@ -12262,12 +12067,12 @@ function headerValueNormalize (potentialValue) { // To normalize a byte sequence potentialValue, remove // any leading and trailing HTTP whitespace bytes from // potentialValue. - let i = 0; let j = potentialValue.length - while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j - while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i - - return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j) + // Trimming the end with `.replace()` and a RegExp is typically subject to + // ReDoS. This is safer and faster. + let i = potentialValue.length + while (/[\r\n\t ]/.test(potentialValue.charAt(--i))); + return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '') } function fill (headers, object) { @@ -12276,8 +12081,7 @@ function fill (headers, object) { // 1. If object is a sequence, then for each header in object: // Note: webidl conversion to array has already been done. if (Array.isArray(object)) { - for (let i = 0; i < object.length; ++i) { - const header = object[i] + for (const header of object) { // 1. If header does not contain exactly two items, then throw a TypeError. if (header.length !== 2) { throw webidl.errors.exception({ @@ -12287,16 +12091,15 @@ function fill (headers, object) { } // 2. Append (header’s first item, header’s second item) to headers. - appendHeader(headers, header[0], header[1]) + headers.append(header[0], header[1]) } } else if (typeof object === 'object' && object !== null) { // Note: null should throw // 2. Otherwise, object is a record, then for each key → value in object, // append (key, value) to headers - const keys = Object.keys(object) - for (let i = 0; i < keys.length; ++i) { - appendHeader(headers, keys[i], object[keys[i]]) + for (const [key, value] of Object.entries(object)) { + headers.append(key, value) } } else { throw webidl.errors.conversionFailed({ @@ -12307,50 +12110,6 @@ function fill (headers, object) { } } -/** - * @see https://fetch.spec.whatwg.org/#concept-headers-append - */ -function appendHeader (headers, name, value) { - // 1. Normalize value. - value = headerValueNormalize(value) - - // 2. If name is not a header name or value is not a - // header value, then throw a TypeError. - if (!isValidHeaderName(name)) { - throw webidl.errors.invalidArgument({ - prefix: 'Headers.append', - value: name, - type: 'header name' - }) - } else if (!isValidHeaderValue(value)) { - throw webidl.errors.invalidArgument({ - prefix: 'Headers.append', - value, - type: 'header value' - }) - } - - // 3. If headers’s guard is "immutable", then throw a TypeError. - // 4. Otherwise, if headers’s guard is "request" and name is a - // forbidden header name, return. - // Note: undici does not implement forbidden header names - if (headers[kGuard] === 'immutable') { - throw new TypeError('immutable') - } else if (headers[kGuard] === 'request-no-cors') { - // 5. Otherwise, if headers’s guard is "request-no-cors": - // TODO - } - - // 6. Otherwise, if headers’s guard is "response" and name is a - // forbidden response-header name, return. - - // 7. Append (name, value) to headers’s header list. - return headers[kHeadersList].append(name, value) - - // 8. If headers’s guard is "request-no-cors", then remove - // privileged no-CORS request headers from headers -} - class HeadersList { /** @type {[string, string][]|null} */ cookies = null @@ -12359,7 +12118,7 @@ class HeadersList { if (init instanceof HeadersList) { this[kHeadersMap] = new Map(init[kHeadersMap]) this[kHeadersSortedMap] = init[kHeadersSortedMap] - this.cookies = init.cookies === null ? null : [...init.cookies] + this.cookies = init.cookies } else { this[kHeadersMap] = new Map(init) this[kHeadersSortedMap] = null @@ -12421,7 +12180,7 @@ class HeadersList { // the first such header to value and remove the // others. // 2. Otherwise, append header (name, value) to list. - this[kHeadersMap].set(lowercaseName, { name, value }) + return this[kHeadersMap].set(lowercaseName, { name, value }) } // https://fetch.spec.whatwg.org/#concept-header-list-delete @@ -12434,18 +12193,20 @@ class HeadersList { this.cookies = null } - this[kHeadersMap].delete(name) + return this[kHeadersMap].delete(name) } // https://fetch.spec.whatwg.org/#concept-header-list-get get (name) { - const value = this[kHeadersMap].get(name.toLowerCase()) - // 1. If list does not contain name, then return null. + if (!this.contains(name)) { + return null + } + // 2. Return the values of all headers in list whose name // is a byte-case-insensitive match for name, // separated from each other by 0x2C 0x20, in order. - return value === undefined ? null : value.value + return this[kHeadersMap].get(name.toLowerCase())?.value ?? null } * [Symbol.iterator] () { @@ -12471,9 +12232,6 @@ class HeadersList { // https://fetch.spec.whatwg.org/#headers-class class Headers { constructor (init = undefined) { - if (init === kConstruct) { - return - } this[kHeadersList] = new HeadersList() // The new Headers(init) constructor steps are: @@ -12497,7 +12255,43 @@ class Headers { name = webidl.converters.ByteString(name) value = webidl.converters.ByteString(value) - return appendHeader(this, name, value) + // 1. Normalize value. + value = headerValueNormalize(value) + + // 2. If name is not a header name or value is not a + // header value, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value: name, + type: 'header name' + }) + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value, + type: 'header value' + }) + } + + // 3. If headers’s guard is "immutable", then throw a TypeError. + // 4. Otherwise, if headers’s guard is "request" and name is a + // forbidden header name, return. + // Note: undici does not implement forbidden header names + if (this[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (this[kGuard] === 'request-no-cors') { + // 5. Otherwise, if headers’s guard is "request-no-cors": + // TODO + } + + // 6. Otherwise, if headers’s guard is "response" and name is a + // forbidden response-header name, return. + + // 7. Append (name, value) to headers’s header list. + // 8. If headers’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from headers + return this[kHeadersList].append(name, value) } // https://fetch.spec.whatwg.org/#dom-headers-delete @@ -12542,7 +12336,7 @@ class Headers { // 7. Delete name from this’s header list. // 8. If this’s guard is "request-no-cors", then remove // privileged no-CORS request headers from this. - this[kHeadersList].delete(name) + return this[kHeadersList].delete(name) } // https://fetch.spec.whatwg.org/#dom-headers-get @@ -12635,7 +12429,7 @@ class Headers { // 7. Set (name, value) in this’s header list. // 8. If this’s guard is "request-no-cors", then remove // privileged no-CORS request headers from this - this[kHeadersList].set(name, value) + return this[kHeadersList].set(name, value) } // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie @@ -12671,8 +12465,7 @@ class Headers { const cookies = this[kHeadersList].cookies // 3. For each name of names: - for (let i = 0; i < names.length; ++i) { - const [name, value] = names[i] + for (const [name, value] of names) { // 1. If name is `set-cookie`, then: if (name === 'set-cookie') { // 1. Let values be a list of all values of headers in list whose name @@ -12680,8 +12473,8 @@ class Headers { // 2. For each value of values: // 1. Append (name, value) to headers. - for (let j = 0; j < cookies.length; ++j) { - headers.push([name, cookies[j]]) + for (const value of cookies) { + headers.push([name, value]) } } else { // 2. Otherwise: @@ -12705,12 +12498,6 @@ class Headers { keys () { webidl.brandCheck(this, Headers) - if (this[kGuard] === 'immutable') { - const value = this[kHeadersSortedMap] - return makeIterator(() => value, 'Headers', - 'key') - } - return makeIterator( () => [...this[kHeadersSortedMap].values()], 'Headers', @@ -12721,12 +12508,6 @@ class Headers { values () { webidl.brandCheck(this, Headers) - if (this[kGuard] === 'immutable') { - const value = this[kHeadersSortedMap] - return makeIterator(() => value, 'Headers', - 'value') - } - return makeIterator( () => [...this[kHeadersSortedMap].values()], 'Headers', @@ -12737,12 +12518,6 @@ class Headers { entries () { webidl.brandCheck(this, Headers) - if (this[kGuard] === 'immutable') { - const value = this[kHeadersSortedMap] - return makeIterator(() => value, 'Headers', - 'key+value') - } - return makeIterator( () => [...this[kHeadersSortedMap].values()], 'Headers', @@ -13114,7 +12889,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') { } // 8. If response’s timing allow passed flag is not set, then: - if (!response.timingAllowPassed) { + if (!timingInfo.timingAllowPassed) { // 1. Set timingInfo to a the result of creating an opaque timing info for timingInfo. timingInfo = createOpaqueTimingInfo({ startTime: timingInfo.startTime @@ -14031,9 +13806,6 @@ function httpRedirectFetch (fetchParams, response) { // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name request.headersList.delete('authorization') - // https://fetch.spec.whatwg.org/#authentication-entries - request.headersList.delete('proxy-authorization', true) - // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement. request.headersList.delete('cookie') request.headersList.delete('host') @@ -14788,7 +14560,7 @@ async function httpNetworkFetch ( path: url.pathname + url.search, origin: url.origin, method: request.method, - body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body, + body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body, headers: request.headersList.entries, maxRedirections: 0, upgrade: request.mode === 'websocket' ? 'websocket' : undefined @@ -14833,7 +14605,7 @@ async function httpNetworkFetch ( location = val } - headers[kHeadersList].append(key, val) + headers.append(key, val) } } else { const keys = Object.keys(headersList) @@ -14847,7 +14619,7 @@ async function httpNetworkFetch ( location = val } - headers[kHeadersList].append(key, val) + headers.append(key, val) } } @@ -14951,7 +14723,7 @@ async function httpNetworkFetch ( const key = headersList[n + 0].toString('latin1') const val = headersList[n + 1].toString('latin1') - headers[kHeadersList].append(key, val) + headers.append(key, val) } resolve({ @@ -14994,8 +14766,7 @@ const { isValidHTTPToken, sameOrigin, normalizeMethod, - makePolicyContainer, - normalizeMethodRecord + makePolicyContainer } = __nccwpck_require__(2538) const { forbiddenMethodsSet, @@ -15012,12 +14783,13 @@ const { kHeaders, kSignal, kState, kGuard, kRealm } = __nccwpck_require__(5861) const { webidl } = __nccwpck_require__(1744) const { getGlobalOrigin } = __nccwpck_require__(1246) const { URLSerializer } = __nccwpck_require__(685) -const { kHeadersList, kConstruct } = __nccwpck_require__(2785) +const { kHeadersList } = __nccwpck_require__(2785) const assert = __nccwpck_require__(9491) const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = __nccwpck_require__(2361) let TransformStream = globalThis.TransformStream +const kInit = Symbol('init') const kAbortController = Symbol('abortController') const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { @@ -15028,7 +14800,7 @@ const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { class Request { // https://fetch.spec.whatwg.org/#dom-request constructor (input, init = {}) { - if (input === kConstruct) { + if (input === kInit) { return } @@ -15167,10 +14939,8 @@ class Request { urlList: [...request.urlList] }) - const initHasKey = Object.keys(init).length !== 0 - // 13. If init is not empty, then: - if (initHasKey) { + if (Object.keys(init).length > 0) { // 1. If request’s mode is "navigate", then set it to "same-origin". if (request.mode === 'navigate') { request.mode = 'same-origin' @@ -15285,7 +15055,7 @@ class Request { } // 23. If init["integrity"] exists, then set request’s integrity metadata to it. - if (init.integrity != null) { + if (init.integrity !== undefined && init.integrity != null) { request.integrity = String(init.integrity) } @@ -15301,16 +15071,16 @@ class Request { // 2. If method is not a method or method is a forbidden method, then // throw a TypeError. - if (!isValidHTTPToken(method)) { - throw new TypeError(`'${method}' is not a valid HTTP method.`) + if (!isValidHTTPToken(init.method)) { + throw TypeError(`'${init.method}' is not a valid HTTP method.`) } if (forbiddenMethodsSet.has(method.toUpperCase())) { - throw new TypeError(`'${method}' HTTP method is unsupported.`) + throw TypeError(`'${init.method}' HTTP method is unsupported.`) } // 3. Normalize method. - method = normalizeMethodRecord[method] ?? normalizeMethod(method) + method = normalizeMethod(init.method) // 4. Set request’s method to method. request.method = method @@ -15381,7 +15151,7 @@ class Request { // 30. Set this’s headers to a new Headers object with this’s relevant // Realm, whose header list is request’s header list and guard is // "request". - this[kHeaders] = new Headers(kConstruct) + this[kHeaders] = new Headers() this[kHeaders][kHeadersList] = request.headersList this[kHeaders][kGuard] = 'request' this[kHeaders][kRealm] = this[kRealm] @@ -15401,25 +15171,25 @@ class Request { } // 32. If init is not empty, then: - if (initHasKey) { - /** @type {HeadersList} */ - const headersList = this[kHeaders][kHeadersList] + if (Object.keys(init).length !== 0) { // 1. Let headers be a copy of this’s headers and its associated header // list. + let headers = new Headers(this[kHeaders]) + // 2. If init["headers"] exists, then set headers to init["headers"]. - const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList) + if (init.headers !== undefined) { + headers = init.headers + } // 3. Empty this’s headers’s header list. - headersList.clear() + this[kHeaders][kHeadersList].clear() // 4. If headers is a Headers object, then for each header in its header // list, append header’s name/header’s value to this’s headers. - if (headers instanceof HeadersList) { + if (headers.constructor.name === 'Headers') { for (const [key, val] of headers) { - headersList.append(key, val) + this[kHeaders].append(key, val) } - // Note: Copy the `set-cookie` meta-data. - headersList.cookies = headers.cookies } else { // 5. Otherwise, fill this’s headers with headers. fillHeaders(this[kHeaders], headers) @@ -15708,10 +15478,10 @@ class Request { // 3. Let clonedRequestObject be the result of creating a Request object, // given clonedRequest, this’s headers’s guard, and this’s relevant Realm. - const clonedRequestObject = new Request(kConstruct) + const clonedRequestObject = new Request(kInit) clonedRequestObject[kState] = clonedRequest clonedRequestObject[kRealm] = this[kRealm] - clonedRequestObject[kHeaders] = new Headers(kConstruct) + clonedRequestObject[kHeaders] = new Headers() clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard] clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm] @@ -15961,7 +15731,7 @@ const { webidl } = __nccwpck_require__(1744) const { FormData } = __nccwpck_require__(2015) const { getGlobalOrigin } = __nccwpck_require__(1246) const { URLSerializer } = __nccwpck_require__(685) -const { kHeadersList, kConstruct } = __nccwpck_require__(2785) +const { kHeadersList } = __nccwpck_require__(2785) const assert = __nccwpck_require__(9491) const { types } = __nccwpck_require__(3837) @@ -16082,7 +15852,7 @@ class Response { // 2. Set this’s headers to a new Headers object with this’s relevant // Realm, whose header list is this’s response’s header list and guard // is "response". - this[kHeaders] = new Headers(kConstruct) + this[kHeaders] = new Headers() this[kHeaders][kGuard] = 'response' this[kHeaders][kHeadersList] = this[kState].headersList this[kHeaders][kRealm] = this[kRealm] @@ -16452,7 +16222,11 @@ webidl.converters.XMLHttpRequestBodyInit = function (V) { return webidl.converters.Blob(V, { strict: false }) } - if (types.isArrayBuffer(V) || types.isTypedArray(V) || types.isDataView(V)) { + if ( + types.isAnyArrayBuffer(V) || + types.isTypedArray(V) || + types.isDataView(V) + ) { return webidl.converters.BufferSource(V) } @@ -16542,18 +16316,14 @@ const { isBlobLike, toUSVString, ReadableStreamFrom } = __nccwpck_require__(3983 const assert = __nccwpck_require__(9491) const { isUint8Array } = __nccwpck_require__(9830) -let supportedHashes = [] - // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable /** @type {import('crypto')|undefined} */ let crypto try { crypto = __nccwpck_require__(6113) - const possibleRelevantHashes = ['sha256', 'sha384', 'sha512'] - supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)) -/* c8 ignore next 3 */ } catch { + } function responseURL (response) { @@ -16642,57 +16412,52 @@ function isValidReasonPhrase (statusText) { return true } -/** - * @see https://tools.ietf.org/html/rfc7230#section-3.2.6 - * @param {number} c - */ -function isTokenCharCode (c) { - switch (c) { - case 0x22: - case 0x28: - case 0x29: - case 0x2c: - case 0x2f: - case 0x3a: - case 0x3b: - case 0x3c: - case 0x3d: - case 0x3e: - case 0x3f: - case 0x40: - case 0x5b: - case 0x5c: - case 0x5d: - case 0x7b: - case 0x7d: - // DQUOTE and "(),/:;<=>?@[\]{}" - return false - default: - // VCHAR %x21-7E - return c >= 0x21 && c <= 0x7e - } +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 === '}' + ) } -/** - * @param {string} characters - */ +// 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.length === 0) { + if (!characters || typeof characters !== 'string') { return false } for (let i = 0; i < characters.length; ++i) { - if (!isTokenCharCode(characters.charCodeAt(i))) { + const c = characters.charCodeAt(i) + if (c > 0x7f || !isTokenChar(c)) { return false } } return true } -/** - * @see https://fetch.spec.whatwg.org/#header-name - * @param {string} potentialValue - */ +// 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) } @@ -17081,56 +16846,66 @@ function bytesMatch (bytes, metadataList) { return true } - // 3. If response is not eligible for integrity validation, return false. - // TODO - - // 4. If parsedMetadata is the empty set, return true. + // 3. If parsedMetadata is the empty set, return true. if (parsedMetadata.length === 0) { return true } - // 5. Let metadata be the result of getting the strongest + // 4. Let metadata be the result of getting the strongest // metadata from parsedMetadata. - const strongest = getStrongestMetadata(parsedMetadata) - const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest) + 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) - // 6. For each item in metadata: + // 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 + let expectedValue = item.hash // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e // "be liberal with padding". This is annoying, and it's not even in the spec. + if (expectedValue.endsWith('==')) { + expectedValue = expectedValue.slice(0, -2) + } + // 3. Let actualValue be the result of applying algorithm to bytes. let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') - if (actualValue[actualValue.length - 1] === '=') { - if (actualValue[actualValue.length - 2] === '=') { - actualValue = actualValue.slice(0, -2) - } else { - actualValue = actualValue.slice(0, -1) - } + if (actualValue.endsWith('==')) { + actualValue = actualValue.slice(0, -2) } // 4. If actualValue is a case-sensitive match for expectedValue, // return true. - if (compareBase64Mixed(actualValue, expectedValue)) { + if (actualValue === expectedValue) { + return true + } + + let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url') + + if (actualBase64URL.endsWith('==')) { + actualBase64URL = actualBase64URL.slice(0, -2) + } + + if (actualBase64URL === expectedValue) { return true } } - // 7. Return false. + // 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 = /(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i +const parseHashWithOptions = /((?sha256|sha384|sha512)-(?[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i /** * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata @@ -17144,6 +16919,8 @@ function parseMetadata (metadata) { // 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. @@ -17153,11 +16930,7 @@ function parseMetadata (metadata) { const parsedToken = parseHashWithOptions.exec(token) // 3. If token does not parse, continue to the next token. - if ( - parsedToken === null || - parsedToken.groups === undefined || - parsedToken.groups.algo === undefined - ) { + 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 @@ -17166,11 +16939,11 @@ function parseMetadata (metadata) { } // 4. Let algorithm be the hash-algo component of token. - const algorithm = parsedToken.groups.algo.toLowerCase() + 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)) { + if (supportedHashes.includes(algorithm.toLowerCase())) { result.push(parsedToken.groups) } } @@ -17183,82 +16956,6 @@ function parseMetadata (metadata) { return result } -/** - * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList - */ -function getStrongestMetadata (metadataList) { - // Let algorithm be the algo component of the first item in metadataList. - // Can be sha256 - let algorithm = metadataList[0].algo - // If the algorithm is sha512, then it is the strongest - // and we can return immediately - if (algorithm[3] === '5') { - return algorithm - } - - for (let i = 1; i < metadataList.length; ++i) { - const metadata = metadataList[i] - // If the algorithm is sha512, then it is the strongest - // and we can break the loop immediately - if (metadata.algo[3] === '5') { - algorithm = 'sha512' - break - // If the algorithm is sha384, then a potential sha256 or sha384 is ignored - } else if (algorithm[3] === '3') { - continue - // algorithm is sha256, check if algorithm is sha384 and if so, set it as - // the strongest - } else if (metadata.algo[3] === '3') { - algorithm = 'sha384' - } - } - return algorithm -} - -function filterMetadataListByAlgorithm (metadataList, algorithm) { - if (metadataList.length === 1) { - return metadataList - } - - let pos = 0 - for (let i = 0; i < metadataList.length; ++i) { - if (metadataList[i].algo === algorithm) { - metadataList[pos++] = metadataList[i] - } - } - - metadataList.length = pos - - return metadataList -} - -/** - * Compares two base64 strings, allowing for base64url - * in the second string. - * -* @param {string} actualValue always base64 - * @param {string} expectedValue base64 or base64url - * @returns {boolean} - */ -function compareBase64Mixed (actualValue, expectedValue) { - if (actualValue.length !== expectedValue.length) { - return false - } - for (let i = 0; i < actualValue.length; ++i) { - if (actualValue[i] !== expectedValue[i]) { - if ( - (actualValue[i] === '+' && expectedValue[i] === '-') || - (actualValue[i] === '/' && expectedValue[i] === '_') - ) { - continue - } - return false - } - } - - return true -} - // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { // TODO @@ -17305,30 +17002,11 @@ function isCancelled (fetchParams) { fetchParams.controller.state === 'terminated' } -const normalizeMethodRecord = { - delete: 'DELETE', - DELETE: 'DELETE', - get: 'GET', - GET: 'GET', - head: 'HEAD', - HEAD: 'HEAD', - options: 'OPTIONS', - OPTIONS: 'OPTIONS', - post: 'POST', - POST: 'POST', - put: 'PUT', - PUT: 'PUT' -} - -// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. -Object.setPrototypeOf(normalizeMethodRecord, null) - -/** - * @see https://fetch.spec.whatwg.org/#concept-method-normalize - * @param {string} method - */ +// https://fetch.spec.whatwg.org/#concept-method-normalize function normalizeMethod (method) { - return normalizeMethodRecord[method.toLowerCase()] ?? 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 @@ -17673,9 +17351,7 @@ module.exports = { urlIsLocal, urlHasHttpsScheme, urlIsHttpHttpsScheme, - readAllBytes, - normalizeMethodRecord, - parseMetadata + readAllBytes } @@ -18114,10 +17790,12 @@ webidl.converters.ByteString = function (V) { // 2. If the value of any element of x is greater than // 255, then throw a TypeError. for (let index = 0; index < x.length; index++) { - if (x.charCodeAt(index) > 255) { + const charCode = x.charCodeAt(index) + + if (charCode > 255) { throw new TypeError( 'Cannot convert argument to a ByteString because the character at ' + - `index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.` + `index ${index} has a value of ${charCode} which is greater than 255.` ) } } @@ -19762,17 +19440,12 @@ function parseLocation (statusCode, headers) { // https://tools.ietf.org/html/rfc7231#section-6.4.4 function shouldRemoveHeader (header, removeContent, unknownOrigin) { - if (header.length === 4) { - return util.headerNameToString(header) === 'host' - } - if (removeContent && util.headerNameToString(header).startsWith('content-')) { - return true - } - if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { - const name = util.headerNameToString(header) - return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization' - } - return false + return ( + (header.length === 4 && header.toString().toLowerCase() === 'host') || + (removeContent && header.toString().toLowerCase().indexOf('content-') === 0) || + (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') || + (unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie') + ) } // https://tools.ietf.org/html/rfc7231#section-6.4 @@ -19799,349 +19472,6 @@ function cleanRequestHeaders (headers, removeContent, unknownOrigin) { module.exports = RedirectHandler -/***/ }), - -/***/ 9609: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const assert = __nccwpck_require__(9491) - -const { kRetryHandlerDefaultRetry } = __nccwpck_require__(2785) -const { RequestRetryError } = __nccwpck_require__(8045) -const { isDisturbed, parseHeaders, parseRangeHeader } = __nccwpck_require__(3983) - -function calculateRetryAfterHeader (retryAfter) { - const current = Date.now() - const diff = new Date(retryAfter).getTime() - current - - return diff -} - -class RetryHandler { - constructor (opts, handlers) { - const { retryOptions, ...dispatchOpts } = opts - const { - // Retry scoped - retry: retryFn, - maxRetries, - maxTimeout, - minTimeout, - timeoutFactor, - // Response scoped - methods, - errorCodes, - retryAfter, - statusCodes - } = retryOptions ?? {} - - this.dispatch = handlers.dispatch - this.handler = handlers.handler - this.opts = dispatchOpts - this.abort = null - this.aborted = false - this.retryOpts = { - retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry], - retryAfter: retryAfter ?? true, - maxTimeout: maxTimeout ?? 30 * 1000, // 30s, - timeout: minTimeout ?? 500, // .5s - timeoutFactor: timeoutFactor ?? 2, - maxRetries: maxRetries ?? 5, - // What errors we should retry - methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'], - // Indicates which errors to retry - statusCodes: statusCodes ?? [500, 502, 503, 504, 429], - // List of errors to retry - errorCodes: errorCodes ?? [ - 'ECONNRESET', - 'ECONNREFUSED', - 'ENOTFOUND', - 'ENETDOWN', - 'ENETUNREACH', - 'EHOSTDOWN', - 'EHOSTUNREACH', - 'EPIPE' - ] - } - - this.retryCount = 0 - this.start = 0 - this.end = null - this.etag = null - this.resume = null - - // Handle possible onConnect duplication - this.handler.onConnect(reason => { - this.aborted = true - if (this.abort) { - this.abort(reason) - } else { - this.reason = reason - } - }) - } - - onRequestSent () { - if (this.handler.onRequestSent) { - this.handler.onRequestSent() - } - } - - onUpgrade (statusCode, headers, socket) { - if (this.handler.onUpgrade) { - this.handler.onUpgrade(statusCode, headers, socket) - } - } - - onConnect (abort) { - if (this.aborted) { - abort(this.reason) - } else { - this.abort = abort - } - } - - onBodySent (chunk) { - if (this.handler.onBodySent) return this.handler.onBodySent(chunk) - } - - static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) { - const { statusCode, code, headers } = err - const { method, retryOptions } = opts - const { - maxRetries, - timeout, - maxTimeout, - timeoutFactor, - statusCodes, - errorCodes, - methods - } = retryOptions - let { counter, currentTimeout } = state - - currentTimeout = - currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout - - // Any code that is not a Undici's originated and allowed to retry - if ( - code && - code !== 'UND_ERR_REQ_RETRY' && - code !== 'UND_ERR_SOCKET' && - !errorCodes.includes(code) - ) { - cb(err) - return - } - - // If a set of method are provided and the current method is not in the list - if (Array.isArray(methods) && !methods.includes(method)) { - cb(err) - return - } - - // If a set of status code are provided and the current status code is not in the list - if ( - statusCode != null && - Array.isArray(statusCodes) && - !statusCodes.includes(statusCode) - ) { - cb(err) - return - } - - // If we reached the max number of retries - if (counter > maxRetries) { - cb(err) - return - } - - let retryAfterHeader = headers != null && headers['retry-after'] - if (retryAfterHeader) { - retryAfterHeader = Number(retryAfterHeader) - retryAfterHeader = isNaN(retryAfterHeader) - ? calculateRetryAfterHeader(retryAfterHeader) - : retryAfterHeader * 1e3 // Retry-After is in seconds - } - - const retryTimeout = - retryAfterHeader > 0 - ? Math.min(retryAfterHeader, maxTimeout) - : Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout) - - state.currentTimeout = retryTimeout - - setTimeout(() => cb(null), retryTimeout) - } - - onHeaders (statusCode, rawHeaders, resume, statusMessage) { - const headers = parseHeaders(rawHeaders) - - this.retryCount += 1 - - if (statusCode >= 300) { - this.abort( - new RequestRetryError('Request failed', statusCode, { - headers, - count: this.retryCount - }) - ) - return false - } - - // Checkpoint for resume from where we left it - if (this.resume != null) { - this.resume = null - - if (statusCode !== 206) { - return true - } - - const contentRange = parseRangeHeader(headers['content-range']) - // If no content range - if (!contentRange) { - this.abort( - new RequestRetryError('Content-Range mismatch', statusCode, { - headers, - count: this.retryCount - }) - ) - return false - } - - // Let's start with a weak etag check - if (this.etag != null && this.etag !== headers.etag) { - this.abort( - new RequestRetryError('ETag mismatch', statusCode, { - headers, - count: this.retryCount - }) - ) - return false - } - - const { start, size, end = size } = contentRange - - assert(this.start === start, 'content-range mismatch') - assert(this.end == null || this.end === end, 'content-range mismatch') - - this.resume = resume - return true - } - - if (this.end == null) { - if (statusCode === 206) { - // First time we receive 206 - const range = parseRangeHeader(headers['content-range']) - - if (range == null) { - return this.handler.onHeaders( - statusCode, - rawHeaders, - resume, - statusMessage - ) - } - - const { start, size, end = size } = range - - assert( - start != null && Number.isFinite(start) && this.start !== start, - 'content-range mismatch' - ) - assert(Number.isFinite(start)) - assert( - end != null && Number.isFinite(end) && this.end !== end, - 'invalid content-length' - ) - - this.start = start - this.end = end - } - - // We make our best to checkpoint the body for further range headers - if (this.end == null) { - const contentLength = headers['content-length'] - this.end = contentLength != null ? Number(contentLength) : null - } - - assert(Number.isFinite(this.start)) - assert( - this.end == null || Number.isFinite(this.end), - 'invalid content-length' - ) - - this.resume = resume - this.etag = headers.etag != null ? headers.etag : null - - return this.handler.onHeaders( - statusCode, - rawHeaders, - resume, - statusMessage - ) - } - - const err = new RequestRetryError('Request failed', statusCode, { - headers, - count: this.retryCount - }) - - this.abort(err) - - return false - } - - onData (chunk) { - this.start += chunk.length - - return this.handler.onData(chunk) - } - - onComplete (rawTrailers) { - this.retryCount = 0 - return this.handler.onComplete(rawTrailers) - } - - onError (err) { - if (this.aborted || isDisturbed(this.opts.body)) { - return this.handler.onError(err) - } - - this.retryOpts.retry( - err, - { - state: { counter: this.retryCount++, currentTimeout: this.retryAfter }, - opts: { retryOptions: this.retryOpts, ...this.opts } - }, - onRetry.bind(this) - ) - - function onRetry (err) { - if (err != null || this.aborted || isDisturbed(this.opts.body)) { - return this.handler.onError(err) - } - - if (this.start !== 0) { - this.opts = { - ...this.opts, - headers: { - ...this.opts.headers, - range: `bytes=${this.start}-${this.end ?? ''}` - } - } - } - - try { - this.dispatch(this.opts, this) - } catch (err) { - this.handler.onError(err) - } - } - } -} - -module.exports = RetryHandler - - /***/ }), /***/ 8861: @@ -22064,9 +21394,6 @@ class ProxyAgent extends DispatcherBase { this[kProxyTls] = opts.proxyTls this[kProxyHeaders] = opts.headers || {} - const resolvedUrl = new URL(opts.uri) - const { origin, port, host, username, password } = resolvedUrl - if (opts.auth && opts.token) { throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token') } else if (opts.auth) { @@ -22074,10 +21401,11 @@ class ProxyAgent extends DispatcherBase { this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}` } else if (opts.token) { this[kProxyHeaders]['proxy-authorization'] = opts.token - } else if (username && password) { - this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}` } + const resolvedUrl = new URL(opts.uri) + const { origin, port, host } = resolvedUrl + const connect = buildConnector({ ...opts.proxyTls }) this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }) this[kClient] = clientFactory(resolvedUrl, { connect }) @@ -22101,7 +21429,7 @@ class ProxyAgent extends DispatcherBase { }) if (statusCode !== 200) { socket.on('error', () => {}).destroy() - callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`)) + callback(new RequestAbortedError('Proxy response !== 200 when HTTP Tunneling')) } if (opts.protocol !== 'https:') { callback(null, socket)