index.js 11 KB

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