1 | 'use strict';
|
2 |
|
3 | module.exports = Parse.create = Parse;
|
4 |
|
5 | var PullStream = require('pullstream');
|
6 | var Stream = require('stream').Stream;
|
7 | var inherits = require('util').inherits;
|
8 | var zlib = require('zlib');
|
9 | var binary = require('binary');
|
10 | var Entry = require('./entry');
|
11 |
|
12 | inherits(Parse, Stream);
|
13 |
|
14 | function Parse() {
|
15 | var self = this;
|
16 | if (!(this instanceof Parse)) {
|
17 | return new Parse();
|
18 | }
|
19 |
|
20 | Stream.apply(this);
|
21 |
|
22 | this.writable = true;
|
23 | this.readable = true;
|
24 | this._pullStream = new PullStream();
|
25 |
|
26 | this._pullStream.on("error", function (e) {
|
27 | self.emit('error', e);
|
28 | });
|
29 |
|
30 | |
31 |
|
32 |
|
33 |
|
34 | this._readRecord();
|
35 | }
|
36 |
|
37 | Parse.prototype._readRecord = function () {
|
38 | var self = this;
|
39 | this._pullStream.pull(4, function (err, data) {
|
40 | if (err) {
|
41 | return self.emit('error', err);
|
42 | }
|
43 |
|
44 | if (data.length === 0) {
|
45 | return;
|
46 | }
|
47 |
|
48 | var signature = data.readUInt32LE(0);
|
49 | if (signature === 0x04034b50) {
|
50 | self._readFile();
|
51 | } else if (signature === 0x02014b50) {
|
52 | self._readCentralDirectoryFileHeader();
|
53 | } else if (signature === 0x06054b50) {
|
54 | self._readEndOfCentralDirectoryRecord();
|
55 | } else {
|
56 | err = new Error('invalid signature: 0x' + signature.toString(16) + ' (at position: 0x' + data.posInStream.toString(16) + ')');
|
57 | self.emit('error', err);
|
58 | }
|
59 | });
|
60 | };
|
61 |
|
62 | Parse.prototype._readFile = function () {
|
63 | var self = this;
|
64 | this._pullStream.pull(26, function (err, data) {
|
65 | if (err) {
|
66 | return self.emit('error', err);
|
67 | }
|
68 |
|
69 | var vars = binary.parse(data)
|
70 | .word16lu('versionsNeededToExtract')
|
71 | .word16lu('flags')
|
72 | .word16lu('compressionMethod')
|
73 | .word16lu('lastModifiedTime')
|
74 | .word16lu('lastModifiedDate')
|
75 | .word32lu('crc32')
|
76 | .word32lu('compressedSize')
|
77 | .word32lu('uncompressedSize')
|
78 | .word16lu('fileNameLength')
|
79 | .word16lu('extraFieldLength')
|
80 | .vars;
|
81 |
|
82 | return self._pullStream.pull(vars.fileNameLength, function (err, fileName) {
|
83 | if (err) {
|
84 | return self.emit('error', err);
|
85 | }
|
86 | fileName = fileName.toString('utf8');
|
87 | var entry = new Entry(self._pullStream);
|
88 | entry.path = fileName;
|
89 | entry.props.path = fileName;
|
90 | entry.type = (vars.compressedSize === 0 && /[\/\\]$/.test(fileName)) ? 'Directory' : 'File';
|
91 | entry.size = vars.uncompressedSize;
|
92 |
|
93 | self.emit('entry', entry);
|
94 |
|
95 | self._pullStream.pull(vars.extraFieldLength, function (err, extraField) {
|
96 | if (err) {
|
97 | return self.emit('error', err);
|
98 | }
|
99 | if (vars.compressionMethod === 0) {
|
100 | self._pullStream.pull(vars.compressedSize, function (err, compressedData) {
|
101 | if (err) {
|
102 | return self.emit('error', err);
|
103 | }
|
104 |
|
105 | entry.emit('data', compressedData);
|
106 | entry.emit('end');
|
107 |
|
108 | return self._readRecord();
|
109 | });
|
110 | } else {
|
111 | var inflater = zlib.createInflateRaw();
|
112 | inflater.on('error', function (err) {
|
113 | self.emit('error', err);
|
114 | });
|
115 | inflater.on('end', function () {
|
116 | entry.emit('end');
|
117 | self._readRecord();
|
118 | });
|
119 | inflater.on('data', function (uncompressedData) {
|
120 | entry.emit('data', uncompressedData);
|
121 | });
|
122 | self._pullStream.pipe(vars.compressedSize, inflater);
|
123 | }
|
124 | });
|
125 | });
|
126 | });
|
127 | };
|
128 |
|
129 | Parse.prototype._readCentralDirectoryFileHeader = function () {
|
130 | var self = this;
|
131 | this._pullStream.pull(42, function (err, data) {
|
132 | if (err) {
|
133 | return self.emit('error', err);
|
134 | }
|
135 |
|
136 | var vars = binary.parse(data)
|
137 | .word16lu('versionMadeBy')
|
138 | .word16lu('versionsNeededToExtract')
|
139 | .word16lu('flags')
|
140 | .word16lu('compressionMethod')
|
141 | .word16lu('lastModifiedTime')
|
142 | .word16lu('lastModifiedDate')
|
143 | .word32lu('crc32')
|
144 | .word32lu('compressedSize')
|
145 | .word32lu('uncompressedSize')
|
146 | .word16lu('fileNameLength')
|
147 | .word16lu('extraFieldLength')
|
148 | .word16lu('fileCommentLength')
|
149 | .word16lu('diskNumber')
|
150 | .word16lu('internalFileAttributes')
|
151 | .word32lu('externalFileAttributes')
|
152 | .word32lu('offsetToLocalFileHeader')
|
153 | .vars;
|
154 |
|
155 | return self._pullStream.pull(vars.fileNameLength, function (err, fileName) {
|
156 | if (err) {
|
157 | return self.emit('error', err);
|
158 | }
|
159 | fileName = fileName.toString('utf8');
|
160 |
|
161 | self._pullStream.pull(vars.extraFieldLength, function (err, extraField) {
|
162 | if (err) {
|
163 | return self.emit('error', err);
|
164 | }
|
165 | self._pullStream.pull(vars.fileCommentLength, function (err, fileComment) {
|
166 | if (err) {
|
167 | return self.emit('error', err);
|
168 | }
|
169 | return self._readRecord();
|
170 | });
|
171 | });
|
172 | });
|
173 | });
|
174 | };
|
175 |
|
176 | Parse.prototype._readEndOfCentralDirectoryRecord = function () {
|
177 | var self = this;
|
178 | this._pullStream.pull(18, function (err, data) {
|
179 | if (err) {
|
180 | return self.emit('error', err);
|
181 | }
|
182 |
|
183 | var vars = binary.parse(data)
|
184 | .word16lu('diskNumber')
|
185 | .word16lu('diskStart')
|
186 | .word16lu('numberOfRecordsOnDisk')
|
187 | .word16lu('numberOfRecords')
|
188 | .word32lu('sizeOfCentralDirectory')
|
189 | .word32lu('offsetToStartOfCentralDirectory')
|
190 | .word16lu('commentLength')
|
191 | .vars;
|
192 |
|
193 | return self._pullStream.pull(vars.commentLength, function (err, comment) {
|
194 | if (err) {
|
195 | return self.emit('error', err);
|
196 | }
|
197 | comment = comment.toString('utf8');
|
198 | self.emit('end');
|
199 | return self.emit('close');
|
200 | });
|
201 | });
|
202 | };
|
203 |
|
204 | Parse.prototype.write = function (data) {
|
205 | this._pullStream.write(data);
|
206 | };
|
207 |
|
208 | Parse.prototype.end = function (data) {
|
209 | this._pullStream.end(data);
|
210 | };
|
211 |
|
212 | Parse.prototype.pipe = function (dest, opts) {
|
213 | var self = this;
|
214 | if (typeof dest.add === "function") {
|
215 | self.on("entry", function (entry) {
|
216 | dest.add(entry);
|
217 | })
|
218 | }
|
219 | return Stream.prototype.pipe.apply(this, arguments);
|
220 | };
|