123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- const { Readable, Writable, getStreamError } = require('streamx')
- const b4a = require('b4a')
- const constants = require('./constants')
- const headers = require('./headers')
- const DMODE = 0o755
- const FMODE = 0o644
- const END_OF_TAR = b4a.alloc(1024)
- class Sink extends Writable {
- constructor (pack, header, callback) {
- super({ mapWritable, eagerOpen: true })
- this.written = 0
- this.header = header
- this._callback = callback
- this._linkname = null
- this._isLinkname = header.type === 'symlink' && !header.linkname
- this._isVoid = header.type !== 'file' && header.type !== 'contiguous-file'
- this._finished = false
- this._pack = pack
- this._openCallback = null
- if (this._pack._stream === null) this._pack._stream = this
- else this._pack._pending.push(this)
- }
- _open (cb) {
- this._openCallback = cb
- if (this._pack._stream === this) this._continueOpen()
- }
- _continuePack (err) {
- if (this._callback === null) return
- const callback = this._callback
- this._callback = null
- callback(err)
- }
- _continueOpen () {
- if (this._pack._stream === null) this._pack._stream = this
- const cb = this._openCallback
- this._openCallback = null
- if (cb === null) return
- if (this._pack.destroying) return cb(new Error('pack stream destroyed'))
- if (this._pack._finalized) return cb(new Error('pack stream is already finalized'))
- this._pack._stream = this
- if (!this._isLinkname) {
- this._pack._encode(this.header)
- }
- if (this._isVoid) {
- this._finish()
- this._continuePack(null)
- }
- cb(null)
- }
- _write (data, cb) {
- if (this._isLinkname) {
- this._linkname = this._linkname ? b4a.concat([this._linkname, data]) : data
- return cb(null)
- }
- if (this._isVoid) {
- if (data.byteLength > 0) {
- return cb(new Error('No body allowed for this entry'))
- }
- return cb()
- }
- this.written += data.byteLength
- if (this._pack.push(data)) return cb()
- this._pack._drain = cb
- }
- _finish () {
- if (this._finished) return
- this._finished = true
- if (this._isLinkname) {
- this.header.linkname = this._linkname ? b4a.toString(this._linkname, 'utf-8') : ''
- this._pack._encode(this.header)
- }
- overflow(this._pack, this.header.size)
- this._pack._done(this)
- }
- _final (cb) {
- if (this.written !== this.header.size) { // corrupting tar
- return cb(new Error('Size mismatch'))
- }
- this._finish()
- cb(null)
- }
- _getError () {
- return getStreamError(this) || new Error('tar entry destroyed')
- }
- _predestroy () {
- this._pack.destroy(this._getError())
- }
- _destroy (cb) {
- this._pack._done(this)
- this._continuePack(this._finished ? null : this._getError())
- cb()
- }
- }
- class Pack extends Readable {
- constructor (opts) {
- super(opts)
- this._drain = noop
- this._finalized = false
- this._finalizing = false
- this._pending = []
- this._stream = null
- }
- entry (header, buffer, callback) {
- if (this._finalized || this.destroying) throw new Error('already finalized or destroyed')
- if (typeof buffer === 'function') {
- callback = buffer
- buffer = null
- }
- if (!callback) callback = noop
- 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 = b4a.from(buffer)
- const sink = new Sink(this, header, callback)
- if (b4a.isBuffer(buffer)) {
- header.size = buffer.byteLength
- sink.write(buffer)
- sink.end()
- return sink
- }
- if (sink._isVoid) {
- return sink
- }
- return sink
- }
- finalize () {
- if (this._stream || this._pending.length > 0) {
- this._finalizing = true
- return
- }
- if (this._finalized) return
- this._finalized = true
- this.push(END_OF_TAR)
- this.push(null)
- }
- _done (stream) {
- if (stream !== this._stream) return
- this._stream = null
- if (this._finalizing) this.finalize()
- if (this._pending.length) this._pending.shift()._continueOpen()
- }
- _encode (header) {
- if (!header.pax) {
- const buf = headers.encode(header)
- if (buf) {
- this.push(buf)
- return
- }
- }
- this._encodePax(header)
- }
- _encodePax (header) {
- const paxHeader = headers.encodePax({
- name: header.name,
- linkname: header.linkname,
- pax: header.pax
- })
- const newHeader = {
- name: 'PaxHeader',
- mode: header.mode,
- uid: header.uid,
- gid: header.gid,
- size: paxHeader.byteLength,
- 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.byteLength)
- newHeader.size = header.size
- newHeader.type = header.type
- this.push(headers.encode(newHeader))
- }
- _doDrain () {
- const drain = this._drain
- this._drain = noop
- drain()
- }
- _predestroy () {
- const err = getStreamError(this)
- if (this._stream) this._stream.destroy(err)
- while (this._pending.length) {
- const stream = this._pending.shift()
- stream.destroy(err)
- stream._continueOpen()
- }
- this._doDrain()
- }
- _read (cb) {
- this._doDrain()
- cb()
- }
- }
- module.exports = function pack (opts) {
- return new Pack(opts)
- }
- 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'
- }
- function noop () {}
- function overflow (self, size) {
- size &= 511
- if (size) self.push(END_OF_TAR.subarray(0, 512 - size))
- }
- function mapWritable (buf) {
- return b4a.isBuffer(buf) ? buf : b4a.from(buf)
- }
|