pack.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. var constants = require('constants')
  2. var eos = require('end-of-stream')
  3. var util = require('util')
  4. var Readable = require('readable-stream').Readable
  5. var Writable = require('readable-stream').Writable
  6. var StringDecoder = require('string_decoder').StringDecoder
  7. var headers = require('./headers')
  8. var DMODE = parseInt('755', 8)
  9. var FMODE = parseInt('644', 8)
  10. var END_OF_TAR = new Buffer(1024)
  11. END_OF_TAR.fill(0)
  12. var noop = function () {}
  13. var overflow = function (self, size) {
  14. size &= 511
  15. if (size) self.push(END_OF_TAR.slice(0, 512 - size))
  16. }
  17. function modeToType (mode) {
  18. switch (mode & constants.S_IFMT) {
  19. case constants.S_IFBLK: return 'block-device'
  20. case constants.S_IFCHR: return 'character-device'
  21. case constants.S_IFDIR: return 'directory'
  22. case constants.S_IFIFO: return 'fifo'
  23. case constants.S_IFLNK: return 'symlink'
  24. }
  25. return 'file'
  26. }
  27. var Sink = function (to) {
  28. Writable.call(this)
  29. this.written = 0
  30. this._to = to
  31. this._destroyed = false
  32. }
  33. util.inherits(Sink, Writable)
  34. Sink.prototype._write = function (data, enc, cb) {
  35. this.written += data.length
  36. if (this._to.push(data)) return cb()
  37. this._to._drain = cb
  38. }
  39. Sink.prototype.destroy = function () {
  40. if (this._destroyed) return
  41. this._destroyed = true
  42. this.emit('close')
  43. }
  44. var LinkSink = function () {
  45. Writable.call(this)
  46. this.linkname = ''
  47. this._decoder = new StringDecoder('utf-8')
  48. this._destroyed = false
  49. }
  50. util.inherits(LinkSink, Writable)
  51. LinkSink.prototype._write = function (data, enc, cb) {
  52. this.linkname += this._decoder.write(data)
  53. cb()
  54. }
  55. LinkSink.prototype.destroy = function () {
  56. if (this._destroyed) return
  57. this._destroyed = true
  58. this.emit('close')
  59. }
  60. var Void = function () {
  61. Writable.call(this)
  62. this._destroyed = false
  63. }
  64. util.inherits(Void, Writable)
  65. Void.prototype._write = function (data, enc, cb) {
  66. cb(new Error('No body allowed for this entry'))
  67. }
  68. Void.prototype.destroy = function () {
  69. if (this._destroyed) return
  70. this._destroyed = true
  71. this.emit('close')
  72. }
  73. var Pack = function (opts) {
  74. if (!(this instanceof Pack)) return new Pack(opts)
  75. Readable.call(this, opts)
  76. this._drain = noop
  77. this._finalized = false
  78. this._finalizing = false
  79. this._destroyed = false
  80. this._stream = null
  81. }
  82. util.inherits(Pack, Readable)
  83. Pack.prototype.entry = function (header, buffer, callback) {
  84. if (this._stream) throw new Error('already piping an entry')
  85. if (this._finalized || this._destroyed) return
  86. if (typeof buffer === 'function') {
  87. callback = buffer
  88. buffer = null
  89. }
  90. if (!callback) callback = noop
  91. var self = this
  92. if (!header.size || header.type === 'symlink') header.size = 0
  93. if (!header.type) header.type = modeToType(header.mode)
  94. if (!header.mode) header.mode = header.type === 'directory' ? DMODE : FMODE
  95. if (!header.uid) header.uid = 0
  96. if (!header.gid) header.gid = 0
  97. if (!header.mtime) header.mtime = new Date()
  98. if (typeof buffer === 'string') buffer = new Buffer(buffer)
  99. if (Buffer.isBuffer(buffer)) {
  100. header.size = buffer.length
  101. this._encode(header)
  102. this.push(buffer)
  103. overflow(self, header.size)
  104. process.nextTick(callback)
  105. return new Void()
  106. }
  107. if (header.type === 'symlink' && !header.linkname) {
  108. var linkSink = new LinkSink()
  109. eos(linkSink, function (err) {
  110. if (err) { // stream was closed
  111. self.destroy()
  112. return callback(err)
  113. }
  114. header.linkname = linkSink.linkname
  115. self._encode(header)
  116. callback()
  117. })
  118. return linkSink
  119. }
  120. this._encode(header)
  121. if (header.type !== 'file' && header.type !== 'contiguous-file') {
  122. process.nextTick(callback)
  123. return new Void()
  124. }
  125. var sink = new Sink(this)
  126. this._stream = sink
  127. eos(sink, function (err) {
  128. self._stream = null
  129. if (err) { // stream was closed
  130. self.destroy()
  131. return callback(err)
  132. }
  133. if (sink.written !== header.size) { // corrupting tar
  134. self.destroy()
  135. return callback(new Error('size mismatch'))
  136. }
  137. overflow(self, header.size)
  138. if (self._finalizing) self.finalize()
  139. callback()
  140. })
  141. return sink
  142. }
  143. Pack.prototype.finalize = function () {
  144. if (this._stream) {
  145. this._finalizing = true
  146. return
  147. }
  148. if (this._finalized) return
  149. this._finalized = true
  150. this.push(END_OF_TAR)
  151. this.push(null)
  152. }
  153. Pack.prototype.destroy = function (err) {
  154. if (this._destroyed) return
  155. this._destroyed = true
  156. if (err) this.emit('error', err)
  157. this.emit('close')
  158. if (this._stream && this._stream.destroy) this._stream.destroy()
  159. }
  160. Pack.prototype._encode = function (header) {
  161. if (!header.pax) {
  162. var buf = headers.encode(header)
  163. if (buf) {
  164. this.push(buf)
  165. return
  166. }
  167. }
  168. this._encodePax(header)
  169. }
  170. Pack.prototype._encodePax = function (header) {
  171. var paxHeader = headers.encodePax({
  172. name: header.name,
  173. linkname: header.linkname,
  174. pax: header.pax
  175. })
  176. var newHeader = {
  177. name: 'PaxHeader',
  178. mode: header.mode,
  179. uid: header.uid,
  180. gid: header.gid,
  181. size: paxHeader.length,
  182. mtime: header.mtime,
  183. type: 'pax-header',
  184. linkname: header.linkname && 'PaxHeader',
  185. uname: header.uname,
  186. gname: header.gname,
  187. devmajor: header.devmajor,
  188. devminor: header.devminor
  189. }
  190. this.push(headers.encode(newHeader))
  191. this.push(paxHeader)
  192. overflow(this, paxHeader.length)
  193. newHeader.size = header.size
  194. newHeader.type = header.type
  195. this.push(headers.encode(newHeader))
  196. }
  197. Pack.prototype._read = function (n) {
  198. var drain = this._drain
  199. this._drain = noop
  200. drain()
  201. }
  202. module.exports = Pack