index.js 8.9 KB

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