You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

extract.js 5.8KB

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