index.js 8.4 KB

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