var constants = require('fs-constants'); var eos = require('end-of-stream'); var inherits = require('inherits'); var alloc = Buffer.alloc; var Readable = require('readable-stream').Readable; var Writable = require('readable-stream').Writable; var StringDecoder = require('string_decoder').StringDecoder; var headers = require('./headers'); var DMODE = parseInt('755', 8); var FMODE = parseInt('644', 8); var END_OF_TAR = alloc(1024); var noop = function () {}; var overflow = function (self, size) { size &= 511; if (size) self.push(END_OF_TAR.slice(0, 512 - size)); }; function modeToType(mode) { switch (mode & constants.S_IFMT) { case constants.S_IFBLK: return 'block-device'; case constants.S_IFCHR: return 'character-device'; case constants.S_IFDIR: return 'directory'; case constants.S_IFIFO: return 'fifo'; case constants.S_IFLNK: return 'symlink'; } return 'file'; } var Sink = function (to) { Writable.call(this); this.written = 0; this._to = to; this._destroyed = false; }; inherits(Sink, Writable); Sink.prototype._write = function (data, enc, cb) { this.written += data.length; if (this._to.push(data)) return cb(); this._to._drain = cb; }; Sink.prototype.destroy = function () { if (this._destroyed) return; this._destroyed = true; this.emit('close'); }; var LinkSink = function () { Writable.call(this); this.linkname = ''; this._decoder = new StringDecoder('utf-8'); this._destroyed = false; }; inherits(LinkSink, Writable); LinkSink.prototype._write = function (data, enc, cb) { this.linkname += this._decoder.write(data); cb(); }; LinkSink.prototype.destroy = function () { if (this._destroyed) return; this._destroyed = true; this.emit('close'); }; var Void = function () { Writable.call(this); this._destroyed = false; }; inherits(Void, Writable); Void.prototype._write = function (data, enc, cb) { cb(new Error('No body allowed for this entry')); }; Void.prototype.destroy = function () { if (this._destroyed) return; this._destroyed = true; this.emit('close'); }; var Pack = function (opts) { if (!(this instanceof Pack)) return new Pack(opts); Readable.call(this, opts); this._drain = noop; this._finalized = false; this._finalizing = false; this._destroyed = false; this._stream = null; }; inherits(Pack, Readable); Pack.prototype.entry = function (header, buffer, callback) { if (this._stream) throw new Error('already piping an entry'); if (this._finalized || this._destroyed) return; if (typeof buffer === 'function') { callback = buffer; buffer = null; } if (!callback) callback = noop; var self = this; if (!header.size || header.type === 'symlink') header.size = 0; if (!header.type) header.type = modeToType(header.mode); if (!header.mode) header.mode = header.type === 'directory' ? DMODE : FMODE; if (!header.uid) header.uid = 0; if (!header.gid) header.gid = 0; if (!header.mtime) header.mtime = new Date(); if (typeof buffer === 'string') buffer = Buffer.from(buffer); if (Buffer.isBuffer(buffer)) { header.size = buffer.length; this._encode(header); var ok = this.push(buffer); overflow(self, header.size); if (ok) process.nextTick(callback); else this._drain = callback; return new Void(); } if (header.type === 'symlink' && !header.linkname) { var linkSink = new LinkSink(); eos(linkSink, function (err) { if (err) { // stream was closed self.destroy(); return callback(err); } header.linkname = linkSink.linkname; self._encode(header); callback(); }); return linkSink; } this._encode(header); if (header.type !== 'file' && header.type !== 'contiguous-file') { process.nextTick(callback); return new Void(); } var sink = new Sink(this); this._stream = sink; eos(sink, function (err) { self._stream = null; if (err) { // stream was closed self.destroy(); return callback(err); } if (sink.written !== header.size) { // corrupting tar self.destroy(); return callback(new Error('size mismatch')); } overflow(self, header.size); if (self._finalizing) self.finalize(); callback(); }); return sink; }; Pack.prototype.finalize = function () { if (this._stream) { this._finalizing = true; return; } if (this._finalized) return; this._finalized = true; this.push(END_OF_TAR); this.push(null); }; Pack.prototype.destroy = function (err) { if (this._destroyed) return; this._destroyed = true; if (err) this.emit('error', err); this.emit('close'); if (this._stream && this._stream.destroy) this._stream.destroy(); }; Pack.prototype._encode = function (header) { if (!header.pax) { var buf = headers.encode(header); if (buf) { this.push(buf); return; } } this._encodePax(header); }; Pack.prototype._encodePax = function (header) { var paxHeader = headers.encodePax({ name: header.name, linkname: header.linkname, pax: header.pax, }); var newHeader = { name: 'PaxHeader', mode: header.mode, uid: header.uid, gid: header.gid, size: paxHeader.length, mtime: header.mtime, type: 'pax-header', linkname: header.linkname && 'PaxHeader', uname: header.uname, gname: header.gname, devmajor: header.devmajor, devminor: header.devminor, }; this.push(headers.encode(newHeader)); this.push(paxHeader); overflow(this, paxHeader.length); newHeader.size = header.size; newHeader.type = header.type; this.push(headers.encode(newHeader)); }; Pack.prototype._read = function (n) { var drain = this._drain; this._drain = noop; drain(); }; module.exports = Pack;