| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 | 
// A writable stream.// It emits "entry" events, which provide a readable stream that has// header info attached.module.exports = Parse.create = Parsevar 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._hardLinks = {}  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  if (entry.type === "Link") {    this._hardLinks[entry.path] = 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  }}
 |