codekingpro/portable-devtools
114k
1import assert from 'assert';
2import { Buffer } from 'buffer';
3import { Minipass } from 'minipass';
4import * as realZlib from 'zlib';
5import { constants } from './constants.js';
6export { constants } from './constants.js';
7const OriginalBufferConcat = Buffer.concat;
8const desc = Object.getOwnPropertyDescriptor(Buffer, 'concat');
9const noop = (args) => args;
10const passthroughBufferConcat = desc?.writable === true || desc?.set !== undefined
11 ? (makeNoOp) => {
12 Buffer.concat = makeNoOp ? noop : OriginalBufferConcat;
13 }
14 : (_) => { };
15const _superWrite = Symbol('_superWrite');
16export class ZlibError extends Error {
17 code;
18 errno;
19 constructor(err, origin) {
20 super('zlib: ' + err.message, { cause: err });
21 this.code = err.code;
22 this.errno = err.errno;
23 /* c8 ignore next */
24 if (!this.code)
25 this.code = 'ZLIB_ERROR';
26 this.message = 'zlib: ' + err.message;
27 Error.captureStackTrace(this, origin ?? this.constructor);
28 }
29 get name() {
30 return 'ZlibError';
31 }
32}
33// the Zlib class they all inherit from
34// This thing manages the queue of requests, and returns
35// true or false if there is anything in the queue when
36// you call the .write() method.
37const _flushFlag = Symbol('flushFlag');
38class ZlibBase extends Minipass {
39 #sawError = false;
40 #ended = false;
41 #flushFlag;
42 #finishFlushFlag;
43 #fullFlushFlag;
44 #handle;
45 #onError;
46 get sawError() {
47 return this.#sawError;
48 }
49 get handle() {
50 return this.#handle;
51 }
52 /* c8 ignore start */
53 get flushFlag() {
54 return this.#flushFlag;
55 }
56 /* c8 ignore stop */
57 constructor(opts, mode) {
58 if (!opts || typeof opts !== 'object')
59 throw new TypeError('invalid options for ZlibBase constructor');
60 //@ts-ignore
61 super(opts);
62 /* c8 ignore start */
63 this.#flushFlag = opts.flush ?? 0;
64 this.#finishFlushFlag = opts.finishFlush ?? 0;
65 this.#fullFlushFlag = opts.fullFlushFlag ?? 0;
66 /* c8 ignore stop */
67 //@ts-ignore
68 if (typeof realZlib[mode] !== 'function') {
69 throw new TypeError('Compression method not supported: ' + mode);
70 }
71 // this will throw if any options are invalid for the class selected
72 try {
73 // @types/node doesn't know that it exports the classes, but they're there
74 //@ts-ignore
75 this.#handle = new realZlib[mode](opts);
76 }
77 catch (er) {
78 // make sure that all errors get decorated properly
79 throw new ZlibError(er, this.constructor);
80 }
81 this.#onError = err => {
82 // no sense raising multiple errors, since we abort on the first one.
83 if (this.#sawError)
84 return;
85 this.#sawError = true;
86 // there is no way to cleanly recover.
87 // continuing only obscures problems.
88 this.close();
89 this.emit('error', err);
90 };
91 this.#handle?.on('error', er => this.#onError(new ZlibError(er)));
92 this.once('end', () => this.close);
93 }
94 close() {
95 if (this.#handle) {
96 this.#handle.close();
97 this.#handle = undefined;
98 this.emit('close');
99 }
100 }
101 reset() {
102 if (!this.#sawError) {
103 assert(this.#handle, 'zlib binding closed');
104 //@ts-ignore
105 return this.#handle.reset?.();
106 }
107 }
108 flush(flushFlag) {
109 if (this.ended)
110 return;
111 if (typeof flushFlag !== 'number')
112 flushFlag = this.#fullFlushFlag;
113 this.write(Object.assign(Buffer.alloc(0), { [_flushFlag]: flushFlag }));
114 }
115 end(chunk, encoding, cb) {
116 /* c8 ignore start */
117 if (typeof chunk === 'function') {
118 cb = chunk;
119 encoding = undefined;
120 chunk = undefined;
121 }
122 if (typeof encoding === 'function') {
123 cb = encoding;
124 encoding = undefined;
125 }
126 /* c8 ignore stop */
127 if (chunk) {
128 if (encoding)
129 this.write(chunk, encoding);
130 else
131 this.write(chunk);
132 }
133 this.flush(this.#finishFlushFlag);
134 this.#ended = true;
135 return super.end(cb);
136 }
137 get ended() {
138 return this.#ended;
139 }
140 // overridden in the gzip classes to do portable writes
141 [_superWrite](data) {
142 return super.write(data);
143 }
144 write(chunk, encoding, cb) {
145 // process the chunk using the sync process
146 // then super.write() all the outputted chunks
147 if (typeof encoding === 'function')
148 (cb = encoding), (encoding = 'utf8');
149 if (typeof chunk === 'string')
150 chunk = Buffer.from(chunk, encoding);
151 if (this.#sawError)
152 return;
153 assert(this.#handle, 'zlib binding closed');
154 // _processChunk tries to .close() the native handle after it's done, so we
155 // intercept that by temporarily making it a no-op.
156 // diving into the node:zlib internals a bit here
157 const nativeHandle = this.#handle
158 ._handle;
159 const originalNativeClose = nativeHandle.close;
160 nativeHandle.close = () => { };
161 const originalClose = this.#handle.close;
162 this.#handle.close = () => { };
163 // It also calls `Buffer.concat()` at the end, which may be convenient
164 // for some, but which we are not interested in as it slows us down.
165 passthroughBufferConcat(true);
166 let result = undefined;
167 try {
168 const flushFlag = typeof chunk[_flushFlag] === 'number'
169 ? chunk[_flushFlag]
170 : this.#flushFlag;
171 result = this.#handle._processChunk(chunk, flushFlag);
172 // if we don't throw, reset it back how it was
173 passthroughBufferConcat(false);
174 }
175 catch (err) {
176 // or if we do, put Buffer.concat() back before we emit error
177 // Error events call into user code, which may call Buffer.concat()
178 passthroughBufferConcat(false);
179 this.#onError(new ZlibError(err, this.write));
180 }
181 finally {
182 if (this.#handle) {
183 // Core zlib resets `_handle` to null after attempting to close the
184 // native handle. Our no-op handler prevented actual closure, but we
185 // need to restore the `._handle` property.
186 ;
187 this.#handle._handle =
188 nativeHandle;
189 nativeHandle.close = originalNativeClose;
190 this.#handle.close = originalClose;
191 // `_processChunk()` adds an 'error' listener. If we don't remove it
192 // after each call, these handlers start piling up.
193 this.#handle.removeAllListeners('error');
194 // make sure OUR error listener is still attached tho
195 }
196 }
197 if (this.#handle)
198 this.#handle.on('error', er => this.#onError(new ZlibError(er, this.write)));
199 let writeReturn;
200 if (result) {
201 if (Array.isArray(result) && result.length > 0) {
202 const r = result[0];
203 // The first buffer is always `handle._outBuffer`, which would be
204 // re-used for later invocations; so, we always have to copy that one.
205 writeReturn = this[_superWrite](Buffer.from(r));
206 for (let i = 1; i < result.length; i++) {
207 writeReturn = this[_superWrite](result[i]);
208 }
209 }
210 else {
211 // either a single Buffer or an empty array
212 writeReturn = this[_superWrite](Buffer.from(result));
213 }
214 }
215 if (cb)
216 cb();
217 return writeReturn;
218 }
219}
220export class Zlib extends ZlibBase {
221 #level;
222 #strategy;
223 constructor(opts, mode) {
224 opts = opts || {};
225 opts.flush = opts.flush || constants.Z_NO_FLUSH;
226 opts.finishFlush = opts.finishFlush || constants.Z_FINISH;
227 opts.fullFlushFlag = constants.Z_FULL_FLUSH;
228 super(opts, mode);
229 this.#level = opts.level;
230 this.#strategy = opts.strategy;
231 }
232 params(level, strategy) {
233 if (this.sawError)
234 return;
235 if (!this.handle)
236 throw new Error('cannot switch params when binding is closed');
237 // no way to test this without also not supporting params at all
238 /* c8 ignore start */
239 if (!this.handle.params)
240 throw new Error('not supported in this implementation');
241 /* c8 ignore stop */
242 if (this.#level !== level || this.#strategy !== strategy) {
243 this.flush(constants.Z_SYNC_FLUSH);
244 assert(this.handle, 'zlib binding closed');
245 // .params() calls .flush(), but the latter is always async in the
246 // core zlib. We override .flush() temporarily to intercept that and
247 // flush synchronously.
248 const origFlush = this.handle.flush;
249 this.handle.flush = (flushFlag, cb) => {
250 /* c8 ignore start */
251 if (typeof flushFlag === 'function') {
252 cb = flushFlag;
253 flushFlag = this.flushFlag;
254 }
255 /* c8 ignore stop */
256 this.flush(flushFlag);
257 cb?.();
258 };
259 try {
260 ;
261 this.handle.params(level, strategy);
262 }
263 finally {
264 this.handle.flush = origFlush;
265 }
266 /* c8 ignore start */
267 if (this.handle) {
268 this.#level = level;
269 this.#strategy = strategy;
270 }
271 /* c8 ignore stop */
272 }
273 }
274}
275// minimal 2-byte header
276export class Deflate extends Zlib {
277 constructor(opts) {
278 super(opts, 'Deflate');
279 }
280}
281export class Inflate extends Zlib {
282 constructor(opts) {
283 super(opts, 'Inflate');
284 }
285}
286export class Gzip extends Zlib {
287 #portable;
288 constructor(opts) {
289 super(opts, 'Gzip');
290 this.#portable = opts && !!opts.portable;
291 }
292 [_superWrite](data) {
293 if (!this.#portable)
294 return super[_superWrite](data);
295 // we'll always get the header emitted in one first chunk
296 // overwrite the OS indicator byte with 0xFF
297 this.#portable = false;
298 data[9] = 255;
299 return super[_superWrite](data);
300 }
301}
302export class Gunzip extends Zlib {
303 constructor(opts) {
304 super(opts, 'Gunzip');
305 }
306}
307// raw - no header
308export class DeflateRaw extends Zlib {
309 constructor(opts) {
310 super(opts, 'DeflateRaw');
311 }
312}
313export class InflateRaw extends Zlib {
314 constructor(opts) {
315 super(opts, 'InflateRaw');
316 }
317}
318// auto-detect header.
319export class Unzip extends Zlib {
320 constructor(opts) {
321 super(opts, 'Unzip');
322 }
323}
324class Brotli extends ZlibBase {
325 constructor(opts, mode) {
326 opts = opts || {};
327 opts.flush = opts.flush || constants.BROTLI_OPERATION_PROCESS;
328 opts.finishFlush =
329 opts.finishFlush || constants.BROTLI_OPERATION_FINISH;
330 opts.fullFlushFlag = constants.BROTLI_OPERATION_FLUSH;
331 super(opts, mode);
332 }
333}
334export class BrotliCompress extends Brotli {
335 constructor(opts) {
336 super(opts, 'BrotliCompress');
337 }
338}
339export class BrotliDecompress extends Brotli {
340 constructor(opts) {
341 super(opts, 'BrotliDecompress');
342 }
343}
344class Zstd extends ZlibBase {
345 constructor(opts, mode) {
346 opts = opts || {};
347 opts.flush = opts.flush || constants.ZSTD_e_continue;
348 opts.finishFlush = opts.finishFlush || constants.ZSTD_e_end;
349 opts.fullFlushFlag = constants.ZSTD_e_flush;
350 super(opts, mode);
351 }
352}
353export class ZstdCompress extends Zstd {
354 constructor(opts) {
355 super(opts, 'ZstdCompress');
356 }
357}
358export class ZstdDecompress extends Zstd {
359 constructor(opts) {
360 super(opts, 'ZstdDecompress');
361 }
362}
363//# sourceMappingURL=index.js.map