123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- // A writable stream.
- // It emits "entry" events, which provide a readable stream that has
- // header info attached.
- module.exports = Parse.create = Parse
- var stream = require("stream")
- , Stream = stream.Stream
- , BlockStream = require("block-stream")
- , tar = require("../tar.js")
- , TarHeader = require("./header.js")
- , Entry = require("./entry.js")
- , BufferEntry = require("./buffer-entry.js")
- , ExtendedHeader = require("./extended-header.js")
- , assert = require("assert").ok
- , inherits = require("inherits")
- , fstream = require("fstream")
- // reading a tar is a lot like reading a directory
- // However, we're actually not going to run the ctor,
- // since it does a stat and various other stuff.
- // This inheritance gives us the pause/resume/pipe
- // behavior that is desired.
- inherits(Parse, fstream.Reader)
- function Parse () {
- var me = this
- if (!(me instanceof Parse)) return new Parse()
- // doesn't apply fstream.Reader ctor?
- // no, becasue we don't want to stat/etc, we just
- // want to get the entry/add logic from .pipe()
- Stream.apply(me)
- me.writable = true
- me.readable = true
- me._stream = new BlockStream(512)
- me.position = 0
- me._ended = false
- me._stream.on("error", function (e) {
- me.emit("error", e)
- })
- me._stream.on("data", function (c) {
- me._process(c)
- })
- me._stream.on("end", function () {
- me._streamEnd()
- })
- me._stream.on("drain", function () {
- me.emit("drain")
- })
- }
- // overridden in Extract class, since it needs to
- // wait for its DirWriter part to finish before
- // emitting "end"
- Parse.prototype._streamEnd = function () {
- var me = this
- if (!me._ended || me._entry) me.error("unexpected eof")
- me.emit("end")
- }
- // a tar reader is actually a filter, not just a readable stream.
- // So, you should pipe a tarball stream into it, and it needs these
- // write/end methods to do that.
- Parse.prototype.write = function (c) {
- if (this._ended) {
- // gnutar puts a LOT of nulls at the end.
- // you can keep writing these things forever.
- // Just ignore them.
- for (var i = 0, l = c.length; i > l; i ++) {
- if (c[i] !== 0) return this.error("write() after end()")
- }
- return
- }
- return this._stream.write(c)
- }
- Parse.prototype.end = function (c) {
- this._ended = true
- return this._stream.end(c)
- }
- // don't need to do anything, since we're just
- // proxying the data up from the _stream.
- // Just need to override the parent's "Not Implemented"
- // error-thrower.
- Parse.prototype._read = function () {}
- Parse.prototype._process = function (c) {
- assert(c && c.length === 512, "block size should be 512")
- // one of three cases.
- // 1. A new header
- // 2. A part of a file/extended header
- // 3. One of two or more EOF null blocks
- if (this._entry) {
- var entry = this._entry
- if(!entry._abort) entry.write(c)
- else {
- entry._remaining -= c.length
- if(entry._remaining < 0) entry._remaining = 0
- }
- if (entry._remaining === 0) {
- entry.end()
- this._entry = null
- }
- } else {
- // either zeroes or a header
- var zero = true
- for (var i = 0; i < 512 && zero; i ++) {
- zero = c[i] === 0
- }
- // eof is *at least* 2 blocks of nulls, and then the end of the
- // file. you can put blocks of nulls between entries anywhere,
- // so appending one tarball to another is technically valid.
- // ending without the eof null blocks is not allowed, however.
- if (zero) {
- if (this._eofStarted)
- this._ended = true
- this._eofStarted = true
- } else {
- this._eofStarted = false
- this._startEntry(c)
- }
- }
- this.position += 512
- }
- // take a header chunk, start the right kind of entry.
- Parse.prototype._startEntry = function (c) {
- var header = new TarHeader(c)
- , self = this
- , entry
- , ev
- , EntryType
- , onend
- , meta = false
- if (null === header.size || !header.cksumValid) {
- var e = new Error("invalid tar file")
- e.header = header
- e.tar_file_offset = this.position
- e.tar_block = this.position / 512
- return this.emit("error", e)
- }
- switch (tar.types[header.type]) {
- case "File":
- case "OldFile":
- case "Link":
- case "SymbolicLink":
- case "CharacterDevice":
- case "BlockDevice":
- case "Directory":
- case "FIFO":
- case "ContiguousFile":
- case "GNUDumpDir":
- // start a file.
- // pass in any extended headers
- // These ones consumers are typically most interested in.
- EntryType = Entry
- ev = "entry"
- break
- case "GlobalExtendedHeader":
- // extended headers that apply to the rest of the tarball
- EntryType = ExtendedHeader
- onend = function () {
- self._global = self._global || {}
- Object.keys(entry.fields).forEach(function (k) {
- self._global[k] = entry.fields[k]
- })
- }
- ev = "globalExtendedHeader"
- meta = true
- break
- case "ExtendedHeader":
- case "OldExtendedHeader":
- // extended headers that apply to the next entry
- EntryType = ExtendedHeader
- onend = function () {
- self._extended = entry.fields
- }
- ev = "extendedHeader"
- meta = true
- break
- case "NextFileHasLongLinkpath":
- // set linkpath=<contents> in extended header
- EntryType = BufferEntry
- onend = function () {
- self._extended = self._extended || {}
- self._extended.linkpath = entry.body
- }
- ev = "longLinkpath"
- meta = true
- break
- case "NextFileHasLongPath":
- case "OldGnuLongPath":
- // set path=<contents> in file-extended header
- EntryType = BufferEntry
- onend = function () {
- self._extended = self._extended || {}
- self._extended.path = entry.body
- }
- ev = "longPath"
- meta = true
- break
- default:
- // all the rest we skip, but still set the _entry
- // member, so that we can skip over their data appropriately.
- // emit an event to say that this is an ignored entry type?
- EntryType = Entry
- ev = "ignoredEntry"
- break
- }
- var global, extended
- if (meta) {
- global = extended = null
- } else {
- var global = this._global
- var extended = this._extended
- // extendedHeader only applies to one entry, so once we start
- // an entry, it's over.
- this._extended = null
- }
- entry = new EntryType(header, extended, global)
- entry.meta = meta
- // only proxy data events of normal files.
- if (!meta) {
- entry.on("data", function (c) {
- me.emit("data", c)
- })
- }
- if (onend) entry.on("end", onend)
- this._entry = entry
- var me = this
- entry.on("pause", function () {
- me.pause()
- })
- entry.on("resume", function () {
- me.resume()
- })
- if (this.listeners("*").length) {
- this.emit("*", ev, entry)
- }
- this.emit(ev, entry)
- // Zero-byte entry. End immediately.
- if (entry.props.size === 0) {
- entry.end()
- this._entry = null
- }
- }
|