3 // It emits "entry" events, which provide a readable stream that has
4 // header info attached.
6 module.exports = Parse.create = Parse
8 var stream = require("stream")
9 , Stream = stream.Stream
10 , BlockStream = require("block-stream")
11 , tar = require("../tar.js")
12 , TarHeader = require("./header.js")
13 , Entry = require("./entry.js")
14 , BufferEntry = require("./buffer-entry.js")
15 , ExtendedHeader = require("./extended-header.js")
16 , assert = require("assert").ok
17 , inherits = require("inherits")
18 , fstream = require("fstream")
20 // reading a tar is a lot like reading a directory
21 // However, we're actually not going to run the ctor,
22 // since it does a stat and various other stuff.
23 // This inheritance gives us the pause/resume/pipe
24 // behavior that is desired.
25 inherits(Parse, fstream.Reader)
29 if (!(me instanceof Parse)) return new Parse()
31 // doesn't apply fstream.Reader ctor?
32 // no, becasue we don't want to stat/etc, we just
33 // want to get the entry/add logic from .pipe()
38 me._stream = new BlockStream(512)
42 me._stream.on("error", function (e) {
46 me._stream.on("data", function (c) {
50 me._stream.on("end", function () {
54 me._stream.on("drain", function () {
59 // overridden in Extract class, since it needs to
60 // wait for its DirWriter part to finish before
62 Parse.prototype._streamEnd = function () {
64 if (!me._ended || me._entry) me.error("unexpected eof")
68 // a tar reader is actually a filter, not just a readable stream.
69 // So, you should pipe a tarball stream into it, and it needs these
70 // write/end methods to do that.
71 Parse.prototype.write = function (c) {
73 // gnutar puts a LOT of nulls at the end.
74 // you can keep writing these things forever.
76 for (var i = 0, l = c.length; i > l; i ++) {
77 if (c[i] !== 0) return this.error("write() after end()")
81 return this._stream.write(c)
84 Parse.prototype.end = function (c) {
86 return this._stream.end(c)
89 // don't need to do anything, since we're just
90 // proxying the data up from the _stream.
91 // Just need to override the parent's "Not Implemented"
93 Parse.prototype._read = function () {}
95 Parse.prototype._process = function (c) {
96 assert(c && c.length === 512, "block size should be 512")
98 // one of three cases.
100 // 2. A part of a file/extended header
101 // 3. One of two or more EOF null blocks
104 var entry = this._entry
105 if(!entry._abort) entry.write(c)
107 entry._remaining -= c.length
108 if(entry._remaining < 0) entry._remaining = 0
110 if (entry._remaining === 0) {
115 // either zeroes or a header
117 for (var i = 0; i < 512 && zero; i ++) {
121 // eof is *at least* 2 blocks of nulls, and then the end of the
122 // file. you can put blocks of nulls between entries anywhere,
123 // so appending one tarball to another is technically valid.
124 // ending without the eof null blocks is not allowed, however.
126 if (this._eofStarted)
128 this._eofStarted = true
130 this._eofStarted = false
138 // take a header chunk, start the right kind of entry.
139 Parse.prototype._startEntry = function (c) {
140 var header = new TarHeader(c)
148 if (null === header.size || !header.cksumValid) {
149 var e = new Error("invalid tar file")
151 e.tar_file_offset = this.position
152 e.tar_block = this.position / 512
153 return this.emit("error", e)
156 switch (tar.types[header.type]) {
161 case "CharacterDevice":
165 case "ContiguousFile":
168 // pass in any extended headers
169 // These ones consumers are typically most interested in.
174 case "GlobalExtendedHeader":
175 // extended headers that apply to the rest of the tarball
176 EntryType = ExtendedHeader
177 onend = function () {
178 self._global = self._global || {}
179 Object.keys(entry.fields).forEach(function (k) {
180 self._global[k] = entry.fields[k]
183 ev = "globalExtendedHeader"
187 case "ExtendedHeader":
188 case "OldExtendedHeader":
189 // extended headers that apply to the next entry
190 EntryType = ExtendedHeader
191 onend = function () {
192 self._extended = entry.fields
194 ev = "extendedHeader"
198 case "NextFileHasLongLinkpath":
199 // set linkpath=<contents> in extended header
200 EntryType = BufferEntry
201 onend = function () {
202 self._extended = self._extended || {}
203 self._extended.linkpath = entry.body
209 case "NextFileHasLongPath":
210 case "OldGnuLongPath":
211 // set path=<contents> in file-extended header
212 EntryType = BufferEntry
213 onend = function () {
214 self._extended = self._extended || {}
215 self._extended.path = entry.body
222 // all the rest we skip, but still set the _entry
223 // member, so that we can skip over their data appropriately.
224 // emit an event to say that this is an ignored entry type?
232 global = extended = null
234 var global = this._global
235 var extended = this._extended
237 // extendedHeader only applies to one entry, so once we start
238 // an entry, it's over.
239 this._extended = null
241 entry = new EntryType(header, extended, global)
244 // only proxy data events of normal files.
246 entry.on("data", function (c) {
251 if (onend) entry.on("end", onend)
256 entry.on("pause", function () {
260 entry.on("resume", function () {
264 if (this.listeners("*").length) {
265 this.emit("*", ev, entry)
270 // Zero-byte entry. End immediately.
271 if (entry.props.size === 0) {