index.js 9.2 KB

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