entry.js
5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// A passthrough read/write stream that sets its properties
// based on a header, extendedHeader, and globalHeader
//
// Can be either a file system object of some sort, or
// a pax/ustar metadata entry.
module.exports = Entry
var TarHeader = require("./header.js")
, tar = require("../tar")
, assert = require("assert").ok
, Stream = require("stream").Stream
, inherits = require("inherits")
, fstream = require("fstream").Abstract
function Entry (header, extended, global) {
Stream.call(this)
this.readable = true
this.writable = true
this._needDrain = false
this._paused = false
this._reading = false
this._ending = false
this._ended = false
this._remaining = 0
this._abort = false
this._queue = []
this._index = 0
this._queueLen = 0
this._read = this._read.bind(this)
this.props = {}
this._header = header
this._extended = extended || {}
// globals can change throughout the course of
// a file parse operation. Freeze it at its current state.
this._global = {}
var me = this
Object.keys(global || {}).forEach(function (g) {
me._global[g] = global[g]
})
this._setProps()
}
inherits(Entry, Stream)
Entry.prototype.write = function (c) {
if (this._ending) this.error("write() after end()", null, true)
if (this._remaining === 0) {
this.error("invalid bytes past eof")
}
// often we'll get a bunch of \0 at the end of the last write,
// since chunks will always be 512 bytes when reading a tarball.
if (c.length > this._remaining) {
c = c.slice(0, this._remaining)
}
this._remaining -= c.length
// put it on the stack.
var ql = this._queueLen
this._queue.push(c)
this._queueLen ++
this._read()
// either paused, or buffered
if (this._paused || ql > 0) {
this._needDrain = true
return false
}
return true
}
Entry.prototype.end = function (c) {
if (c) this.write(c)
this._ending = true
this._read()
}
Entry.prototype.pause = function () {
this._paused = true
this.emit("pause")
}
Entry.prototype.resume = function () {
// console.error(" Tar Entry resume", this.path)
this.emit("resume")
this._paused = false
this._read()
return this._queueLen - this._index > 1
}
// This is bound to the instance
Entry.prototype._read = function () {
// console.error(" Tar Entry _read", this.path)
if (this._paused || this._reading || this._ended) return
// set this flag so that event handlers don't inadvertently
// get multiple _read() calls running.
this._reading = true
// have any data to emit?
while (this._index < this._queueLen && !this._paused) {
var chunk = this._queue[this._index ++]
this.emit("data", chunk)
}
// check if we're drained
if (this._index >= this._queueLen) {
this._queue.length = this._queueLen = this._index = 0
if (this._needDrain) {
this._needDrain = false
this.emit("drain")
}
if (this._ending) {
this._ended = true
this.emit("end")
}
}
// if the queue gets too big, then pluck off whatever we can.
// this should be fairly rare.
var mql = this._maxQueueLen
if (this._queueLen > mql && this._index > 0) {
mql = Math.min(this._index, mql)
this._index -= mql
this._queueLen -= mql
this._queue = this._queue.slice(mql)
}
this._reading = false
}
Entry.prototype._setProps = function () {
// props = extended->global->header->{}
var header = this._header
, extended = this._extended
, global = this._global
, props = this.props
// first get the values from the normal header.
var fields = tar.fields
for (var f = 0; fields[f] !== null; f ++) {
var field = fields[f]
, val = header[field]
if (typeof val !== "undefined") props[field] = val
}
// next, the global header for this file.
// numeric values, etc, will have already been parsed.
;[global, extended].forEach(function (p) {
Object.keys(p).forEach(function (f) {
if (typeof p[f] !== "undefined") props[f] = p[f]
})
})
// no nulls allowed in path or linkpath
;["path", "linkpath"].forEach(function (p) {
if (props.hasOwnProperty(p)) {
props[p] = props[p].split("\0")[0]
}
})
// set date fields to be a proper date
;["mtime", "ctime", "atime"].forEach(function (p) {
if (props.hasOwnProperty(p)) {
props[p] = new Date(props[p] * 1000)
}
})
// set the type so that we know what kind of file to create
var type
switch (tar.types[props.type]) {
case "OldFile":
case "ContiguousFile":
type = "File"
break
case "GNUDumpDir":
type = "Directory"
break
case undefined:
type = "Unknown"
break
case "Link":
case "SymbolicLink":
case "CharacterDevice":
case "BlockDevice":
case "Directory":
case "FIFO":
default:
type = tar.types[props.type]
}
this.type = type
this.path = props.path
this.size = props.size
// size is special, since it signals when the file needs to end.
this._remaining = props.size
}
// the parser may not call write if _abort is true.
// useful for skipping data from some files quickly.
Entry.prototype.abort = function(){
this._abort = true
}
Entry.prototype.warn = fstream.warn
Entry.prototype.error = fstream.error