diff --git a/lib/core/constants.js b/lib/core/constants.js index 088cf47d80f..7efde2ef258 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -1,5 +1,9 @@ 'use strict' +const nullBodyStatus = /** @type {const} */ ([101, 204, 205, 304]) + +const redirectStatus = /** @type {const} */ ([301, 302, 303, 307, 308]) + /** * @see https://developer.mozilla.org/docs/Web/HTTP/Headers */ @@ -137,6 +141,8 @@ for (let i = 0; i < wellknownHeaderNames.length; ++i) { } module.exports = { + nullBodyStatus, + redirectStatus, wellknownHeaderNames, headerNameLowerCasedRecord, getHeaderNameAsBuffer diff --git a/lib/core/util.js b/lib/core/util.js index 23f979f4ede..fac3a8a9a37 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -11,8 +11,12 @@ const nodeUtil = require('node:util') const { stringify } = require('node:querystring') const { EventEmitter: EE } = require('node:events') const { InvalidArgumentError } = require('./errors') -const { headerNameLowerCasedRecord, getHeaderNameAsBuffer } = require('./constants') -const { nullBodyStatus, redirectStatusSet } = require('../web/fetch/constants') +const { + headerNameLowerCasedRecord, + getHeaderNameAsBuffer, + nullBodyStatus, + redirectStatus +} = require('./constants') const { tree } = require('./tree') const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) @@ -898,7 +902,7 @@ function decompress (request, response) { const willFollow = response.headers.get('location') && request.redirect === 'follow' && - redirectStatusSet.has(response.status) + redirectStatus.includes(response.status) if ( request.method === 'HEAD' || @@ -932,11 +936,53 @@ function getCodings (contentEncoding) { return codings } -let createInflate +// A Stream, which pipes the response to zlib.createInflate() or +// zlib.createInflateRaw() depending on the first byte of the Buffer. +// If the lower byte of the first byte is 0x08, then the stream is +// interpreted as a zlib stream, otherwise it's interpreted as a +// raw deflate stream. +class InflateStream extends stream.Transform { + #zlibOptions + + /** @param {zlib.ZlibOptions} [zlibOptions] */ + constructor (zlibOptions) { + super() + this.#zlibOptions = zlibOptions + } + + _transform (chunk, encoding, callback) { + if (!this._inflateStream) { + if (chunk.length === 0) { + callback() + return + } + this._inflateStream = (chunk[0] & 0x0F) === 0x08 + ? zlib.createInflate(this.#zlibOptions) + : zlib.createInflateRaw(this.#zlibOptions) + + this._inflateStream.on('data', this.push.bind(this)) + this._inflateStream.on('end', () => this.push(null)) + this._inflateStream.on('error', (err) => this.destroy(err)) + } + + this._inflateStream.write(chunk, encoding, callback) + } -function lazyCreateInflate (zlibOptions) { - createInflate ??= require('../web/fetch/util').createInflate - return createInflate(zlibOptions) + _final (callback) { + if (this._inflateStream) { + this._inflateStream.end() + this._inflateStream = null + } + callback() + } +} + +/** + * @param {zlib.ZlibOptions} [zlibOptions] + * @returns {InflateStream} + */ +function createInflate (zlibOptions) { + return new InflateStream(zlibOptions) } /** @@ -965,7 +1011,7 @@ function getDecoders (contentEncoding) { }) ) } else if (coding === 'deflate') { - decoders.push(lazyCreateInflate({ + decoders.push(createInflate({ flush: zlib.constants.Z_SYNC_FLUSH, finishFlush: zlib.constants.Z_SYNC_FLUSH })) diff --git a/lib/web/fetch/constants.js b/lib/web/fetch/constants.js index ef63b0c8e10..de53b52c0a9 100644 --- a/lib/web/fetch/constants.js +++ b/lib/web/fetch/constants.js @@ -1,11 +1,10 @@ 'use strict' +const { nullBodyStatus, redirectStatus } = require('../../core/constants') + const corsSafeListedMethods = /** @type {const} */ (['GET', 'HEAD', 'POST']) const corsSafeListedMethodsSet = new Set(corsSafeListedMethods) -const nullBodyStatus = /** @type {const} */ ([101, 204, 205, 304]) - -const redirectStatus = /** @type {const} */ ([301, 302, 303, 307, 308]) const redirectStatusSet = new Set(redirectStatus) /** diff --git a/lib/web/fetch/util.js b/lib/web/fetch/util.js index be6060058a3..e86cd45f6b8 100644 --- a/lib/web/fetch/util.js +++ b/lib/web/fetch/util.js @@ -1,7 +1,5 @@ 'use strict' -const { Transform } = require('node:stream') -const zlib = require('node:zlib') const { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require('./constants') const { getGlobalOrigin } = require('./global') const { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require('./data-url') @@ -1485,55 +1483,6 @@ function buildContentRange (rangeStart, rangeEnd, fullLength) { return contentRange } -// A Stream, which pipes the response to zlib.createInflate() or -// zlib.createInflateRaw() depending on the first byte of the Buffer. -// If the lower byte of the first byte is 0x08, then the stream is -// interpreted as a zlib stream, otherwise it's interpreted as a -// raw deflate stream. -class InflateStream extends Transform { - #zlibOptions - - /** @param {zlib.ZlibOptions} [zlibOptions] */ - constructor (zlibOptions) { - super() - this.#zlibOptions = zlibOptions - } - - _transform (chunk, encoding, callback) { - if (!this._inflateStream) { - if (chunk.length === 0) { - callback() - return - } - this._inflateStream = (chunk[0] & 0x0F) === 0x08 - ? zlib.createInflate(this.#zlibOptions) - : zlib.createInflateRaw(this.#zlibOptions) - - this._inflateStream.on('data', this.push.bind(this)) - this._inflateStream.on('end', () => this.push(null)) - this._inflateStream.on('error', (err) => this.destroy(err)) - } - - this._inflateStream.write(chunk, encoding, callback) - } - - _final (callback) { - if (this._inflateStream) { - this._inflateStream.end() - this._inflateStream = null - } - callback() - } -} - -/** - * @param {zlib.ZlibOptions} [zlibOptions] - * @returns {InflateStream} - */ -function createInflate (zlibOptions) { - return new InflateStream(zlibOptions) -} - /** * @see https://fetch.spec.whatwg.org/#concept-header-extract-mime-type * @param {import('./headers').HeadersList} headers @@ -1773,7 +1722,6 @@ module.exports = { simpleRangeHeaderValue, buildContentRange, parseMetadata, - createInflate, extractMimeType, getDecodeSplit, utf8DecodeBytes, diff --git a/test/decompress-stream.js b/test/decompress-stream.js index fdce17ddb1a..60426fd9ea8 100644 --- a/test/decompress-stream.js +++ b/test/decompress-stream.js @@ -3,6 +3,7 @@ const { test } = require('node:test') const assert = require('node:assert') const zlib = require('node:zlib') +const { Response } = require('../index-fetch') const { decompressStream } = require('../lib/core/util') test('returns the stream as-is given no content encoding', async () => {