UNPKG

16.7 kBJavaScriptView Raw
1'use strict';
2
3var Buffer = require('buffer').Buffer;
4var Transform = require('stream').Transform;
5var binding = require('./binding');
6var util = require('util');
7var assert = require('assert').ok;
8var kMaxLength = require('buffer').kMaxLength;
9var kRangeErrorMessage = 'Cannot create final Buffer. It would be larger ' + 'than 0x' + kMaxLength.toString(16) + ' bytes';
10
11// zlib doesn't provide these, so kludge them in following the same
12// const naming scheme zlib uses.
13binding.Z_MIN_WINDOWBITS = 8;
14binding.Z_MAX_WINDOWBITS = 15;
15binding.Z_DEFAULT_WINDOWBITS = 15;
16
17// fewer than 64 bytes per chunk is stupid.
18// technically it could work with as few as 8, but even 64 bytes
19// is absurdly low. Usually a MB or more is best.
20binding.Z_MIN_CHUNK = 64;
21binding.Z_MAX_CHUNK = Infinity;
22binding.Z_DEFAULT_CHUNK = 16 * 1024;
23
24binding.Z_MIN_MEMLEVEL = 1;
25binding.Z_MAX_MEMLEVEL = 9;
26binding.Z_DEFAULT_MEMLEVEL = 8;
27
28binding.Z_MIN_LEVEL = -1;
29binding.Z_MAX_LEVEL = 9;
30binding.Z_DEFAULT_LEVEL = binding.Z_DEFAULT_COMPRESSION;
31
32// expose all the zlib constants
33var bkeys = Object.keys(binding);
34for (var bk = 0; bk < bkeys.length; bk++) {
35 var bkey = bkeys[bk];
36 if (bkey.match(/^Z/)) {
37 Object.defineProperty(exports, bkey, {
38 enumerable: true, value: binding[bkey], writable: false
39 });
40 }
41}
42
43// translation table for return codes.
44var codes = {
45 Z_OK: binding.Z_OK,
46 Z_STREAM_END: binding.Z_STREAM_END,
47 Z_NEED_DICT: binding.Z_NEED_DICT,
48 Z_ERRNO: binding.Z_ERRNO,
49 Z_STREAM_ERROR: binding.Z_STREAM_ERROR,
50 Z_DATA_ERROR: binding.Z_DATA_ERROR,
51 Z_MEM_ERROR: binding.Z_MEM_ERROR,
52 Z_BUF_ERROR: binding.Z_BUF_ERROR,
53 Z_VERSION_ERROR: binding.Z_VERSION_ERROR
54};
55
56var ckeys = Object.keys(codes);
57for (var ck = 0; ck < ckeys.length; ck++) {
58 var ckey = ckeys[ck];
59 codes[codes[ckey]] = ckey;
60}
61
62Object.defineProperty(exports, 'codes', {
63 enumerable: true, value: Object.freeze(codes), writable: false
64});
65
66exports.Deflate = Deflate;
67exports.Inflate = Inflate;
68exports.Gzip = Gzip;
69exports.Gunzip = Gunzip;
70exports.DeflateRaw = DeflateRaw;
71exports.InflateRaw = InflateRaw;
72exports.Unzip = Unzip;
73
74exports.createDeflate = function (o) {
75 return new Deflate(o);
76};
77
78exports.createInflate = function (o) {
79 return new Inflate(o);
80};
81
82exports.createDeflateRaw = function (o) {
83 return new DeflateRaw(o);
84};
85
86exports.createInflateRaw = function (o) {
87 return new InflateRaw(o);
88};
89
90exports.createGzip = function (o) {
91 return new Gzip(o);
92};
93
94exports.createGunzip = function (o) {
95 return new Gunzip(o);
96};
97
98exports.createUnzip = function (o) {
99 return new Unzip(o);
100};
101
102// Convenience methods.
103// compress/decompress a string or buffer in one step.
104exports.deflate = function (buffer, opts, callback) {
105 if (typeof opts === 'function') {
106 callback = opts;
107 opts = {};
108 }
109 return zlibBuffer(new Deflate(opts), buffer, callback);
110};
111
112exports.deflateSync = function (buffer, opts) {
113 return zlibBufferSync(new Deflate(opts), buffer);
114};
115
116exports.gzip = function (buffer, opts, callback) {
117 if (typeof opts === 'function') {
118 callback = opts;
119 opts = {};
120 }
121 return zlibBuffer(new Gzip(opts), buffer, callback);
122};
123
124exports.gzipSync = function (buffer, opts) {
125 return zlibBufferSync(new Gzip(opts), buffer);
126};
127
128exports.deflateRaw = function (buffer, opts, callback) {
129 if (typeof opts === 'function') {
130 callback = opts;
131 opts = {};
132 }
133 return zlibBuffer(new DeflateRaw(opts), buffer, callback);
134};
135
136exports.deflateRawSync = function (buffer, opts) {
137 return zlibBufferSync(new DeflateRaw(opts), buffer);
138};
139
140exports.unzip = function (buffer, opts, callback) {
141 if (typeof opts === 'function') {
142 callback = opts;
143 opts = {};
144 }
145 return zlibBuffer(new Unzip(opts), buffer, callback);
146};
147
148exports.unzipSync = function (buffer, opts) {
149 return zlibBufferSync(new Unzip(opts), buffer);
150};
151
152exports.inflate = function (buffer, opts, callback) {
153 if (typeof opts === 'function') {
154 callback = opts;
155 opts = {};
156 }
157 return zlibBuffer(new Inflate(opts), buffer, callback);
158};
159
160exports.inflateSync = function (buffer, opts) {
161 return zlibBufferSync(new Inflate(opts), buffer);
162};
163
164exports.gunzip = function (buffer, opts, callback) {
165 if (typeof opts === 'function') {
166 callback = opts;
167 opts = {};
168 }
169 return zlibBuffer(new Gunzip(opts), buffer, callback);
170};
171
172exports.gunzipSync = function (buffer, opts) {
173 return zlibBufferSync(new Gunzip(opts), buffer);
174};
175
176exports.inflateRaw = function (buffer, opts, callback) {
177 if (typeof opts === 'function') {
178 callback = opts;
179 opts = {};
180 }
181 return zlibBuffer(new InflateRaw(opts), buffer, callback);
182};
183
184exports.inflateRawSync = function (buffer, opts) {
185 return zlibBufferSync(new InflateRaw(opts), buffer);
186};
187
188function zlibBuffer(engine, buffer, callback) {
189 var buffers = [];
190 var nread = 0;
191
192 engine.on('error', onError);
193 engine.on('end', onEnd);
194
195 engine.end(buffer);
196 flow();
197
198 function flow() {
199 var chunk;
200 while (null !== (chunk = engine.read())) {
201 buffers.push(chunk);
202 nread += chunk.length;
203 }
204 engine.once('readable', flow);
205 }
206
207 function onError(err) {
208 engine.removeListener('end', onEnd);
209 engine.removeListener('readable', flow);
210 callback(err);
211 }
212
213 function onEnd() {
214 var buf;
215 var err = null;
216
217 if (nread >= kMaxLength) {
218 err = new RangeError(kRangeErrorMessage);
219 } else {
220 buf = Buffer.concat(buffers, nread);
221 }
222
223 buffers = [];
224 engine.close();
225 callback(err, buf);
226 }
227}
228
229function zlibBufferSync(engine, buffer) {
230 if (typeof buffer === 'string') buffer = Buffer.from(buffer);
231
232 if (!Buffer.isBuffer(buffer)) throw new TypeError('Not a string or buffer');
233
234 var flushFlag = engine._finishFlushFlag;
235
236 return engine._processChunk(buffer, flushFlag);
237}
238
239// generic zlib
240// minimal 2-byte header
241function Deflate(opts) {
242 if (!(this instanceof Deflate)) return new Deflate(opts);
243 Zlib.call(this, opts, binding.DEFLATE);
244}
245
246function Inflate(opts) {
247 if (!(this instanceof Inflate)) return new Inflate(opts);
248 Zlib.call(this, opts, binding.INFLATE);
249}
250
251// gzip - bigger header, same deflate compression
252function Gzip(opts) {
253 if (!(this instanceof Gzip)) return new Gzip(opts);
254 Zlib.call(this, opts, binding.GZIP);
255}
256
257function Gunzip(opts) {
258 if (!(this instanceof Gunzip)) return new Gunzip(opts);
259 Zlib.call(this, opts, binding.GUNZIP);
260}
261
262// raw - no header
263function DeflateRaw(opts) {
264 if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts);
265 Zlib.call(this, opts, binding.DEFLATERAW);
266}
267
268function InflateRaw(opts) {
269 if (!(this instanceof InflateRaw)) return new InflateRaw(opts);
270 Zlib.call(this, opts, binding.INFLATERAW);
271}
272
273// auto-detect header.
274function Unzip(opts) {
275 if (!(this instanceof Unzip)) return new Unzip(opts);
276 Zlib.call(this, opts, binding.UNZIP);
277}
278
279function isValidFlushFlag(flag) {
280 return flag === binding.Z_NO_FLUSH || flag === binding.Z_PARTIAL_FLUSH || flag === binding.Z_SYNC_FLUSH || flag === binding.Z_FULL_FLUSH || flag === binding.Z_FINISH || flag === binding.Z_BLOCK;
281}
282
283// the Zlib class they all inherit from
284// This thing manages the queue of requests, and returns
285// true or false if there is anything in the queue when
286// you call the .write() method.
287
288function Zlib(opts, mode) {
289 var _this = this;
290
291 this._opts = opts = opts || {};
292 this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK;
293
294 Transform.call(this, opts);
295
296 if (opts.flush && !isValidFlushFlag(opts.flush)) {
297 throw new Error('Invalid flush flag: ' + opts.flush);
298 }
299 if (opts.finishFlush && !isValidFlushFlag(opts.finishFlush)) {
300 throw new Error('Invalid flush flag: ' + opts.finishFlush);
301 }
302
303 this._flushFlag = opts.flush || binding.Z_NO_FLUSH;
304 this._finishFlushFlag = typeof opts.finishFlush !== 'undefined' ? opts.finishFlush : binding.Z_FINISH;
305
306 if (opts.chunkSize) {
307 if (opts.chunkSize < exports.Z_MIN_CHUNK || opts.chunkSize > exports.Z_MAX_CHUNK) {
308 throw new Error('Invalid chunk size: ' + opts.chunkSize);
309 }
310 }
311
312 if (opts.windowBits) {
313 if (opts.windowBits < exports.Z_MIN_WINDOWBITS || opts.windowBits > exports.Z_MAX_WINDOWBITS) {
314 throw new Error('Invalid windowBits: ' + opts.windowBits);
315 }
316 }
317
318 if (opts.level) {
319 if (opts.level < exports.Z_MIN_LEVEL || opts.level > exports.Z_MAX_LEVEL) {
320 throw new Error('Invalid compression level: ' + opts.level);
321 }
322 }
323
324 if (opts.memLevel) {
325 if (opts.memLevel < exports.Z_MIN_MEMLEVEL || opts.memLevel > exports.Z_MAX_MEMLEVEL) {
326 throw new Error('Invalid memLevel: ' + opts.memLevel);
327 }
328 }
329
330 if (opts.strategy) {
331 if (opts.strategy != exports.Z_FILTERED && opts.strategy != exports.Z_HUFFMAN_ONLY && opts.strategy != exports.Z_RLE && opts.strategy != exports.Z_FIXED && opts.strategy != exports.Z_DEFAULT_STRATEGY) {
332 throw new Error('Invalid strategy: ' + opts.strategy);
333 }
334 }
335
336 if (opts.dictionary) {
337 if (!Buffer.isBuffer(opts.dictionary)) {
338 throw new Error('Invalid dictionary: it should be a Buffer instance');
339 }
340 }
341
342 this._handle = new binding.Zlib(mode);
343
344 var self = this;
345 this._hadError = false;
346 this._handle.onerror = function (message, errno) {
347 // there is no way to cleanly recover.
348 // continuing only obscures problems.
349 _close(self);
350 self._hadError = true;
351
352 var error = new Error(message);
353 error.errno = errno;
354 error.code = exports.codes[errno];
355 self.emit('error', error);
356 };
357
358 var level = exports.Z_DEFAULT_COMPRESSION;
359 if (typeof opts.level === 'number') level = opts.level;
360
361 var strategy = exports.Z_DEFAULT_STRATEGY;
362 if (typeof opts.strategy === 'number') strategy = opts.strategy;
363
364 this._handle.init(opts.windowBits || exports.Z_DEFAULT_WINDOWBITS, level, opts.memLevel || exports.Z_DEFAULT_MEMLEVEL, strategy, opts.dictionary);
365
366 this._buffer = Buffer.allocUnsafe(this._chunkSize);
367 this._offset = 0;
368 this._level = level;
369 this._strategy = strategy;
370
371 this.once('end', this.close);
372
373 Object.defineProperty(this, '_closed', {
374 get: function () {
375 return !_this._handle;
376 },
377 configurable: true,
378 enumerable: true
379 });
380}
381
382util.inherits(Zlib, Transform);
383
384Zlib.prototype.params = function (level, strategy, callback) {
385 if (level < exports.Z_MIN_LEVEL || level > exports.Z_MAX_LEVEL) {
386 throw new RangeError('Invalid compression level: ' + level);
387 }
388 if (strategy != exports.Z_FILTERED && strategy != exports.Z_HUFFMAN_ONLY && strategy != exports.Z_RLE && strategy != exports.Z_FIXED && strategy != exports.Z_DEFAULT_STRATEGY) {
389 throw new TypeError('Invalid strategy: ' + strategy);
390 }
391
392 if (this._level !== level || this._strategy !== strategy) {
393 var self = this;
394 this.flush(binding.Z_SYNC_FLUSH, function () {
395 assert(self._handle, 'zlib binding closed');
396 self._handle.params(level, strategy);
397 if (!self._hadError) {
398 self._level = level;
399 self._strategy = strategy;
400 if (callback) callback();
401 }
402 });
403 } else {
404 process.nextTick(callback);
405 }
406};
407
408Zlib.prototype.reset = function () {
409 assert(this._handle, 'zlib binding closed');
410 return this._handle.reset();
411};
412
413// This is the _flush function called by the transform class,
414// internally, when the last chunk has been written.
415Zlib.prototype._flush = function (callback) {
416 this._transform(Buffer.alloc(0), '', callback);
417};
418
419Zlib.prototype.flush = function (kind, callback) {
420 var _this2 = this;
421
422 var ws = this._writableState;
423
424 if (typeof kind === 'function' || kind === undefined && !callback) {
425 callback = kind;
426 kind = binding.Z_FULL_FLUSH;
427 }
428
429 if (ws.ended) {
430 if (callback) process.nextTick(callback);
431 } else if (ws.ending) {
432 if (callback) this.once('end', callback);
433 } else if (ws.needDrain) {
434 if (callback) {
435 this.once('drain', function () {
436 return _this2.flush(kind, callback);
437 });
438 }
439 } else {
440 this._flushFlag = kind;
441 this.write(Buffer.alloc(0), '', callback);
442 }
443};
444
445Zlib.prototype.close = function (callback) {
446 _close(this, callback);
447 process.nextTick(emitCloseNT, this);
448};
449
450function _close(engine, callback) {
451 if (callback) process.nextTick(callback);
452
453 // Caller may invoke .close after a zlib error (which will null _handle).
454 if (!engine._handle) return;
455
456 engine._handle.close();
457 engine._handle = null;
458}
459
460function emitCloseNT(self) {
461 self.emit('close');
462}
463
464Zlib.prototype._transform = function (chunk, encoding, cb) {
465 var flushFlag;
466 var ws = this._writableState;
467 var ending = ws.ending || ws.ended;
468 var last = ending && (!chunk || ws.length === chunk.length);
469
470 if (chunk !== null && !Buffer.isBuffer(chunk)) return cb(new Error('invalid input'));
471
472 if (!this._handle) return cb(new Error('zlib binding closed'));
473
474 // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag
475 // (or whatever flag was provided using opts.finishFlush).
476 // If it's explicitly flushing at some other time, then we use
477 // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression
478 // goodness.
479 if (last) flushFlag = this._finishFlushFlag;else {
480 flushFlag = this._flushFlag;
481 // once we've flushed the last of the queue, stop flushing and
482 // go back to the normal behavior.
483 if (chunk.length >= ws.length) {
484 this._flushFlag = this._opts.flush || binding.Z_NO_FLUSH;
485 }
486 }
487
488 this._processChunk(chunk, flushFlag, cb);
489};
490
491Zlib.prototype._processChunk = function (chunk, flushFlag, cb) {
492 var availInBefore = chunk && chunk.length;
493 var availOutBefore = this._chunkSize - this._offset;
494 var inOff = 0;
495
496 var self = this;
497
498 var async = typeof cb === 'function';
499
500 if (!async) {
501 var buffers = [];
502 var nread = 0;
503
504 var error;
505 this.on('error', function (er) {
506 error = er;
507 });
508
509 assert(this._handle, 'zlib binding closed');
510 do {
511 var res = this._handle.writeSync(flushFlag, chunk, // in
512 inOff, // in_off
513 availInBefore, // in_len
514 this._buffer, // out
515 this._offset, //out_off
516 availOutBefore); // out_len
517 } while (!this._hadError && callback(res[0], res[1]));
518
519 if (this._hadError) {
520 throw error;
521 }
522
523 if (nread >= kMaxLength) {
524 _close(this);
525 throw new RangeError(kRangeErrorMessage);
526 }
527
528 var buf = Buffer.concat(buffers, nread);
529 _close(this);
530
531 return buf;
532 }
533
534 assert(this._handle, 'zlib binding closed');
535 var req = this._handle.write(flushFlag, chunk, // in
536 inOff, // in_off
537 availInBefore, // in_len
538 this._buffer, // out
539 this._offset, //out_off
540 availOutBefore); // out_len
541
542 req.buffer = chunk;
543 req.callback = callback;
544
545 function callback(availInAfter, availOutAfter) {
546 // When the callback is used in an async write, the callback's
547 // context is the `req` object that was created. The req object
548 // is === this._handle, and that's why it's important to null
549 // out the values after they are done being used. `this._handle`
550 // can stay in memory longer than the callback and buffer are needed.
551 if (this) {
552 this.buffer = null;
553 this.callback = null;
554 }
555
556 if (self._hadError) return;
557
558 var have = availOutBefore - availOutAfter;
559 assert(have >= 0, 'have should not go down');
560
561 if (have > 0) {
562 var out = self._buffer.slice(self._offset, self._offset + have);
563 self._offset += have;
564 // serve some output to the consumer.
565 if (async) {
566 self.push(out);
567 } else {
568 buffers.push(out);
569 nread += out.length;
570 }
571 }
572
573 // exhausted the output buffer, or used all the input create a new one.
574 if (availOutAfter === 0 || self._offset >= self._chunkSize) {
575 availOutBefore = self._chunkSize;
576 self._offset = 0;
577 self._buffer = Buffer.allocUnsafe(self._chunkSize);
578 }
579
580 if (availOutAfter === 0) {
581 // Not actually done. Need to reprocess.
582 // Also, update the availInBefore to the availInAfter value,
583 // so that if we have to hit it a third (fourth, etc.) time,
584 // it'll have the correct byte counts.
585 inOff += availInBefore - availInAfter;
586 availInBefore = availInAfter;
587
588 if (!async) return true;
589
590 var newReq = self._handle.write(flushFlag, chunk, inOff, availInBefore, self._buffer, self._offset, self._chunkSize);
591 newReq.callback = callback; // this same function
592 newReq.buffer = chunk;
593 return;
594 }
595
596 if (!async) return false;
597
598 // finished with the chunk.
599 cb();
600 }
601};
602
603util.inherits(Deflate, Zlib);
604util.inherits(Inflate, Zlib);
605util.inherits(Gzip, Zlib);
606util.inherits(Gunzip, Zlib);
607util.inherits(DeflateRaw, Zlib);
608util.inherits(InflateRaw, Zlib);
609util.inherits(Unzip, Zlib);
\No newline at end of file