extract.js 5.4 KB

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