From a9b21b6ee03b9a04e8e77eed844733502934881b Mon Sep 17 00:00:00 2001 From: Mathias Buus Date: Thu, 19 Dec 2013 23:08:43 +0100 Subject: [PATCH] inital commit --- extract.js | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ headers.js | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 extract.js create mode 100644 headers.js diff --git a/extract.js b/extract.js new file mode 100644 index 0000000..52604b6 --- /dev/null +++ b/extract.js @@ -0,0 +1,132 @@ +var stream = require('stream'); +var util = require('util'); +var bl = require('bl'); +var headers = require('./headers'); + +var noop = function() {}; + +var overflow = function(size) { + size &= 511; + return size && 512 - size; +}; + +var emptyStream = function() { + var s = new stream.PassThrough(); + s.end(); + return s; +}; + +var Extract = function(opts) { + if (!(this instanceof Extract)) return new Extract(opts); + stream.Writable.call(this, opts); + + this._buffer = bl(); + this._missing = 0; + this._onparse = noop; + this._header = null; + this._stream = null; + this._overflow = null; + this._cb = null; + this._locked = false; + + var self = this; + var b = self._buffer; + + var oncontinue = function() { + self._continue(); + }; + + var onunlock = function(err) { + self._locked = false; + if (err) return self.emit('error', err); + if (!self._stream) oncontinue(); + }; + + var onstreamend = function() { + self._stream = null; + var drain = overflow(self._header.size); + if (drain) self._parse(drain, ondrain); + else self._parse(512, onheader); + if (!self._locked) oncontinue(); + }; + + var ondrain = function() { + self._buffer.consume(overflow(self._header.size)); + self._parse(512, onheader); + oncontinue(); + }; + + var onheader = function() { + var header = self._header = headers.decode(b.slice(0, 512)); + b.consume(512); + + if (!header) { + self._parse(512, onheader); + oncontinue(); + return; + } + if (!header.size) { + self._parse(512, onheader); + self.emit('entry', header, emptyStream(), oncontinue); + return; + } + + self._locked = true; + self._stream = new stream.PassThrough(); + + self.emit('entry', header, self._stream, onunlock); + self._parse(header.size, onstreamend); + oncontinue(); + }; + + this._parse(512, onheader); +}; + +util.inherits(Extract, stream.Writable); + +Extract.prototype._parse = function(size, onparse) { + this._missing = size; + this._onparse = onparse; +}; + +Extract.prototype._continue = function(err) { + var cb = this._cb; + this._cb = noop; + if (this._overflow) this._write(this._overflow, undefined, cb); + else cb(); +}; + +Extract.prototype._write = function(data, enc, cb) { + var s = this._stream; + var b = this._buffer; + var missing = this._missing; + + // we do not reach end-of-chunk now. just forward it + + if (data.length < missing) { + this._missing -= data.length; + this._overflow = null; + if (s) return s.write(data, cb); + b.append(data); + return cb(); + } + + // end-of-chunk. the parser should call cb. + + this._cb = cb; + this._missing = 0; + + var overflow = null; + if (data.length > missing) { + overflow = data.slice(missing); + data = data.slice(0, missing); + } + + if (s) s.end(data); + else b.append(data); + + this._overflow = overflow; + this._onparse(); +}; + +module.exports = Extract; \ No newline at end of file diff --git a/headers.js b/headers.js new file mode 100644 index 0000000..5a3b50f --- /dev/null +++ b/headers.js @@ -0,0 +1,136 @@ +var ZEROS = '0000000000000000000'; +var ZERO_OFFSET = '0'.charCodeAt(0); +var USTAR = 'ustar00'; + +var toType = function(flag) { + switch (flag) { + case 1: + return 'link'; + case 3: + return 'character'; + case 4: + return 'block'; + case 5: + return 'directory'; + case 6: + return 'fifo'; + } + return 'file'; +}; + +var toTypeflag = function(flag) { + switch (flag) { + case 'link': + return 1; + case 'character': + return 3; + case 'block': + return 4; + case 'directory': + return 5; + case 'fifo': + return 6; + } + + return 0; +}; + +var alloc = function(size) { + var buf = new Buffer(size); + buf.fill(0); + return buf; +}; + +var indexOf = function(block, num, offset) { + for (; offset < block.length; offset++) { + if (block[offset] === num) return offset; + } + return -1; +}; + +var cksum = function(block) { + var sum = 8 * 32; + for (var i = 0; i < 148; i++) sum += block[i]; + for (var i = 156; i < 512; i++) sum += block[i]; + return sum; +}; + +var oct = function(val, n) { + val = val.toString(8); + return ZEROS.slice(0, n-val.length)+val+' '; +}; + +var decodeOct = function(val, offset) { + return parseInt(val.slice(offset, indexOf(val, 32, offset)).toString(), 8); +}; + +var decodeStr = function(val, offset) { + return val.slice(offset, indexOf(val, 0, offset)).toString(); +}; + +exports.encode = function(opts) { + var buf = alloc(512); + var name = opts.path; + var prefix = ''; + + if (opts.typeflag === 5 && name[name.length-1] !== '/') name += '/'; + + while (Buffer.byteLength(name) > 100) { + var i = name.indexOf('/'); + prefix += prefix ? '/' + name.slice(0, i) : name.slice(0, i); + name = name.slice(i+1); + } + + buf.write(name); + buf.write(oct(opts.mode, 6), 100); + buf.write(oct(opts.uid, 6), 108); + buf.write(oct(opts.gid, 6), 116); + buf.write(oct(opts.size, 11), 124); + buf.write(oct((opts.mtime.getTime() / 1000) | 0, 11), 136); + + buf[156] = ZERO_OFFSET + toTypeflag(opts.type); + + if (opts.linkname) buf.write(opts.linkname, 157); + + buf.write(USTAR, 257); + if (opts.uname) buf.write(opts.uname, 265); + if (opts.gname) buf.write(opts.gname, 297); + buf.write(oct(0, 6), 329); + buf.write(oct(0, 6), 337); + + if (prefix) buf.write(prefix, 345); + + buf.write(oct(cksum(buf), 6), 148); + + return buf; +}; + +exports.decode = function(buf) { + var name = decodeStr(buf, 0); + var mode = decodeOct(buf, 100); + var uid = decodeOct(buf, 108); + var gid = decodeOct(buf, 116); + var size = decodeOct(buf, 124); + var mtime = decodeOct(buf, 136); + var typeflag = buf[156] - ZERO_OFFSET; + var linkname = buf[157] === 0 ? null : decodeStr(buf, 157); + var uname = decodeStr(buf, 265); + var gname = decodeStr(buf, 297); + + if (buf[345]) name = decodeStr(buf, 345)+'/'+name; + + if (cksum(buf) !== decodeOct(buf, 148)) return null; + + return { + path: name, + mode: mode, + uid: uid, + gid: gid, + size: size, + mtime: new Date(1000 * mtime), + type: toType(typeflag), + linkname: linkname, + uname: uname, + gname: gname + }; +}; \ No newline at end of file