http-encoding
Version:
Everything you need to handle HTTP message body content-encoding
276 lines • 11.9 kB
JavaScript
;
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