parse.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. 'use strict';
  2. module.exports = Parse.create = Parse;
  3. require("setimmediate");
  4. var Transform = require('readable-stream/transform');
  5. var inherits = require('util').inherits;
  6. var zlib = require('zlib');
  7. var binary = require('binary');
  8. var PullStream = require('pullstream');
  9. var MatchStream = require('match-stream');
  10. var Entry = require('./entry');
  11. inherits(Parse, Transform);
  12. function Parse(opts) {
  13. var self = this;
  14. if (!(this instanceof Parse)) {
  15. return new Parse(opts);
  16. }
  17. Transform.call(this, { lowWaterMark: 0 });
  18. this._opts = opts || { verbose: false };
  19. this._hasEntryListener = false;
  20. this._pullStream = new PullStream();
  21. this._pullStream.on("error", function (e) {
  22. self.emit('error', e);
  23. });
  24. this._pullStream.once("end", function () {
  25. self._streamEnd = true;
  26. });
  27. this._pullStream.once("finish", function () {
  28. self._streamFinish = true;
  29. });
  30. this._readRecord();
  31. }
  32. Parse.prototype._readRecord = function () {
  33. var self = this;
  34. this._pullStream.pull(4, function (err, data) {
  35. if (err) {
  36. return self.emit('error', err);
  37. }
  38. if (data.length === 0) {
  39. return;
  40. }
  41. var signature = data.readUInt32LE(0);
  42. if (signature === 0x04034b50) {
  43. self._readFile();
  44. } else if (signature === 0x02014b50) {
  45. self._readCentralDirectoryFileHeader();
  46. } else if (signature === 0x06054b50) {
  47. self._readEndOfCentralDirectoryRecord();
  48. } else {
  49. err = new Error('invalid signature: 0x' + signature.toString(16));
  50. self.emit('error', err);
  51. }
  52. });
  53. };
  54. Parse.prototype._readFile = function () {
  55. var self = this;
  56. this._pullStream.pull(26, function (err, data) {
  57. if (err) {
  58. return self.emit('error', err);
  59. }
  60. var vars = binary.parse(data)
  61. .word16lu('versionsNeededToExtract')
  62. .word16lu('flags')
  63. .word16lu('compressionMethod')
  64. .word16lu('lastModifiedTime')
  65. .word16lu('lastModifiedDate')
  66. .word32lu('crc32')
  67. .word32lu('compressedSize')
  68. .word32lu('uncompressedSize')
  69. .word16lu('fileNameLength')
  70. .word16lu('extraFieldLength')
  71. .vars;
  72. return self._pullStream.pull(vars.fileNameLength, function (err, fileName) {
  73. if (err) {
  74. return self.emit('error', err);
  75. }
  76. fileName = fileName.toString('utf8');
  77. var entry = new Entry();
  78. entry.path = fileName;
  79. entry.props.path = fileName;
  80. entry.type = (vars.compressedSize === 0 && /[\/\\]$/.test(fileName)) ? 'Directory' : 'File';
  81. if (self._opts.verbose) {
  82. if (entry.type === 'Directory') {
  83. console.log(' creating:', fileName);
  84. } else if (entry.type === 'File') {
  85. if (vars.compressionMethod === 0) {
  86. console.log(' extracting:', fileName);
  87. } else {
  88. console.log(' inflating:', fileName);
  89. }
  90. }
  91. }
  92. var hasEntryListener = self._hasEntryListener;
  93. if (hasEntryListener) {
  94. self.emit('entry', entry);
  95. }
  96. self._pullStream.pull(vars.extraFieldLength, function (err, extraField) {
  97. if (err) {
  98. return self.emit('error', err);
  99. }
  100. if (vars.compressionMethod === 0) {
  101. self._pullStream.pull(vars.compressedSize, function (err, compressedData) {
  102. if (err) {
  103. return self.emit('error', err);
  104. }
  105. if (hasEntryListener) {
  106. entry.write(compressedData);
  107. entry.end();
  108. }
  109. return self._readRecord();
  110. });
  111. } else {
  112. var fileSizeKnown = !(vars.flags & 0x08);
  113. var inflater = zlib.createInflateRaw();
  114. inflater.on('error', function (err) {
  115. self.emit('error', err);
  116. });
  117. if (fileSizeKnown) {
  118. entry.size = vars.uncompressedSize;
  119. if (hasEntryListener) {
  120. entry.on('finish', self._readRecord.bind(self));
  121. self._pullStream.pipe(vars.compressedSize, inflater).pipe(entry);
  122. } else {
  123. self._pullStream.drain(vars.compressedSize, function (err) {
  124. if (err) {
  125. return self.emit('error', err);
  126. }
  127. self._readRecord();
  128. });
  129. }
  130. } else {
  131. var descriptorSig = new Buffer(4);
  132. descriptorSig.writeUInt32LE(0x08074b50, 0);
  133. var matchStream = new MatchStream({ pattern: descriptorSig }, function (buf, matched, extra) {
  134. if (hasEntryListener) {
  135. if (!matched) {
  136. return this.push(buf);
  137. }
  138. this.push(buf);
  139. }
  140. setImmediate(function() {
  141. self._pullStream.unpipe();
  142. self._pullStream.prepend(extra);
  143. self._processDataDescriptor(entry);
  144. });
  145. return this.push(null);
  146. });
  147. self._pullStream.pipe(matchStream);
  148. if (hasEntryListener) {
  149. matchStream.pipe(inflater).pipe(entry);
  150. }
  151. }
  152. }
  153. });
  154. });
  155. });
  156. };
  157. Parse.prototype._processDataDescriptor = function (entry) {
  158. var self = this;
  159. this._pullStream.pull(16, function (err, data) {
  160. if (err) {
  161. return self.emit('error', err);
  162. }
  163. var vars = binary.parse(data)
  164. .word32lu('dataDescriptorSignature')
  165. .word32lu('crc32')
  166. .word32lu('compressedSize')
  167. .word32lu('uncompressedSize')
  168. .vars;
  169. entry.size = vars.uncompressedSize;
  170. self._readRecord();
  171. });
  172. };
  173. Parse.prototype._readCentralDirectoryFileHeader = function () {
  174. var self = this;
  175. this._pullStream.pull(42, function (err, data) {
  176. if (err) {
  177. return self.emit('error', err);
  178. }
  179. var vars = binary.parse(data)
  180. .word16lu('versionMadeBy')
  181. .word16lu('versionsNeededToExtract')
  182. .word16lu('flags')
  183. .word16lu('compressionMethod')
  184. .word16lu('lastModifiedTime')
  185. .word16lu('lastModifiedDate')
  186. .word32lu('crc32')
  187. .word32lu('compressedSize')
  188. .word32lu('uncompressedSize')
  189. .word16lu('fileNameLength')
  190. .word16lu('extraFieldLength')
  191. .word16lu('fileCommentLength')
  192. .word16lu('diskNumber')
  193. .word16lu('internalFileAttributes')
  194. .word32lu('externalFileAttributes')
  195. .word32lu('offsetToLocalFileHeader')
  196. .vars;
  197. return self._pullStream.pull(vars.fileNameLength, function (err, fileName) {
  198. if (err) {
  199. return self.emit('error', err);
  200. }
  201. fileName = fileName.toString('utf8');
  202. self._pullStream.pull(vars.extraFieldLength, function (err, extraField) {
  203. if (err) {
  204. return self.emit('error', err);
  205. }
  206. self._pullStream.pull(vars.fileCommentLength, function (err, fileComment) {
  207. if (err) {
  208. return self.emit('error', err);
  209. }
  210. return self._readRecord();
  211. });
  212. });
  213. });
  214. });
  215. };
  216. Parse.prototype._readEndOfCentralDirectoryRecord = function () {
  217. var self = this;
  218. this._pullStream.pull(18, function (err, data) {
  219. if (err) {
  220. return self.emit('error', err);
  221. }
  222. var vars = binary.parse(data)
  223. .word16lu('diskNumber')
  224. .word16lu('diskStart')
  225. .word16lu('numberOfRecordsOnDisk')
  226. .word16lu('numberOfRecords')
  227. .word32lu('sizeOfCentralDirectory')
  228. .word32lu('offsetToStartOfCentralDirectory')
  229. .word16lu('commentLength')
  230. .vars;
  231. if (vars.commentLength) {
  232. setImmediate(function() {
  233. self._pullStream.pull(vars.commentLength, function (err, comment) {
  234. if (err) {
  235. return self.emit('error', err);
  236. }
  237. comment = comment.toString('utf8');
  238. return self._pullStream.end();
  239. });
  240. });
  241. } else {
  242. self._pullStream.end();
  243. }
  244. });
  245. };
  246. Parse.prototype._transform = function (chunk, encoding, callback) {
  247. if (this._pullStream.write(chunk)) {
  248. return callback();
  249. }
  250. this._pullStream.once('drain', callback);
  251. };
  252. Parse.prototype.pipe = function (dest, opts) {
  253. var self = this;
  254. if (typeof dest.add === "function") {
  255. self.on("entry", function (entry) {
  256. dest.add(entry);
  257. })
  258. }
  259. return Transform.prototype.pipe.apply(this, arguments);
  260. };
  261. Parse.prototype._flush = function (callback) {
  262. if (!this._streamEnd || !this._streamFinish) {
  263. return setImmediate(this._flush.bind(this, callback));
  264. }
  265. this.emit('close');
  266. return callback();
  267. };
  268. Parse.prototype.addListener = function(type, listener) {
  269. if ('entry' === type) {
  270. this._hasEntryListener = true;
  271. }
  272. return Transform.prototype.addListener.call(this, type, listener);
  273. };
  274. Parse.prototype.on = Parse.prototype.addListener;