blob: eec3496dd0e7264816cd310e6b57f30a343bdbef [file] [log] [blame]
var util = require('util')
var eos = require('end-of-stream')
var headers = require('./headers')
var Readable = require('readable-stream').Readable
var Writable = require('readable-stream').Writable
var PassThrough = require('readable-stream').PassThrough
var END_OF_TAR = new Buffer(1024)
END_OF_TAR.fill(0)
var noop = function() {}
var overflow = function(self, size) {
size &= 511
if (size) self.push(END_OF_TAR.slice(0, 512 - size))
}
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 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.size = 0
if (!header.type) header.type = 'file'
if (!header.mode) header.mode = header.type === 'directory' ? 0755 : 0644
if (!header.uid) header.uid = 0
if (!header.gid) header.gid = 0
if (!header.mtime) header.mtime = new Date()
if (typeof buffer === 'string') buffer = new Buffer(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 !== 'file' && header.type !== 'contigious-file') {
this._encode(header)
process.nextTick(callback)
return new Void()
}
var sink = new Sink(this)
this._encode(header)
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) {
var buf = headers.encode(header)
if (buf) this.push(buf)
else this._encodePax(header)
}
Pack.prototype._encodePax = function(header) {
var paxHeader = headers.encodePax({
name: header.name,
linkname: header.linkname
})
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