UNPKG

http-encoding

Version:

Everything you need to handle HTTP message body content-encoding

276 lines 11.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeBase64 = exports.encodeBase64 = exports.zstdDecompress = exports.zstdCompress = exports.brotliDecompress = exports.brotliCompress = exports.inflateRaw = exports.inflate = exports.deflate = exports.gunzip = exports.gzip = void 0; exports.decodeBuffer = decodeBuffer; exports.decodeBufferSync = decodeBufferSync; exports.encodeBuffer = encodeBuffer; const zlib = require("zlib"); const promisify = require('pify'); exports.gzip = promisify(zlib.gzip); exports.gunzip = promisify(zlib.gunzip); exports.deflate = promisify(zlib.deflate); exports.inflate = promisify(zlib.inflate); exports.inflateRaw = promisify(zlib.inflateRaw); // Use Node's new built-in Brotli compression, if available, or // use the brotli-wasm package if not. exports.brotliCompress = zlib.brotliCompress ? ((buffer, level) => __awaiter(void 0, void 0, void 0, function* () { // In node, we just have to convert between the options formats and promisify: return new Promise((resolve, reject) => { zlib.brotliCompress(buffer, level !== undefined ? { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: level } } : {}, (err, result) => { if (err) reject(err); else resolve(result); }); }); })) : ((buffer, level) => __awaiter(void 0, void 0, void 0, function* () { const { compress } = yield Promise.resolve().then(() => require('brotli-wasm')); // Sync in node, async in browsers return compress(buffer, { quality: level }); })); exports.brotliDecompress = zlib.brotliDecompress ? promisify(zlib.brotliDecompress) : ((buffer) => __awaiter(void 0, void 0, void 0, function* () { const { decompress } = yield Promise.resolve().then(() => require('brotli-wasm')); // Sync in node, async in browsers return decompress(buffer); })); // Browser Zstd is a non-built-in wasm implementation that initializes async. We handle this // by loading it when the first zstd buffer is decompressed. That lets us defer loading // until that point too, which is good since it's large-ish & rarely used. let zstd; const getZstd = () => __awaiter(void 0, void 0, void 0, function* () { // In Node 22.15 / 23.8+, we can use zstd built-in: if (zlib.zstdCompress && zlib.zstdDecompress) { return { compress: (buffer, level) => { return new Promise((resolve, reject) => { const options = level !== undefined ? { [zlib.constants.ZSTD_c_compressionLevel]: level } : {}; zlib.zstdCompress(buffer, options, (err, result) => { if (err) reject(err); else resolve(result); }); }); }, decompress: (buffer) => { return new Promise((resolve, reject) => { zlib.zstdDecompress(buffer, (err, result) => { if (err) reject(err); else resolve(result); }); }); } }; } // In older Node and browsers, we fall back to zstd-codec: else if (!zstd) { zstd = new Promise((resolve) => __awaiter(void 0, void 0, void 0, function* () { const { ZstdCodec } = yield Promise.resolve().then(() => require('zstd-codec')); ZstdCodec.run((binding) => { resolve(new binding.Streaming()); }); })); } return yield zstd; }); const zstdCompress = (buffer, level) => __awaiter(void 0, void 0, void 0, function* () { return (yield getZstd()).compress(buffer, level); }); exports.zstdCompress = zstdCompress; const zstdDecompress = (buffer) => __awaiter(void 0, void 0, void 0, function* () { return (yield getZstd()).decompress(buffer); }); exports.zstdDecompress = zstdDecompress; const encodeBase64 = (buffer) => { return Buffer.from(asBuffer(buffer).toString('base64'), 'utf8'); }; const decodeBase64 = (buffer) => { return Buffer.from(asBuffer(buffer).toString('utf8'), 'base64'); }; // We export promisified versions for consistency const encodeBase64Promisified = promisify(encodeBase64); exports.encodeBase64 = encodeBase64Promisified; const decodeBase64Promisified = promisify(decodeBase64); exports.decodeBase64 = decodeBase64Promisified; const asBuffer = (input) => { if (Buffer.isBuffer(input)) { return input; } else if (input instanceof ArrayBuffer) { return Buffer.from(input); } else { // Offset & length allow us to support all sorts of buffer views: return Buffer.from(input.buffer, input.byteOffset, input.byteLength); } }; const IDENTITY_ENCODINGS = [ // Explicitly unencoded in the standard way: 'identity', // Weird encoding used by some AWS requests, actually just unencoded JSON: // https://docs.aws.amazon.com/en_us/AmazonCloudWatch/latest/APIReference/making-api-requests.html 'amz-1.0', // Workaround for Apache's mod_deflate handling of 'identity', used in the wild mostly with PHP. // https://github.com/curl/curl/pull/2298 'none', // No idea where these come from, but they definitely exist in real traffic and seem to come // from common confusion between content encodings and content types: 'text', 'binary', 'utf8', 'utf-8' ]; /** * Decodes a buffer, using the encodings as specified in a content-encoding header. Returns * a Buffer instance in Node, or a Uint8Array in a browser. * * Throws if any unrecognized/unavailable content-encoding is found. */ function decodeBuffer(body, encoding) { return __awaiter(this, void 0, void 0, function* () { const bodyBuffer = asBuffer(body); if (Array.isArray(encoding) || (typeof encoding === 'string' && encoding.indexOf(', ') >= 0)) { const encodings = typeof encoding === 'string' ? encoding.split(', ').reverse() : encoding; return encodings.reduce((contentPromise, nextEncoding) => { return contentPromise.then((content) => decodeBuffer(content, nextEncoding)); }, Promise.resolve(bodyBuffer)); } if (!encoding) encoding = 'identity'; else encoding = encoding.toLowerCase(); if (encoding === 'gzip' || encoding === 'x-gzip') { return (0, exports.gunzip)(bodyBuffer); } else if (encoding === 'deflate' || encoding === 'x-deflate') { // Deflate is ambiguous, and may or may not have a zlib wrapper. // This checks the buffer header directly, based on // https://stackoverflow.com/a/37528114/68051 const lowNibble = bodyBuffer[0] & 0xF; if (lowNibble === 8) { return (0, exports.inflate)(bodyBuffer); } else { return (0, exports.inflateRaw)(bodyBuffer); } } else if (encoding === 'br') { return asBuffer(yield (0, exports.brotliDecompress)(bodyBuffer)); } else if (encoding === 'zstd') { return asBuffer(yield (0, exports.zstdDecompress)(bodyBuffer)); } else if (encoding === 'base64') { return asBuffer(yield decodeBase64(bodyBuffer)); } else if (IDENTITY_ENCODINGS.includes(encoding)) { return asBuffer(bodyBuffer); } throw new Error(`Unsupported encoding: ${encoding}`); }); } ; /** * Decodes a buffer, using the encodings as specified in a content-encoding header, synchronously. * Returns a Buffer instance in Node, or a Uint8Array in a browser. * * Zstandard and Brotli decoding are not be supported in synchronous usage. * * Throws if any unrecognized/unavailable content-encoding is found. * * @deprecated This is here for convenience with some existing APIs, but for performance & consistency * async usage with decodeBuffer is preferable. */ function decodeBufferSync(body, encoding) { const bodyBuffer = asBuffer(body); if (Array.isArray(encoding) || (typeof encoding === 'string' && encoding.indexOf(', ') >= 0)) { const encodings = typeof encoding === 'string' ? encoding.split(', ').reverse() : encoding; return encodings.reduce((content, nextEncoding) => { return decodeBufferSync(content, nextEncoding); }, bodyBuffer); } if (!encoding) encoding = 'identity'; else encoding = encoding.toLowerCase(); if (encoding === 'gzip' || encoding === 'x-gzip') { return zlib.gunzipSync(bodyBuffer); } else if (encoding === 'deflate' || encoding === 'x-deflate') { // Deflate is ambiguous, and may or may not have a zlib wrapper. // This checks the buffer header directly, based on // https://stackoverflow.com/a/37528114/68051 const lowNibble = bodyBuffer[0] & 0xF; if (lowNibble === 8) { return zlib.inflateSync(bodyBuffer); } else { return zlib.inflateRawSync(bodyBuffer); } } else if (encoding === 'base64') { return asBuffer(decodeBase64(bodyBuffer)); } else if (IDENTITY_ENCODINGS.includes(encoding)) { return asBuffer(bodyBuffer); } throw new Error(`Unsupported encoding: ${encoding}`); } ; /** * Encodes a buffer, given a single encoding name (as used in content-encoding headers), and an optional * level. Returns a Buffer instance in Node, or a Uint8Array in a browser. * * Throws if an unrecognized/unavailable encoding is specified */ function encodeBuffer(body_1, encoding_1) { return __awaiter(this, arguments, void 0, function* (body, encoding, options = {}) { var _a; const bodyBuffer = asBuffer(body); const level = (_a = options.level) !== null && _a !== void 0 ? _a : 4; if (!encoding) encoding = 'identity'; else encoding = encoding.toLowerCase(); if (encoding === 'gzip' || encoding === 'x-gzip') { return (0, exports.gzip)(bodyBuffer, { level }); } else if (encoding === 'deflate' || encoding === 'x-deflate') { return (0, exports.deflate)(bodyBuffer, { level }); } else if (encoding === 'br') { return asBuffer(yield (0, exports.brotliCompress)(bodyBuffer, level)); } else if (encoding === 'zstd') { return asBuffer(yield (0, exports.zstdCompress)(bodyBuffer, level)); } else if (encoding === 'base64') { return asBuffer(encodeBase64(bodyBuffer)); } else if (IDENTITY_ENCODINGS.includes(encoding)) { return asBuffer(bodyBuffer); } else { throw new Error(`Unsupported encoding: ${encoding}`); } }); } ; //# sourceMappingURL=index.js.map