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