index.js 11 KB

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