2024-01-01 14:58:21 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2023-06-24 10:12:08 -04:00
|
|
|
|
2024-06-20 03:44:23 -04:00
|
|
|
import { core, primordials } from "ext:core/mod.js";
|
|
|
|
const {
|
|
|
|
Uint8Array,
|
2024-08-11 04:56:30 -04:00
|
|
|
Number,
|
2024-06-20 03:44:23 -04:00
|
|
|
PromisePrototypeThen,
|
|
|
|
PromisePrototypeCatch,
|
2024-08-11 04:56:30 -04:00
|
|
|
ObjectEntries,
|
|
|
|
ArrayPrototypeMap,
|
2024-06-20 03:44:23 -04:00
|
|
|
TypedArrayPrototypeSlice,
|
|
|
|
TypedArrayPrototypeSubarray,
|
|
|
|
TypedArrayPrototypeGetByteLength,
|
|
|
|
DataViewPrototypeGetBuffer,
|
|
|
|
TypedArrayPrototypeGetBuffer,
|
|
|
|
} = primordials;
|
|
|
|
const { isTypedArray, isDataView, close } = core;
|
2024-01-29 08:58:08 -05:00
|
|
|
import {
|
2024-01-10 17:37:25 -05:00
|
|
|
op_brotli_compress,
|
|
|
|
op_brotli_compress_async,
|
|
|
|
op_brotli_compress_stream,
|
|
|
|
op_brotli_compress_stream_end,
|
|
|
|
op_brotli_decompress,
|
|
|
|
op_brotli_decompress_async,
|
|
|
|
op_brotli_decompress_stream,
|
2024-03-12 07:53:31 -04:00
|
|
|
op_brotli_decompress_stream_end,
|
2024-01-10 17:37:25 -05:00
|
|
|
op_create_brotli_compress,
|
|
|
|
op_create_brotli_decompress,
|
2024-01-29 08:58:08 -05:00
|
|
|
} from "ext:core/ops";
|
2024-01-10 17:37:25 -05:00
|
|
|
|
2023-06-24 10:12:08 -04:00
|
|
|
import { zlib as constants } from "ext:deno_node/internal_binding/constants.ts";
|
|
|
|
import { TextEncoder } from "ext:deno_web/08_text_encoding.js";
|
2023-07-02 14:19:30 -04:00
|
|
|
import { Transform } from "node:stream";
|
|
|
|
import { Buffer } from "node:buffer";
|
2023-06-24 10:12:08 -04:00
|
|
|
|
|
|
|
const enc = new TextEncoder();
|
|
|
|
const toU8 = (input) => {
|
|
|
|
if (typeof input === "string") {
|
|
|
|
return enc.encode(input);
|
|
|
|
}
|
|
|
|
|
2024-06-20 03:44:23 -04:00
|
|
|
if (isTypedArray(input)) {
|
2024-08-02 10:23:21 -04:00
|
|
|
return new Uint8Array(TypedArrayPrototypeGetBuffer(input));
|
2024-06-20 03:44:23 -04:00
|
|
|
} else if (isDataView(input)) {
|
2024-08-02 10:23:21 -04:00
|
|
|
return new Uint8Array(DataViewPrototypeGetBuffer(input));
|
2023-12-31 06:50:37 -05:00
|
|
|
}
|
|
|
|
|
2023-06-24 10:12:08 -04:00
|
|
|
return input;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function createBrotliCompress(options) {
|
|
|
|
return new BrotliCompress(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createBrotliDecompress(options) {
|
|
|
|
return new BrotliDecompress(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
export class BrotliDecompress extends Transform {
|
|
|
|
#context;
|
|
|
|
|
|
|
|
// TODO(littledivy): use `options` argument
|
2024-09-06 06:52:59 -04:00
|
|
|
constructor(_options = { __proto__: null }) {
|
2023-06-24 10:12:08 -04:00
|
|
|
super({
|
|
|
|
// TODO(littledivy): use `encoding` argument
|
|
|
|
transform(chunk, _encoding, callback) {
|
|
|
|
const input = toU8(chunk);
|
2024-06-20 03:44:23 -04:00
|
|
|
const output = new Uint8Array(TypedArrayPrototypeGetByteLength(chunk));
|
2024-01-10 17:37:25 -05:00
|
|
|
const avail = op_brotli_decompress_stream(context, input, output);
|
2024-06-20 03:44:23 -04:00
|
|
|
// deno-lint-ignore prefer-primordials
|
|
|
|
this.push(TypedArrayPrototypeSlice(output, 0, avail));
|
2023-06-24 10:12:08 -04:00
|
|
|
callback();
|
|
|
|
},
|
|
|
|
flush(callback) {
|
2024-03-12 07:53:31 -04:00
|
|
|
const output = new Uint8Array(1024);
|
|
|
|
let avail;
|
|
|
|
while ((avail = op_brotli_decompress_stream_end(context, output)) > 0) {
|
2024-06-20 03:44:23 -04:00
|
|
|
// deno-lint-ignore prefer-primordials
|
|
|
|
this.push(TypedArrayPrototypeSlice(output, 0, avail));
|
2024-03-12 07:53:31 -04:00
|
|
|
}
|
2024-06-20 03:44:23 -04:00
|
|
|
close(context);
|
2023-06-24 10:12:08 -04:00
|
|
|
callback();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2024-01-10 17:37:25 -05:00
|
|
|
this.#context = op_create_brotli_decompress();
|
2023-06-24 10:12:08 -04:00
|
|
|
const context = this.#context;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class BrotliCompress extends Transform {
|
|
|
|
#context;
|
|
|
|
|
2024-09-06 06:52:59 -04:00
|
|
|
constructor(options = { __proto__: null }) {
|
2023-06-24 10:12:08 -04:00
|
|
|
super({
|
|
|
|
// TODO(littledivy): use `encoding` argument
|
|
|
|
transform(chunk, _encoding, callback) {
|
|
|
|
const input = toU8(chunk);
|
|
|
|
const output = new Uint8Array(brotliMaxCompressedSize(input.length));
|
2024-02-07 11:23:32 -05:00
|
|
|
const written = op_brotli_compress_stream(context, input, output);
|
|
|
|
if (written > 0) {
|
2024-06-20 03:44:23 -04:00
|
|
|
// deno-lint-ignore prefer-primordials
|
|
|
|
this.push(TypedArrayPrototypeSlice(output, 0, written));
|
2024-02-07 11:23:32 -05:00
|
|
|
}
|
2023-06-24 10:12:08 -04:00
|
|
|
callback();
|
|
|
|
},
|
|
|
|
flush(callback) {
|
|
|
|
const output = new Uint8Array(1024);
|
2024-02-07 11:23:32 -05:00
|
|
|
let avail;
|
|
|
|
while ((avail = op_brotli_compress_stream_end(context, output)) > 0) {
|
2024-06-20 03:44:23 -04:00
|
|
|
// deno-lint-ignore prefer-primordials
|
|
|
|
this.push(TypedArrayPrototypeSlice(output, 0, avail));
|
2024-02-07 11:23:32 -05:00
|
|
|
}
|
2024-06-20 03:44:23 -04:00
|
|
|
close(context);
|
2023-06-24 10:12:08 -04:00
|
|
|
callback();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2024-08-11 04:56:30 -04:00
|
|
|
const params = ArrayPrototypeMap(
|
|
|
|
ObjectEntries(options?.params ?? {}),
|
|
|
|
// Undo the stringification of the keys
|
|
|
|
(o) => [Number(o[0]), o[1]],
|
|
|
|
);
|
2024-01-10 17:37:25 -05:00
|
|
|
this.#context = op_create_brotli_compress(params);
|
2023-06-24 10:12:08 -04:00
|
|
|
const context = this.#context;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function oneOffCompressOptions(options) {
|
2024-08-02 10:44:32 -04:00
|
|
|
let quality = options?.params?.[constants.BROTLI_PARAM_QUALITY] ??
|
2023-06-24 10:12:08 -04:00
|
|
|
constants.BROTLI_DEFAULT_QUALITY;
|
|
|
|
const lgwin = options?.params?.[constants.BROTLI_PARAM_LGWIN] ??
|
|
|
|
constants.BROTLI_DEFAULT_WINDOW;
|
|
|
|
const mode = options?.params?.[constants.BROTLI_PARAM_MODE] ??
|
|
|
|
constants.BROTLI_MODE_GENERIC;
|
|
|
|
|
2024-08-02 10:44:32 -04:00
|
|
|
// NOTE(bartlomieju): currently the rust-brotli crate panics if the quality
|
|
|
|
// is set to 10. Coerce it down to 9.5 which is the maximum supported value.
|
|
|
|
// https://github.com/dropbox/rust-brotli/issues/216
|
|
|
|
if (quality == 10) {
|
|
|
|
quality = 9.5;
|
|
|
|
}
|
|
|
|
|
2023-06-24 10:12:08 -04:00
|
|
|
return {
|
|
|
|
quality,
|
|
|
|
lgwin,
|
|
|
|
mode,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function brotliMaxCompressedSize(input) {
|
|
|
|
if (input == 0) return 2;
|
|
|
|
|
|
|
|
// [window bits / empty metadata] + N * [uncompressed] + [last empty]
|
|
|
|
const numLargeBlocks = input >> 24;
|
|
|
|
const overhead = 2 + (4 * numLargeBlocks) + 3 + 1;
|
|
|
|
const result = input + overhead;
|
|
|
|
|
|
|
|
return result < input ? 0 : result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function brotliCompress(
|
|
|
|
input,
|
|
|
|
options,
|
|
|
|
callback,
|
|
|
|
) {
|
|
|
|
const buf = toU8(input);
|
|
|
|
|
|
|
|
if (typeof options === "function") {
|
|
|
|
callback = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
const { quality, lgwin, mode } = oneOffCompressOptions(options);
|
2024-06-20 03:44:23 -04:00
|
|
|
PromisePrototypeCatch(
|
|
|
|
PromisePrototypeThen(
|
|
|
|
op_brotli_compress_async(buf, quality, lgwin, mode),
|
|
|
|
(result) => callback(null, Buffer.from(result)),
|
|
|
|
),
|
|
|
|
(err) => callback(err),
|
|
|
|
);
|
2023-06-24 10:12:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function brotliCompressSync(
|
|
|
|
input,
|
|
|
|
options,
|
|
|
|
) {
|
|
|
|
const buf = toU8(input);
|
|
|
|
const output = new Uint8Array(brotliMaxCompressedSize(buf.length));
|
|
|
|
|
|
|
|
const { quality, lgwin, mode } = oneOffCompressOptions(options);
|
2024-01-10 17:37:25 -05:00
|
|
|
const len = op_brotli_compress(buf, output, quality, lgwin, mode);
|
2024-06-20 03:44:23 -04:00
|
|
|
return Buffer.from(TypedArrayPrototypeSubarray(output, 0, len));
|
2023-06-24 10:12:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function brotliDecompress(input) {
|
|
|
|
const buf = toU8(input);
|
2024-06-20 03:44:23 -04:00
|
|
|
return PromisePrototypeCatch(
|
|
|
|
PromisePrototypeThen(
|
|
|
|
op_brotli_decompress_async(buf),
|
|
|
|
(result) => callback(null, Buffer.from(result)),
|
|
|
|
),
|
|
|
|
(err) => callback(err),
|
|
|
|
);
|
2023-06-24 10:12:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function brotliDecompressSync(input) {
|
2024-01-10 17:37:25 -05:00
|
|
|
return Buffer.from(op_brotli_decompress(toU8(input)));
|
2023-06-24 10:12:08 -04:00
|
|
|
}
|