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.
255 lines
5.5 KiB
255 lines
5.5 KiB
var constants = require('fs-constants') |
|
var eos = require('end-of-stream') |
|
var util = require('util') |
|
var alloc = require('buffer-alloc') |
|
var toBuffer = require('to-buffer') |
|
|
|
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 |
|
} |
|
|
|
util.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 |
|
} |
|
|
|
util.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 |
|
} |
|
|
|
util.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 |
|
} |
|
|
|
util.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 = toBuffer(buffer) |
|
if (Buffer.isBuffer(buffer)) { |
|
header.size = buffer.length |
|
this._encode(header) |
|
this.push(buffer) |
|
overflow(self, header.size) |
|
process.nextTick(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
|
|
|