Skip to content
File

Blob: src/node/internal/internal_zlib.ts

typescript557 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4// Copyright Joyent and Node contributors. All rights reserved. MIT license.
5 
6import {
7 default as zlibUtil,
8 type ZlibOptions,
9 type BrotliOptions,
10 type ZstdOptions,
11} from 'node-internal:zlib';
12import { Buffer } from 'node-internal:internal_buffer';
13import { validateUint32 } from 'node-internal:validators';
14import { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';
15import {
16 Zlib,
17 Brotli,
18 Zstd,
19 zstdInitCParamsArray,
20 zstdInitDParamsArray,
21 kMaxZstdCParam,
22 kMaxZstdDParam,
23 type ZlibBase,
24} from 'node-internal:internal_zlib_base';
25 
26type ZlibResult = Buffer | { buffer: Buffer; engine: ZlibBase };
27type CompressCallback = (err: Error | null, result?: ZlibResult) => void;
28 
29const {
30 CONST_DEFLATE,
31 CONST_DEFLATERAW,
32 CONST_INFLATE,
33 CONST_INFLATERAW,
34 CONST_GUNZIP,
35 CONST_GZIP,
36 CONST_UNZIP,
37 CONST_BROTLI_DECODE,
38 CONST_BROTLI_ENCODE,
39 CONST_ZSTD_ENCODE,
40 CONST_ZSTD_DECODE,
41} = zlibUtil;
42 
43const ZlibMode = {
44 DEFLATE: 1,
45 INFLATE: 2,
46 GZIP: 3,
47 GUNZIP: 4,
48 DEFLATERAW: 5,
49 INFLATERAW: 6,
50 UNZIP: 7,
51};
52 
53export function crc32(
54 data: ArrayBufferView | string,
55 value: number = 0
56): number {
57 validateUint32(value, 'value');
58 return zlibUtil.crc32(data, value);
59}
60 
61function processChunk(
62 engine: ZlibBase,
63 data: ArrayBufferView | string
64): ZlibResult {
65 return {
66 engine,
67 // TODO(soon): What is the proper way to deal with ArrayBufferView to Buffer typing issues?
68 buffer: engine._processChunk(
69 typeof data === 'string' ? Buffer.from(data) : (data as Buffer),
70 engine._finishFlushFlag
71 ),
72 };
73}
74 
75function zlibSyncImpl(
76 data: ArrayBufferView | string,
77 options: ZlibOptions,
78 mode: (typeof ZlibMode)[keyof typeof ZlibMode]
79): ZlibResult {
80 if (!options.info) {
81 // Fast path, where we send the data directly to C++
82 return Buffer.from(zlibUtil.zlibSync(data, options, mode));
83 }
84 
85 // Else, use the Engine class in sync mode
86 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
87 return processChunk(new CLASS_BY_MODE[mode]!(options), data);
88}
89 
90export function inflateSync(
91 data: ArrayBufferView | string,
92 options: ZlibOptions = {}
93): ZlibResult {
94 return zlibSyncImpl(data, options, CONST_INFLATE);
95}
96 
97export function deflateSync(
98 data: ArrayBufferView | string,
99 options: ZlibOptions = {}
100): ZlibResult {
101 return zlibSyncImpl(data, options, CONST_DEFLATE);
102}
103 
104export function gunzipSync(
105 data: ArrayBufferView | string,
106 options: ZlibOptions = {}
107): ZlibResult {
108 return zlibSyncImpl(data, options, CONST_GUNZIP);
109}
110 
111export function gzipSync(
112 data: ArrayBufferView | string,
113 options: ZlibOptions = {}
114): ZlibResult {
115 return zlibSyncImpl(data, options, CONST_GZIP);
116}
117 
118export function inflateRawSync(
119 data: ArrayBufferView | string,
120 options: ZlibOptions = {}
121): ZlibResult {
122 return zlibSyncImpl(data, options, CONST_INFLATERAW);
123}
124 
125export function deflateRawSync(
126 data: ArrayBufferView | string,
127 options: ZlibOptions = {}
128): ZlibResult {
129 return zlibSyncImpl(data, options, CONST_DEFLATERAW);
130}
131 
132export function unzipSync(
133 data: ArrayBufferView | string,
134 options: ZlibOptions = {}
135): ZlibResult {
136 return zlibSyncImpl(data, options, CONST_UNZIP);
137}
138 
139export function brotliDecompressSync(
140 data: ArrayBufferView | string,
141 options: BrotliOptions = {}
142): ZlibResult {
143 if (!options.info) {
144 // Fast path, where we send the data directly to C++
145 return Buffer.from(zlibUtil.brotliDecompressSync(data, options));
146 }
147 
148 // Else, use the Engine class in sync mode
149 return processChunk(new BrotliDecompress(options), data);
150}
151 
152export function brotliCompressSync(
153 data: ArrayBufferView | string,
154 options: BrotliOptions = {}
155): ZlibResult {
156 if (!options.info) {
157 // Fast path, where we send the data directly to C++
158 return Buffer.from(zlibUtil.brotliCompressSync(data, options));
159 }
160 
161 // Else, use the Engine class in sync mode
162 return processChunk(new BrotliCompress(options), data);
163}
164 
165function normalizeArgs(
166 options: ZlibOptions | CompressCallback,
167 callback?: CompressCallback
168): [ZlibOptions, CompressCallback] {
169 if (typeof options === 'function') {
170 return [{}, options];
171 } else if (typeof callback === 'function') {
172 return [options, callback];
173 }
174 
175 throw new ERR_INVALID_ARG_TYPE('callback', 'Function', callback);
176}
177 
178function processChunkCaptureError(
179 engine: ZlibBase,
180 data: ArrayBufferView | string,
181 cb: CompressCallback
182): void {
183 try {
184 const res = processChunk(engine, data);
185 queueMicrotask(() => {
186 cb(null, res);
187 });
188 } catch (err: unknown) {
189 if (err instanceof Error) {
190 queueMicrotask(() => {
191 cb(err);
192 });
193 return;
194 }
195 
196 const unreachable = new Error('Unreachable');
197 unreachable.cause = err;
198 throw unreachable;
199 }
200}
201 
202function zlibImpl(
203 mode: (typeof ZlibMode)[keyof typeof ZlibMode],
204 data: ArrayBufferView | string,
205 optionsOrCallback: ZlibOptions | CompressCallback,
206 callbackOrUndefined?: CompressCallback
207): void {
208 const [options, callback] = normalizeArgs(
209 optionsOrCallback,
210 callbackOrUndefined
211 );
212 
213 if (!options.info) {
214 // Fast path
215 zlibUtil.zlib(data, options, mode, (res) => {
216 queueMicrotask(() => {
217 if (res instanceof Error) {
218 callback(res);
219 } else {
220 callback(null, Buffer.from(res));
221 }
222 });
223 });
224 
225 return;
226 }
227 
228 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
229 processChunkCaptureError(new CLASS_BY_MODE[mode]!(options), data, callback);
230}
231 
232export function inflate(
233 data: ArrayBufferView | string,
234 options: ZlibOptions | CompressCallback,
235 callback?: CompressCallback
236): void {
237 zlibImpl(CONST_INFLATE, data, options, callback);
238}
239 
240export function unzip(
241 data: ArrayBufferView | string,
242 options: ZlibOptions | CompressCallback,
243 callback?: CompressCallback
244): void {
245 zlibImpl(CONST_UNZIP, data, options, callback);
246}
247 
248export function inflateRaw(
249 data: ArrayBufferView | string,
250 options: ZlibOptions | CompressCallback,
251 callback?: CompressCallback
252): void {
253 zlibImpl(CONST_INFLATERAW, data, options, callback);
254}
255 
256export function gunzip(
257 data: ArrayBufferView | string,
258 options: ZlibOptions | CompressCallback,
259 callback?: CompressCallback
260): void {
261 zlibImpl(CONST_GUNZIP, data, options, callback);
262}
263 
264export function deflate(
265 data: ArrayBufferView | string,
266 options: ZlibOptions | CompressCallback,
267 callback?: CompressCallback
268): void {
269 zlibImpl(CONST_DEFLATE, data, options, callback);
270}
271 
272export function deflateRaw(
273 data: ArrayBufferView | string,
274 options: ZlibOptions | CompressCallback,
275 callback?: CompressCallback
276): void {
277 zlibImpl(CONST_DEFLATERAW, data, options, callback);
278}
279 
280export function gzip(
281 data: ArrayBufferView | string,
282 options: ZlibOptions | CompressCallback,
283 callback?: CompressCallback
284): void {
285 zlibImpl(CONST_GZIP, data, options, callback);
286}
287 
288export function brotliDecompress(
289 data: ArrayBufferView | string,
290 optionsOrCallback: BrotliOptions | CompressCallback,
291 callbackOrUndefined?: CompressCallback
292): void {
293 const [options, callback] = normalizeArgs(
294 optionsOrCallback,
295 callbackOrUndefined
296 );
297 
298 if (!options.info) {
299 // Fast path
300 zlibUtil.brotliDecompress(data, options, (res) => {
301 queueMicrotask(() => {
302 if (res instanceof Error) {
303 callback(res);
304 } else {
305 callback(null, Buffer.from(res));
306 }
307 });
308 });
309 
310 return;
311 }
312 
313 processChunkCaptureError(new BrotliDecompress(options), data, callback);
314}
315 
316export function brotliCompress(
317 data: ArrayBufferView | string,
318 optionsOrCallback: BrotliOptions | CompressCallback,
319 callbackOrUndefined?: CompressCallback
320): void {
321 const [options, callback] = normalizeArgs(
322 optionsOrCallback,
323 callbackOrUndefined
324 );
325 
326 if (!options.info) {
327 // Fast path
328 zlibUtil.brotliCompress(data, options, (res) => {
329 queueMicrotask(() => {
330 if (res instanceof Error) {
331 callback(res);
332 } else {
333 callback(null, Buffer.from(res));
334 }
335 });
336 });
337 
338 return;
339 }
340 
341 processChunkCaptureError(new BrotliCompress(options), data, callback);
342}
343 
344export function zstdDecompressSync(
345 data: ArrayBufferView | string,
346 options: ZstdOptions = {}
347): ZlibResult {
348 if (!options.info) {
349 // Fast path, where we send the data directly to C++
350 return Buffer.from(zlibUtil.zstdDecompressSync(data, options));
351 }
352 
353 // Else, use the Engine class in sync mode
354 return processChunk(new ZstdDecompress(options), data);
355}
356 
357export function zstdCompressSync(
358 data: ArrayBufferView | string,
359 options: ZstdOptions = {}
360): ZlibResult {
361 if (!options.info) {
362 // Fast path, where we send the data directly to C++
363 return Buffer.from(zlibUtil.zstdCompressSync(data, options));
364 }
365 
366 // Else, use the Engine class in sync mode
367 return processChunk(new ZstdCompress(options), data);
368}
369 
370export function zstdDecompress(
371 data: ArrayBufferView | string,
372 optionsOrCallback: ZstdOptions | CompressCallback,
373 callbackOrUndefined?: CompressCallback
374): void {
375 const [options, callback] = normalizeArgs(
376 optionsOrCallback,
377 callbackOrUndefined
378 );
379 
380 if (!options.info) {
381 // Fast path
382 zlibUtil.zstdDecompress(data, options, (res) => {
383 queueMicrotask(() => {
384 if (res instanceof Error) {
385 callback(res);
386 } else {
387 callback(null, Buffer.from(res));
388 }
389 });
390 });
391 
392 return;
393 }
394 
395 processChunkCaptureError(new ZstdDecompress(options), data, callback);
396}
397 
398export function zstdCompress(
399 data: ArrayBufferView | string,
400 optionsOrCallback: ZstdOptions | CompressCallback,
401 callbackOrUndefined?: CompressCallback
402): void {
403 const [options, callback] = normalizeArgs(
404 optionsOrCallback,
405 callbackOrUndefined
406 );
407 
408 if (!options.info) {
409 // Fast path
410 zlibUtil.zstdCompress(data, options, (res) => {
411 queueMicrotask(() => {
412 if (res instanceof Error) {
413 callback(res);
414 } else {
415 callback(null, Buffer.from(res));
416 }
417 });
418 });
419 
420 return;
421 }
422 
423 processChunkCaptureError(new ZstdCompress(options), data, callback);
424}
425export class Gzip extends Zlib {
426 constructor(options: ZlibOptions) {
427 super(options, CONST_GZIP);
428 }
429}
430 
431export class Gunzip extends Zlib {
432 constructor(options: ZlibOptions) {
433 super(options, CONST_GUNZIP);
434 }
435}
436 
437export class Deflate extends Zlib {
438 constructor(options: ZlibOptions) {
439 super(options, CONST_DEFLATE);
440 }
441}
442 
443export class DeflateRaw extends Zlib {
444 constructor(options?: ZlibOptions) {
445 if (options?.windowBits === 8) {
446 options.windowBits = 9;
447 }
448 super(options, CONST_DEFLATERAW);
449 }
450}
451 
452export class Inflate extends Zlib {
453 constructor(options: ZlibOptions) {
454 super(options, CONST_INFLATE);
455 }
456}
457 
458export class InflateRaw extends Zlib {
459 constructor(options: ZlibOptions) {
460 super(options, CONST_INFLATERAW);
461 }
462}
463 
464export class Unzip extends Zlib {
465 constructor(options: ZlibOptions) {
466 super(options, CONST_UNZIP);
467 }
468}
469 
470export class BrotliCompress extends Brotli {
471 constructor(options: BrotliOptions) {
472 super(options, CONST_BROTLI_ENCODE);
473 }
474}
475 
476export class BrotliDecompress extends Brotli {
477 constructor(options: BrotliOptions) {
478 super(options, CONST_BROTLI_DECODE);
479 }
480}
481 
482export class ZstdCompress extends Zstd {
483 constructor(options: ZstdOptions) {
484 super(options, CONST_ZSTD_ENCODE, zstdInitCParamsArray, kMaxZstdCParam);
485 }
486}
487 
488export class ZstdDecompress extends Zstd {
489 constructor(options: ZstdOptions) {
490 super(options, CONST_ZSTD_DECODE, zstdInitDParamsArray, kMaxZstdDParam);
491 }
492}
493 
494const CLASS_BY_MODE: Record<
495 (typeof ZlibMode)[keyof typeof ZlibMode],
496 | typeof Deflate
497 | typeof Inflate
498 | typeof InflateRaw
499 | typeof Unzip
500 | typeof BrotliCompress
501 | typeof BrotliDecompress
502> = {
503 [ZlibMode.DEFLATE]: Deflate,
504 [ZlibMode.INFLATE]: Inflate,
505 [ZlibMode.DEFLATERAW]: DeflateRaw,
506 [ZlibMode.INFLATERAW]: InflateRaw,
507 [ZlibMode.GZIP]: Gzip,
508 [ZlibMode.GUNZIP]: Gunzip,
509 [ZlibMode.UNZIP]: Unzip,
510};
511 
512export function createGzip(options: ZlibOptions): Gzip {
513 return new Gzip(options);
514}
515 
516export function createGunzip(options: ZlibOptions): Gunzip {
517 return new Gunzip(options);
518}
519 
520export function createDeflate(options: ZlibOptions): Deflate {
521 return new Deflate(options);
522}
523 
524export function createDeflateRaw(options: ZlibOptions): DeflateRaw {
525 return new DeflateRaw(options);
526}
527 
528export function createInflate(options: ZlibOptions): Inflate {
529 return new Inflate(options);
530}
531 
532export function createInflateRaw(options: ZlibOptions): InflateRaw {
533 return new InflateRaw(options);
534}
535 
536export function createUnzip(options: ZlibOptions): Unzip {
537 return new Unzip(options);
538}
539 
540export function createBrotliCompress(options: BrotliOptions): BrotliCompress {
541 return new BrotliCompress(options);
542}
543 
544export function createBrotliDecompress(
545 options: BrotliOptions
546): BrotliDecompress {
547 return new BrotliDecompress(options);
548}
549 
550export function createZstdCompress(options: ZstdOptions): ZstdCompress {
551 return new ZstdCompress(options);
552}
553 
554export function createZstdDecompress(options: ZstdOptions): ZstdDecompress {
555 return new ZstdDecompress(options);
556}