Initial commit
[yaffs-website] / node_modules / tar / lib / parse.js
1
2 // A writable stream.
3 // It emits "entry" events, which provide a readable stream that has
4 // header info attached.
5
6 module.exports = Parse.create = Parse
7
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")
19
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)
26
27 function Parse () {
28   var me = this
29   if (!(me instanceof Parse)) return new Parse()
30
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()
34   Stream.apply(me)
35
36   me.writable = true
37   me.readable = true
38   me._stream = new BlockStream(512)
39   me.position = 0
40   me._ended = false
41
42   me._stream.on("error", function (e) {
43     me.emit("error", e)
44   })
45
46   me._stream.on("data", function (c) {
47     me._process(c)
48   })
49
50   me._stream.on("end", function () {
51     me._streamEnd()
52   })
53
54   me._stream.on("drain", function () {
55     me.emit("drain")
56   })
57 }
58
59 // overridden in Extract class, since it needs to
60 // wait for its DirWriter part to finish before
61 // emitting "end"
62 Parse.prototype._streamEnd = function () {
63   var me = this
64   if (!me._ended || me._entry) me.error("unexpected eof")
65   me.emit("end")
66 }
67
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) {
72   if (this._ended) {
73     // gnutar puts a LOT of nulls at the end.
74     // you can keep writing these things forever.
75     // Just ignore them.
76     for (var i = 0, l = c.length; i > l; i ++) {
77       if (c[i] !== 0) return this.error("write() after end()")
78     }
79     return
80   }
81   return this._stream.write(c)
82 }
83
84 Parse.prototype.end = function (c) {
85   this._ended = true
86   return this._stream.end(c)
87 }
88
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"
92 // error-thrower.
93 Parse.prototype._read = function () {}
94
95 Parse.prototype._process = function (c) {
96   assert(c && c.length === 512, "block size should be 512")
97
98   // one of three cases.
99   // 1. A new header
100   // 2. A part of a file/extended header
101   // 3. One of two or more EOF null blocks
102
103   if (this._entry) {
104     var entry = this._entry
105     if(!entry._abort) entry.write(c)
106     else {
107       entry._remaining -= c.length
108       if(entry._remaining < 0) entry._remaining = 0
109     }
110     if (entry._remaining === 0) {
111       entry.end()
112       this._entry = null
113     }
114   } else {
115     // either zeroes or a header
116     var zero = true
117     for (var i = 0; i < 512 && zero; i ++) {
118       zero = c[i] === 0
119     }
120
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.
125     if (zero) {
126       if (this._eofStarted)
127         this._ended = true
128       this._eofStarted = true
129     } else {
130       this._eofStarted = false
131       this._startEntry(c)
132     }
133   }
134
135   this.position += 512
136 }
137
138 // take a header chunk, start the right kind of entry.
139 Parse.prototype._startEntry = function (c) {
140   var header = new TarHeader(c)
141     , self = this
142     , entry
143     , ev
144     , EntryType
145     , onend
146     , meta = false
147
148   if (null === header.size || !header.cksumValid) {
149     var e = new Error("invalid tar file")
150     e.header = header
151     e.tar_file_offset = this.position
152     e.tar_block = this.position / 512
153     return this.emit("error", e)
154   }
155
156   switch (tar.types[header.type]) {
157     case "File":
158     case "OldFile":
159     case "Link":
160     case "SymbolicLink":
161     case "CharacterDevice":
162     case "BlockDevice":
163     case "Directory":
164     case "FIFO":
165     case "ContiguousFile":
166     case "GNUDumpDir":
167       // start a file.
168       // pass in any extended headers
169       // These ones consumers are typically most interested in.
170       EntryType = Entry
171       ev = "entry"
172       break
173
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]
181         })
182       }
183       ev = "globalExtendedHeader"
184       meta = true
185       break
186
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
193       }
194       ev = "extendedHeader"
195       meta = true
196       break
197
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
204       }
205       ev = "longLinkpath"
206       meta = true
207       break
208
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
216       }
217       ev = "longPath"
218       meta = true
219       break
220
221     default:
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?
225       EntryType = Entry
226       ev = "ignoredEntry"
227       break
228   }
229
230   var global, extended
231   if (meta) {
232     global = extended = null
233   } else {
234     var global = this._global
235     var extended = this._extended
236
237     // extendedHeader only applies to one entry, so once we start
238     // an entry, it's over.
239     this._extended = null
240   }
241   entry = new EntryType(header, extended, global)
242   entry.meta = meta
243
244   // only proxy data events of normal files.
245   if (!meta) {
246     entry.on("data", function (c) {
247       me.emit("data", c)
248     })
249   }
250
251   if (onend) entry.on("end", onend)
252
253   this._entry = entry
254   var me = this
255
256   entry.on("pause", function () {
257     me.pause()
258   })
259
260   entry.on("resume", function () {
261     me.resume()
262   })
263
264   if (this.listeners("*").length) {
265     this.emit("*", ev, entry)
266   }
267
268   this.emit(ev, entry)
269
270   // Zero-byte entry.  End immediately.
271   if (entry.props.size === 0) {
272     entry.end()
273     this._entry = null
274   }
275 }