extract.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. var util = require('util')
  2. var bl = require('bl')
  3. var headers = require('./headers')
  4. var Writable = require('readable-stream').Writable
  5. var PassThrough = require('readable-stream').PassThrough
  6. var noop = function () {}
  7. var overflow = function (size) {
  8. size &= 511
  9. return size && 512 - size
  10. }
  11. var emptyStream = function (self, offset) {
  12. var s = new Source(self, offset)
  13. s.end()
  14. return s
  15. }
  16. var mixinPax = function (header, pax) {
  17. if (pax.path) header.name = pax.path
  18. if (pax.linkpath) header.linkname = pax.linkpath
  19. if (pax.size) header.size = parseInt(pax.size, 10)
  20. header.pax = pax
  21. return header
  22. }
  23. var Source = function (self, offset) {
  24. this._parent = self
  25. this.offset = offset
  26. PassThrough.call(this, { autoDestroy: false })
  27. }
  28. util.inherits(Source, PassThrough)
  29. Source.prototype.destroy = function (err) {
  30. this._parent.destroy(err)
  31. }
  32. var Extract = function (opts) {
  33. if (!(this instanceof Extract)) return new Extract(opts)
  34. Writable.call(this, opts)
  35. opts = opts || {}
  36. this._offset = 0
  37. this._buffer = bl()
  38. this._missing = 0
  39. this._partial = false
  40. this._onparse = noop
  41. this._header = null
  42. this._stream = null
  43. this._overflow = null
  44. this._cb = null
  45. this._locked = false
  46. this._destroyed = false
  47. this._pax = null
  48. this._paxGlobal = null
  49. this._gnuLongPath = null
  50. this._gnuLongLinkPath = null
  51. var self = this
  52. var b = self._buffer
  53. var oncontinue = function () {
  54. self._continue()
  55. }
  56. var onunlock = function (err) {
  57. self._locked = false
  58. if (err) return self.destroy(err)
  59. if (!self._stream) oncontinue()
  60. }
  61. var onstreamend = function () {
  62. self._stream = null
  63. var drain = overflow(self._header.size)
  64. if (drain) self._parse(drain, ondrain)
  65. else self._parse(512, onheader)
  66. if (!self._locked) oncontinue()
  67. }
  68. var ondrain = function () {
  69. self._buffer.consume(overflow(self._header.size))
  70. self._parse(512, onheader)
  71. oncontinue()
  72. }
  73. var onpaxglobalheader = function () {
  74. var size = self._header.size
  75. self._paxGlobal = headers.decodePax(b.slice(0, size))
  76. b.consume(size)
  77. onstreamend()
  78. }
  79. var onpaxheader = function () {
  80. var size = self._header.size
  81. self._pax = headers.decodePax(b.slice(0, size))
  82. if (self._paxGlobal) self._pax = Object.assign({}, self._paxGlobal, self._pax)
  83. b.consume(size)
  84. onstreamend()
  85. }
  86. var ongnulongpath = function () {
  87. var size = self._header.size
  88. this._gnuLongPath = headers.decodeLongPath(b.slice(0, size), opts.filenameEncoding)
  89. b.consume(size)
  90. onstreamend()
  91. }
  92. var ongnulonglinkpath = function () {
  93. var size = self._header.size
  94. this._gnuLongLinkPath = headers.decodeLongPath(b.slice(0, size), opts.filenameEncoding)
  95. b.consume(size)
  96. onstreamend()
  97. }
  98. var onheader = function () {
  99. var offset = self._offset
  100. var header
  101. try {
  102. header = self._header = headers.decode(b.slice(0, 512), opts.filenameEncoding, opts.allowUnknownFormat)
  103. } catch (err) {
  104. self.emit('error', err)
  105. }
  106. b.consume(512)
  107. if (!header) {
  108. self._parse(512, onheader)
  109. oncontinue()
  110. return
  111. }
  112. if (header.type === 'gnu-long-path') {
  113. self._parse(header.size, ongnulongpath)
  114. oncontinue()
  115. return
  116. }
  117. if (header.type === 'gnu-long-link-path') {
  118. self._parse(header.size, ongnulonglinkpath)
  119. oncontinue()
  120. return
  121. }
  122. if (header.type === 'pax-global-header') {
  123. self._parse(header.size, onpaxglobalheader)
  124. oncontinue()
  125. return
  126. }
  127. if (header.type === 'pax-header') {
  128. self._parse(header.size, onpaxheader)
  129. oncontinue()
  130. return
  131. }
  132. if (self._gnuLongPath) {
  133. header.name = self._gnuLongPath
  134. self._gnuLongPath = null
  135. }
  136. if (self._gnuLongLinkPath) {
  137. header.linkname = self._gnuLongLinkPath
  138. self._gnuLongLinkPath = null
  139. }
  140. if (self._pax) {
  141. self._header = header = mixinPax(header, self._pax)
  142. self._pax = null
  143. }
  144. self._locked = true
  145. if (!header.size || header.type === 'directory') {
  146. self._parse(512, onheader)
  147. self.emit('entry', header, emptyStream(self, offset), onunlock)
  148. return
  149. }
  150. self._stream = new Source(self, offset)
  151. self.emit('entry', header, self._stream, onunlock)
  152. self._parse(header.size, onstreamend)
  153. oncontinue()
  154. }
  155. this._onheader = onheader
  156. this._parse(512, onheader)
  157. }
  158. util.inherits(Extract, Writable)
  159. Extract.prototype.destroy = function (err) {
  160. if (this._destroyed) return
  161. this._destroyed = true
  162. if (err) this.emit('error', err)
  163. this.emit('close')
  164. if (this._stream) this._stream.emit('close')
  165. }
  166. Extract.prototype._parse = function (size, onparse) {
  167. if (this._destroyed) return
  168. this._offset += size
  169. this._missing = size
  170. if (onparse === this._onheader) this._partial = false
  171. this._onparse = onparse
  172. }
  173. Extract.prototype._continue = function () {
  174. if (this._destroyed) return
  175. var cb = this._cb
  176. this._cb = noop
  177. if (this._overflow) this._write(this._overflow, undefined, cb)
  178. else cb()
  179. }
  180. Extract.prototype._write = function (data, enc, cb) {
  181. if (this._destroyed) return
  182. var s = this._stream
  183. var b = this._buffer
  184. var missing = this._missing
  185. if (data.length) this._partial = true
  186. // we do not reach end-of-chunk now. just forward it
  187. if (data.length < missing) {
  188. this._missing -= data.length
  189. this._overflow = null
  190. if (s) return s.write(data, cb)
  191. b.append(data)
  192. return cb()
  193. }
  194. // end-of-chunk. the parser should call cb.
  195. this._cb = cb
  196. this._missing = 0
  197. var overflow = null
  198. if (data.length > missing) {
  199. overflow = data.slice(missing)
  200. data = data.slice(0, missing)
  201. }
  202. if (s) s.end(data)
  203. else b.append(data)
  204. this._overflow = overflow
  205. this._onparse()
  206. }
  207. Extract.prototype._final = function (cb) {
  208. if (this._partial) return this.destroy(new Error('Unexpected end of data'))
  209. cb()
  210. }
  211. module.exports = Extract