[Pkg-javascript-commits] [node-tar] 01/03: Imported Upstream version 1.0.3
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Sat Mar 14 00:34:45 UTC 2015
This is an automated email from the git hooks/post-receive script.
sebastic pushed a commit to branch master
in repository node-tar.
commit 6f0d67714589d6123712045dbe0d8cc538ff189a
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Fri Mar 13 23:03:05 2015 +0100
Imported Upstream version 1.0.3
---
.gitignore | 5 +
.travis.yml | 4 +
LICENCE | 25 ++
README.md | 48 +++
examples/extracter.js | 19 +
examples/packer.js | 24 ++
examples/reader.js | 36 ++
lib/buffer-entry.js | 30 ++
lib/entry-writer.js | 169 ++++++++
lib/entry.js | 213 ++++++++++
lib/extended-header-writer.js | 191 +++++++++
lib/extended-header.js | 140 +++++++
lib/extract.js | 86 ++++
lib/global-header-writer.js | 14 +
lib/header.js | 385 +++++++++++++++++
lib/pack.js | 231 +++++++++++
lib/parse.js | 271 ++++++++++++
package.json | 26 ++
tar.js | 173 ++++++++
test/00-setup-fixtures.js | 53 +++
test/extract-move.js | 132 ++++++
test/extract.js | 367 +++++++++++++++++
test/fixtures.tgz | Bin 0 -> 19352 bytes
test/header.js | 183 +++++++++
test/pack-no-proprietary.js | 886 +++++++++++++++++++++++++++++++++++++++
test/pack.js | 934 ++++++++++++++++++++++++++++++++++++++++++
test/parse.js | 359 ++++++++++++++++
test/zz-cleanup.js | 20 +
28 files changed, 5024 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c167ad5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.*.swp
+node_modules
+examples/extract/
+test/tmp/
+test/fixtures/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fca8ef0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+ - 0.10
+ - 0.11
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..74489e2
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,25 @@
+Copyright (c) Isaac Z. Schlueter
+All rights reserved.
+
+The BSD License
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..424a278
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+# node-tar
+
+Tar for Node.js.
+
+[![NPM](https://nodei.co/npm/tar.png)](https://nodei.co/npm/tar/)
+
+## API
+
+See `examples/` for usage examples.
+
+### var tar = require('tar')
+
+Returns an object with `.Pack`, `.Extract` and `.Parse` methods.
+
+### tar.Pack([properties])
+
+Returns a through stream. Use
+[fstream](https://npmjs.org/package/fstream) to write files into the
+pack stream and you will receive tar archive data from the pack
+stream.
+
+This only works with directories, it does not work with individual files.
+
+The optional `properties` object are used to set properties in the tar
+'Global Extended Header'.
+
+### tar.Extract([options])
+
+Returns a through stream. Write tar data to the stream and the files
+in the tarball will be extracted onto the filesystem.
+
+`options` can be:
+
+```js
+{
+ path: '/path/to/extract/tar/into',
+ strip: 0, // how many path segments to strip from the root when extracting
+}
+```
+
+`options` also get passed to the `fstream.Writer` instance that `tar`
+uses internally.
+
+### tar.Parse()
+
+Returns a writable stream. Write tar data to it and it will emit
+`entry` events for each entry parsed from the tarball. This is used by
+`tar.Extract`.
diff --git a/examples/extracter.js b/examples/extracter.js
new file mode 100644
index 0000000..f6253a7
--- /dev/null
+++ b/examples/extracter.js
@@ -0,0 +1,19 @@
+var tar = require("../tar.js")
+ , fs = require("fs")
+
+
+function onError(err) {
+ console.error('An error occurred:', err)
+}
+
+function onEnd() {
+ console.log('Extracted!')
+}
+
+var extractor = tar.Extract({path: __dirname + "/extract"})
+ .on('error', onError)
+ .on('end', onEnd);
+
+fs.createReadStream(__dirname + "/../test/fixtures/c.tar")
+ .on('error', onError)
+ .pipe(extractor);
diff --git a/examples/packer.js b/examples/packer.js
new file mode 100644
index 0000000..039969c
--- /dev/null
+++ b/examples/packer.js
@@ -0,0 +1,24 @@
+var tar = require("../tar.js")
+ , fstream = require("fstream")
+ , fs = require("fs")
+
+var dirDest = fs.createWriteStream('dir.tar')
+
+
+function onError(err) {
+ console.error('An error occurred:', err)
+}
+
+function onEnd() {
+ console.log('Packed!')
+}
+
+var packer = tar.Pack({ noProprietary: true })
+ .on('error', onError)
+ .on('end', onEnd);
+
+// This must be a "directory"
+fstream.Reader({ path: __dirname, type: "Directory" })
+ .on('error', onError)
+ .pipe(packer)
+ .pipe(dirDest)
diff --git a/examples/reader.js b/examples/reader.js
new file mode 100644
index 0000000..39f3f08
--- /dev/null
+++ b/examples/reader.js
@@ -0,0 +1,36 @@
+var tar = require("../tar.js")
+ , fs = require("fs")
+
+fs.createReadStream(__dirname + "/../test/fixtures/c.tar")
+ .pipe(tar.Parse())
+ .on("extendedHeader", function (e) {
+ console.error("extended pax header", e.props)
+ e.on("end", function () {
+ console.error("extended pax fields:", e.fields)
+ })
+ })
+ .on("ignoredEntry", function (e) {
+ console.error("ignoredEntry?!?", e.props)
+ })
+ .on("longLinkpath", function (e) {
+ console.error("longLinkpath entry", e.props)
+ e.on("end", function () {
+ console.error("value=%j", e.body.toString())
+ })
+ })
+ .on("longPath", function (e) {
+ console.error("longPath entry", e.props)
+ e.on("end", function () {
+ console.error("value=%j", e.body.toString())
+ })
+ })
+ .on("entry", function (e) {
+ console.error("entry", e.props)
+ e.on("data", function (c) {
+ console.error(" >>>" + c.toString().replace(/\n/g, "\\n"))
+ })
+ e.on("end", function () {
+ console.error(" <<<EOF")
+ })
+ })
+
diff --git a/lib/buffer-entry.js b/lib/buffer-entry.js
new file mode 100644
index 0000000..6c1da23
--- /dev/null
+++ b/lib/buffer-entry.js
@@ -0,0 +1,30 @@
+// just like the Entry class, but it buffers the contents
+//
+// XXX It would be good to set a maximum BufferEntry filesize,
+// since it eats up memory. In normal operation,
+// these are only for long filenames or link names, which are
+// rarely very big.
+
+module.exports = BufferEntry
+
+var inherits = require("inherits")
+ , Entry = require("./entry.js")
+
+function BufferEntry () {
+ Entry.apply(this, arguments)
+ this._buffer = new Buffer(this.props.size)
+ this._offset = 0
+ this.body = ""
+ this.on("end", function () {
+ this.body = this._buffer.toString().slice(0, -1)
+ })
+}
+
+inherits(BufferEntry, Entry)
+
+// collect the bytes as they come in.
+BufferEntry.prototype.write = function (c) {
+ c.copy(this._buffer, this._offset)
+ this._offset += c.length
+ Entry.prototype.write.call(this, c)
+}
diff --git a/lib/entry-writer.js b/lib/entry-writer.js
new file mode 100644
index 0000000..8e09042
--- /dev/null
+++ b/lib/entry-writer.js
@@ -0,0 +1,169 @@
+module.exports = EntryWriter
+
+var tar = require("../tar.js")
+ , TarHeader = require("./header.js")
+ , Entry = require("./entry.js")
+ , inherits = require("inherits")
+ , BlockStream = require("block-stream")
+ , ExtendedHeaderWriter
+ , Stream = require("stream").Stream
+ , EOF = {}
+
+inherits(EntryWriter, Stream)
+
+function EntryWriter (props) {
+ var me = this
+
+ if (!(me instanceof EntryWriter)) {
+ return new EntryWriter(props)
+ }
+
+ Stream.apply(this)
+
+ me.writable = true
+ me.readable = true
+
+ me._stream = new BlockStream(512)
+
+ me._stream.on("data", function (c) {
+ me.emit("data", c)
+ })
+
+ me._stream.on("drain", function () {
+ me.emit("drain")
+ })
+
+ me._stream.on("end", function () {
+ me.emit("end")
+ me.emit("close")
+ })
+
+ me.props = props
+ if (props.type === "Directory") {
+ props.size = 0
+ }
+ props.ustar = "ustar\0"
+ props.ustarver = "00"
+ me.path = props.path
+
+ me._buffer = []
+ me._didHeader = false
+ me._meta = false
+
+ me.on("pipe", function () {
+ me._process()
+ })
+}
+
+EntryWriter.prototype.write = function (c) {
+ // console.error(".. ew write")
+ if (this._ended) return this.emit("error", new Error("write after end"))
+ this._buffer.push(c)
+ this._process()
+ this._needDrain = this._buffer.length > 0
+ return !this._needDrain
+}
+
+EntryWriter.prototype.end = function (c) {
+ // console.error(".. ew end")
+ if (c) this._buffer.push(c)
+ this._buffer.push(EOF)
+ this._ended = true
+ this._process()
+ this._needDrain = this._buffer.length > 0
+}
+
+EntryWriter.prototype.pause = function () {
+ // console.error(".. ew pause")
+ this._paused = true
+ this.emit("pause")
+}
+
+EntryWriter.prototype.resume = function () {
+ // console.error(".. ew resume")
+ this._paused = false
+ this.emit("resume")
+ this._process()
+}
+
+EntryWriter.prototype.add = function (entry) {
+ // console.error(".. ew add")
+ if (!this.parent) return this.emit("error", new Error("no parent"))
+
+ // make sure that the _header and such is emitted, and clear out
+ // the _currentEntry link on the parent.
+ if (!this._ended) this.end()
+
+ return this.parent.add(entry)
+}
+
+EntryWriter.prototype._header = function () {
+ // console.error(".. ew header")
+ if (this._didHeader) return
+ this._didHeader = true
+
+ var headerBlock = TarHeader.encode(this.props)
+
+ if (this.props.needExtended && !this._meta) {
+ var me = this
+
+ ExtendedHeaderWriter = ExtendedHeaderWriter ||
+ require("./extended-header-writer.js")
+
+ ExtendedHeaderWriter(this.props)
+ .on("data", function (c) {
+ me.emit("data", c)
+ })
+ .on("error", function (er) {
+ me.emit("error", er)
+ })
+ .end()
+ }
+
+ // console.error(".. .. ew headerBlock emitting")
+ this.emit("data", headerBlock)
+ this.emit("header")
+}
+
+EntryWriter.prototype._process = function () {
+ // console.error(".. .. ew process")
+ if (!this._didHeader && !this._meta) {
+ this._header()
+ }
+
+ if (this._paused || this._processing) {
+ // console.error(".. .. .. paused=%j, processing=%j", this._paused, this._processing)
+ return
+ }
+
+ this._processing = true
+
+ var buf = this._buffer
+ for (var i = 0; i < buf.length; i ++) {
+ // console.error(".. .. .. i=%d", i)
+
+ var c = buf[i]
+
+ if (c === EOF) this._stream.end()
+ else this._stream.write(c)
+
+ if (this._paused) {
+ // console.error(".. .. .. paused mid-emission")
+ this._processing = false
+ if (i < buf.length) {
+ this._needDrain = true
+ this._buffer = buf.slice(i + 1)
+ }
+ return
+ }
+ }
+
+ // console.error(".. .. .. emitted")
+ this._buffer.length = 0
+ this._processing = false
+
+ // console.error(".. .. .. emitting drain")
+ this.emit("drain")
+}
+
+EntryWriter.prototype.destroy = function () {}
diff --git a/lib/entry.js b/lib/entry.js
new file mode 100644
index 0000000..4af5c41
--- /dev/null
+++ b/lib/entry.js
@@ -0,0 +1,213 @@
+// 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._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
+}
+
+Entry.prototype.warn = fstream.warn
+Entry.prototype.error = fstream.error
diff --git a/lib/extended-header-writer.js b/lib/extended-header-writer.js
new file mode 100644
index 0000000..1728c45
--- /dev/null
+++ b/lib/extended-header-writer.js
@@ -0,0 +1,191 @@
+
+module.exports = ExtendedHeaderWriter
+
+var inherits = require("inherits")
+ , EntryWriter = require("./entry-writer.js")
+
+inherits(ExtendedHeaderWriter, EntryWriter)
+
+var tar = require("../tar.js")
+ , path = require("path")
+ , TarHeader = require("./header.js")
+
+// props is the props of the thing we need to write an
+// extended header for.
+// Don't be shy with it. Just encode everything.
+function ExtendedHeaderWriter (props) {
+ // console.error(">> ehw ctor")
+ var me = this
+
+ if (!(me instanceof ExtendedHeaderWriter)) {
+ return new ExtendedHeaderWriter(props)
+ }
+
+ me.fields = props
+
+ var p =
+ { path : ("PaxHeader" + path.join("/", props.path || ""))
+ .replace(/\\/g, "/").substr(0, 100)
+ , mode : props.mode || 0666
+ , uid : props.uid || 0
+ , gid : props.gid || 0
+ , size : 0 // will be set later
+ , mtime : props.mtime || Date.now() / 1000
+ , type : "x"
+ , linkpath : ""
+ , ustar : "ustar\0"
+ , ustarver : "00"
+ , uname : props.uname || ""
+ , gname : props.gname || ""
+ , devmaj : props.devmaj || 0
+ , devmin : props.devmin || 0
+ }
+
+
+ EntryWriter.call(me, p)
+ // console.error(">> ehw props", me.props)
+ me.props = p
+
+ me._meta = true
+}
+
+ExtendedHeaderWriter.prototype.end = function () {
+ // console.error(">> ehw end")
+ var me = this
+
+ if (me._ended) return
+ me._ended = true
+
+ me._encodeFields()
+
+ if (me.props.size === 0) {
+ // nothing to write!
+ me._ready = true
+ me._stream.end()
+ return
+ }
+
+ me._stream.write(TarHeader.encode(me.props))
+ me.body.forEach(function (l) {
+ me._stream.write(l)
+ })
+ me._ready = true
+
+ // console.error(">> ehw _process calling end()", me.props)
+ this._stream.end()
+}
+
+ExtendedHeaderWriter.prototype._encodeFields = function () {
+ // console.error(">> ehw _encodeFields")
+ this.body = []
+ if (this.fields.prefix) {
+ this.fields.path = this.fields.prefix + "/" + this.fields.path
+ this.fields.prefix = ""
+ }
+ encodeFields(this.fields, "", this.body, this.fields.noProprietary)
+ var me = this
+ this.body.forEach(function (l) {
+ me.props.size += l.length
+ })
+}
+
+function encodeFields (fields, prefix, body, nop) {
+ // console.error(">> >> ehw encodeFields")
+ // "%d %s=%s\n", <length>, <keyword>, <value>
+ // The length is a decimal number, and includes itself and the \n
+ // Numeric values are decimal strings.
+
+ Object.keys(fields).forEach(function (k) {
+ var val = fields[k]
+ , numeric = tar.numeric[k]
+
+ if (prefix) k = prefix + "." + k
+
+ // already including NODETAR.type, don't need File=true also
+ if (k === fields.type && val === true) return
+
+ switch (k) {
+ // don't include anything that's always handled just fine
+ // in the normal header, or only meaningful in the context
+ // of nodetar
+ case "mode":
+ case "cksum":
+ case "ustar":
+ case "ustarver":
+ case "prefix":
+ case "basename":
+ case "dirname":
+ case "needExtended":
+ case "block":
+ case "filter":
+ return
+
+ case "rdev":
+ if (val === 0) return
+ break
+
+ case "nlink":
+ case "dev": // Truly a hero among men, Creator of Star!
+ case "ino": // Speak his name with reverent awe! It is:
+ k = "SCHILY." + k
+ break
+
+ default: break
+ }
+
+ if (val && typeof val === "object" &&
+ !Buffer.isBuffer(val)) encodeFields(val, k, body, nop)
+ else if (val === null || val === undefined) return
+ else body.push.apply(body, encodeField(k, val, nop))
+ })
+
+ return body
+}
+
+function encodeField (k, v, nop) {
+ // lowercase keys must be valid, otherwise prefix with
+ // "NODETAR."
+ if (k.charAt(0) === k.charAt(0).toLowerCase()) {
+ var m = k.split(".")[0]
+ if (!tar.knownExtended[m]) k = "NODETAR." + k
+ }
+
+ // no proprietary
+ if (nop && k.charAt(0) !== k.charAt(0).toLowerCase()) {
+ return []
+ }
+
+ if (typeof val === "number") val = val.toString(10)
+
+ var s = new Buffer(" " + k + "=" + v + "\n")
+ , digits = Math.floor(Math.log(s.length) / Math.log(10)) + 1
+
+ // console.error("1 s=%j digits=%j s.length=%d", s.toString(), digits, s.length)
+
+ // if adding that many digits will make it go over that length,
+ // then add one to it. For example, if the string is:
+ // " foo=bar\n"
+ // then that's 9 characters. With the "9", that bumps the length
+ // up to 10. However, this is invalid:
+ // "10 foo=bar\n"
+ // but, since that's actually 11 characters, since 10 adds another
+ // character to the length, and the length includes the number
+ // itself. In that case, just bump it up again.
+ if (s.length + digits >= Math.pow(10, digits)) digits += 1
+ // console.error("2 s=%j digits=%j s.length=%d", s.toString(), digits, s.length)
+
+ var len = digits + s.length
+ // console.error("3 s=%j digits=%j s.length=%d len=%d", s.toString(), digits, s.length, len)
+ var lenBuf = new Buffer("" + len)
+ if (lenBuf.length + s.length !== len) {
+ throw new Error("Bad length calculation\n"+
+ "len="+len+"\n"+
+ "lenBuf="+JSON.stringify(lenBuf.toString())+"\n"+
+ "lenBuf.length="+lenBuf.length+"\n"+
+ "digits="+digits+"\n"+
+ "s="+JSON.stringify(s.toString())+"\n"+
+ "s.length="+s.length)
+ }
+
+ return [lenBuf, s]
+}
diff --git a/lib/extended-header.js b/lib/extended-header.js
new file mode 100644
index 0000000..74f432c
--- /dev/null
+++ b/lib/extended-header.js
@@ -0,0 +1,140 @@
+// An Entry consisting of:
+//
+// "%d %s=%s\n", <length>, <keyword>, <value>
+//
+// The length is a decimal number, and includes itself and the \n
+// \0 does not terminate anything. Only the length terminates the string.
+// Numeric values are decimal strings.
+
+module.exports = ExtendedHeader
+
+var Entry = require("./entry.js")
+ , inherits = require("inherits")
+ , tar = require("../tar.js")
+ , numeric = tar.numeric
+ , keyTrans = { "SCHILY.dev": "dev"
+ , "SCHILY.ino": "ino"
+ , "SCHILY.nlink": "nlink" }
+
+function ExtendedHeader () {
+ Entry.apply(this, arguments)
+ this.on("data", this._parse)
+ this.fields = {}
+ this._position = 0
+ this._fieldPos = 0
+ this._state = SIZE
+ this._sizeBuf = []
+ this._keyBuf = []
+ this._valBuf = []
+ this._size = -1
+ this._key = ""
+}
+
+inherits(ExtendedHeader, Entry)
+ExtendedHeader.prototype._parse = parse
+
+var s = 0
+ , states = ExtendedHeader.states = {}
+ , SIZE = states.SIZE = s++
+ , KEY = states.KEY = s++
+ , VAL = states.VAL = s++
+ , ERR = states.ERR = s++
+
+Object.keys(states).forEach(function (s) {
+ states[states[s]] = states[s]
+})
+
+states[s] = null
+
+// char code values for comparison
+var _0 = "0".charCodeAt(0)
+ , _9 = "9".charCodeAt(0)
+ , point = ".".charCodeAt(0)
+ , a = "a".charCodeAt(0)
+ , Z = "Z".charCodeAt(0)
+ , a = "a".charCodeAt(0)
+ , z = "z".charCodeAt(0)
+ , space = " ".charCodeAt(0)
+ , eq = "=".charCodeAt(0)
+ , cr = "\n".charCodeAt(0)
+
+function parse (c) {
+ if (this._state === ERR) return
+
+ for ( var i = 0, l = c.length
+ ; i < l
+ ; this._position++, this._fieldPos++, i++) {
+ // console.error("top of loop, size="+this._size)
+
+ var b = c[i]
+
+ if (this._size >= 0 && this._fieldPos > this._size) {
+ error(this, "field exceeds length="+this._size)
+ return
+ }
+
+ switch (this._state) {
+ case ERR: return
+
+ case SIZE:
+ // console.error("parsing size, b=%d, rest=%j", b, c.slice(i).toString())
+ if (b === space) {
+ this._state = KEY
+ // this._fieldPos = this._sizeBuf.length
+ this._size = parseInt(new Buffer(this._sizeBuf).toString(), 10)
+ this._sizeBuf.length = 0
+ continue
+ }
+ if (b < _0 || b > _9) {
+ error(this, "expected [" + _0 + ".." + _9 + "], got " + b)
+ return
+ }
+ this._sizeBuf.push(b)
+ continue
+
+ case KEY:
+ // can be any char except =, not > size.
+ if (b === eq) {
+ this._state = VAL
+ this._key = new Buffer(this._keyBuf).toString()
+ if (keyTrans[this._key]) this._key = keyTrans[this._key]
+ this._keyBuf.length = 0
+ continue
+ }
+ this._keyBuf.push(b)
+ continue
+
+ case VAL:
+ // field must end with cr
+ if (this._fieldPos === this._size - 1) {
+ // console.error("finished with "+this._key)
+ if (b !== cr) {
+ error(this, "expected \\n at end of field")
+ return
+ }
+ var val = new Buffer(this._valBuf).toString()
+ if (numeric[this._key]) {
+ val = parseFloat(val)
+ }
+ this.fields[this._key] = val
+
+ this._valBuf.length = 0
+ this._state = SIZE
+ this._size = -1
+ this._fieldPos = -1
+ continue
+ }
+ this._valBuf.push(b)
+ continue
+ }
+ }
+}
+
+function error (me, msg) {
+ msg = "invalid header: " + msg
+ + "\nposition=" + me._position
+ + "\nfield position=" + me._fieldPos
+
+ me.error(msg)
+ me.state = ERR
+}
diff --git a/lib/extract.js b/lib/extract.js
new file mode 100644
index 0000000..9fb1e6f
--- /dev/null
+++ b/lib/extract.js
@@ -0,0 +1,86 @@
+// give it a tarball and a path, and it'll dump the contents
+
+module.exports = Extract
+
+var tar = require("../tar.js")
+ , fstream = require("fstream")
+ , inherits = require("inherits")
+ , path = require("path")
+
+function Extract (opts) {
+ if (!(this instanceof Extract)) return new Extract(opts)
+ tar.Parse.apply(this)
+
+ // have to dump into a directory
+ opts.type = "Directory"
+ opts.Directory = true
+
+ if (typeof opts !== "object") {
+ opts = { path: opts }
+ }
+
+ // better to drop in cwd? seems more standard.
+ opts.path = opts.path || path.resolve("node-tar-extract")
+ opts.type = "Directory"
+ opts.Directory = true
+
+ // similar to --strip or --strip-components
+ opts.strip = +opts.strip
+ if (!opts.strip || opts.strip <= 0) opts.strip = 0
+
+ this._fst = fstream.Writer(opts)
+
+ this.pause()
+ var me = this
+
+ // Hardlinks in tarballs are relative to the root
+ // of the tarball. So, they need to be resolved against
+ // the target directory in order to be created properly.
+ me.on("entry", function (entry) {
+ // if there's a "strip" argument, then strip off that many
+ // path components.
+ if (opts.strip) {
+ var p = entry.path.split("/").slice(opts.strip).join("/")
+ entry.path = entry.props.path = p
+ if (entry.linkpath) {
+ var lp = entry.linkpath.split("/").slice(opts.strip).join("/")
+ entry.linkpath = entry.props.linkpath = lp
+ }
+ }
+ if (entry.type !== "Link") return
+ entry.linkpath = entry.props.linkpath =
+ path.join(opts.path, path.join("/", entry.props.linkpath))
+ })
+
+ this._fst.on("ready", function () {
+ me.pipe(me._fst, { end: false })
+ me.resume()
+ })
+
+ this._fst.on('error', function(err) {
+ me.emit('error', err)
+ })
+
+ this._fst.on('drain', function() {
+ me.emit('drain')
+ })
+
+ // this._fst.on("end", function () {
+ // console.error("\nEEEE Extract End", me._fst.path)
+ // })
+
+ this._fst.on("close", function () {
+ // console.error("\nEEEE Extract End", me._fst.path)
+ me.emit("end")
+ me.emit("close")
+ })
+}
+
+inherits(Extract, tar.Parse)
+
+Extract.prototype._streamEnd = function () {
+ var me = this
+ if (!me._ended) me.error("unexpected eof")
+ me._fst.end()
+ // my .end() is coming later.
+}
diff --git a/lib/global-header-writer.js b/lib/global-header-writer.js
new file mode 100644
index 0000000..0bfc7b8
--- /dev/null
+++ b/lib/global-header-writer.js
@@ -0,0 +1,14 @@
+module.exports = GlobalHeaderWriter
+
+var ExtendedHeaderWriter = require("./extended-header-writer.js")
+ , inherits = require("inherits")
+
+inherits(GlobalHeaderWriter, ExtendedHeaderWriter)
+
+function GlobalHeaderWriter (props) {
+ if (!(this instanceof GlobalHeaderWriter)) {
+ return new GlobalHeaderWriter(props)
+ }
+ ExtendedHeaderWriter.call(this, props)
+ this.props.type = "g"
+}
diff --git a/lib/header.js b/lib/header.js
new file mode 100644
index 0000000..05b237c
--- /dev/null
+++ b/lib/header.js
@@ -0,0 +1,385 @@
+// parse a 512-byte header block to a data object, or vice-versa
+// If the data won't fit nicely in a simple header, then generate
+// the appropriate extended header file, and return that.
+
+module.exports = TarHeader
+
+var tar = require("../tar.js")
+ , fields = tar.fields
+ , fieldOffs = tar.fieldOffs
+ , fieldEnds = tar.fieldEnds
+ , fieldSize = tar.fieldSize
+ , numeric = tar.numeric
+ , assert = require("assert").ok
+ , space = " ".charCodeAt(0)
+ , slash = "/".charCodeAt(0)
+ , bslash = process.platform === "win32" ? "\\".charCodeAt(0) : null
+
+function TarHeader (block) {
+ if (!(this instanceof TarHeader)) return new TarHeader(block)
+ if (block) this.decode(block)
+}
+
+TarHeader.prototype =
+ { decode : decode
+ , encode: encode
+ , calcSum: calcSum
+ , checkSum: checkSum
+ }
+
+TarHeader.parseNumeric = parseNumeric
+TarHeader.encode = encode
+TarHeader.decode = decode
+
+// note that this will only do the normal ustar header, not any kind
+// of extended posix header file. If something doesn't fit comfortably,
+// then it will set obj.needExtended = true, and set the block to
+// the closest approximation.
+function encode (obj) {
+ if (!obj && !(this instanceof TarHeader)) throw new Error(
+ "encode must be called on a TarHeader, or supplied an object")
+
+ obj = obj || this
+ var block = obj.block = new Buffer(512)
+
+ // if the object has a "prefix", then that's actually an extension of
+ // the path field.
+ if (obj.prefix) {
+ // console.error("%% header encoding, got a prefix", obj.prefix)
+ obj.path = obj.prefix + "/" + obj.path
+ // console.error("%% header encoding, prefixed path", obj.path)
+ obj.prefix = ""
+ }
+
+ obj.needExtended = false
+
+ if (obj.mode) {
+ if (typeof obj.mode === "string") obj.mode = parseInt(obj.mode, 8)
+ obj.mode = obj.mode & 0777
+ }
+
+ for (var f = 0; fields[f] !== null; f ++) {
+ var field = fields[f]
+ , off = fieldOffs[f]
+ , end = fieldEnds[f]
+ , ret
+
+ switch (field) {
+ case "cksum":
+ // special, done below, after all the others
+ break
+
+ case "prefix":
+ // special, this is an extension of the "path" field.
+ // console.error("%% header encoding, skip prefix later")
+ break
+
+ case "type":
+ // convert from long name to a single char.
+ var type = obj.type || "0"
+ if (type.length > 1) {
+ type = tar.types[obj.type]
+ if (!type) type = "0"
+ }
+ writeText(block, off, end, type)
+ break
+
+ case "path":
+ // uses the "prefix" field if > 100 bytes, but <= 255
+ var pathLen = Buffer.byteLength(obj.path)
+ , pathFSize = fieldSize[fields.path]
+ , prefFSize = fieldSize[fields.prefix]
+
+ // paths between 100 and 255 should use the prefix field.
+ // longer than 255
+ if (pathLen > pathFSize &&
+ pathLen <= pathFSize + prefFSize) {
+ // need to find a slash somewhere in the middle so that
+ // path and prefix both fit in their respective fields
+ var searchStart = pathLen - 1 - pathFSize
+ , searchEnd = prefFSize
+ , found = false
+ , pathBuf = new Buffer(obj.path)
+
+ for ( var s = searchStart
+ ; (s <= searchEnd)
+ ; s ++ ) {
+ if (pathBuf[s] === slash || pathBuf[s] === bslash) {
+ found = s
+ break
+ }
+ }
+
+ if (found !== false) {
+ prefix = pathBuf.slice(0, found).toString("utf8")
+ path = pathBuf.slice(found + 1).toString("utf8")
+
+ ret = writeText(block, off, end, path)
+ off = fieldOffs[fields.prefix]
+ end = fieldEnds[fields.prefix]
+ // console.error("%% header writing prefix", off, end, prefix)
+ ret = writeText(block, off, end, prefix) || ret
+ break
+ }
+ }
+
+ // paths less than 100 chars don't need a prefix
+ // and paths longer than 255 need an extended header and will fail
+ // on old implementations no matter what we do here.
+ // Null out the prefix, and fallthrough to default.
+ // console.error("%% header writing no prefix")
+ var poff = fieldOffs[fields.prefix]
+ , pend = fieldEnds[fields.prefix]
+ writeText(block, poff, pend, "")
+ // fallthrough
+
+ // all other fields are numeric or text
+ default:
+ ret = numeric[field]
+ ? writeNumeric(block, off, end, obj[field])
+ : writeText(block, off, end, obj[field] || "")
+ break
+ }
+ obj.needExtended = obj.needExtended || ret
+ }
+
+ var off = fieldOffs[fields.cksum]
+ , end = fieldEnds[fields.cksum]
+
+ writeNumeric(block, off, end, calcSum.call(this, block))
+
+ return block
+}
+
+// if it's a negative number, or greater than will fit,
+// then use write256.
+var MAXNUM = { 12: 077777777777
+ , 11: 07777777777
+ , 8 : 07777777
+ , 7 : 0777777 }
+function writeNumeric (block, off, end, num) {
+ var writeLen = end - off
+ , maxNum = MAXNUM[writeLen] || 0
+
+ num = num || 0
+ // console.error(" numeric", num)
+
+ if (num instanceof Date ||
+ Object.prototype.toString.call(num) === "[object Date]") {
+ num = num.getTime() / 1000
+ }
+
+ if (num > maxNum || num < 0) {
+ write256(block, off, end, num)
+ // need an extended header if negative or too big.
+ return true
+ }
+
+ // god, tar is so annoying
+ // if the string is small enough, you should put a space
+ // between the octal string and the \0, but if it doesn't
+ // fit, then don't.
+ var numStr = Math.floor(num).toString(8)
+ if (num < MAXNUM[writeLen - 1]) numStr += " "
+
+ // pad with "0" chars
+ if (numStr.length < writeLen) {
+ numStr = (new Array(writeLen - numStr.length).join("0")) + numStr
+ }
+
+ if (numStr.length !== writeLen - 1) {
+ throw new Error("invalid length: " + JSON.stringify(numStr) + "\n" +
+ "expected: "+writeLen)
+ }
+ block.write(numStr, off, writeLen, "utf8")
+ block[end - 1] = 0
+}
+
+function write256 (block, off, end, num) {
+ var buf = block.slice(off, end)
+ var positive = num >= 0
+ buf[0] = positive ? 0x80 : 0xFF
+
+ // get the number as a base-256 tuple
+ if (!positive) num *= -1
+ var tuple = []
+ do {
+ var n = num % 256
+ tuple.push(n)
+ num = (num - n) / 256
+ } while (num)
+
+ var bytes = tuple.length
+
+ var fill = buf.length - bytes
+ for (var i = 1; i < fill; i ++) {
+ buf[i] = positive ? 0 : 0xFF
+ }
+
+ // tuple is a base256 number, with [0] as the *least* significant byte
+ // if it's negative, then we need to flip all the bits once we hit the
+ // first non-zero bit. The 2's-complement is (0x100 - n), and the 1's-
+ // complement is (0xFF - n).
+ var zero = true
+ for (i = bytes; i > 0; i --) {
+ var byte = tuple[bytes - i]
+ if (positive) buf[fill + i] = byte
+ else if (zero && byte === 0) buf[fill + i] = 0
+ else if (zero) {
+ zero = false
+ buf[fill + i] = 0x100 - byte
+ } else buf[fill + i] = 0xFF - byte
+ }
+}
+
+function writeText (block, off, end, str) {
+ // strings are written as utf8, then padded with \0
+ var strLen = Buffer.byteLength(str)
+ , writeLen = Math.min(strLen, end - off)
+ // non-ascii fields need extended headers
+ // long fields get truncated
+ , needExtended = strLen !== str.length || strLen > writeLen
+
+ // write the string, and null-pad
+ if (writeLen > 0) block.write(str, off, writeLen, "utf8")
+ for (var i = off + writeLen; i < end; i ++) block[i] = 0
+
+ return needExtended
+}
+
+function calcSum (block) {
+ block = block || this.block
+ assert(Buffer.isBuffer(block) && block.length === 512)
+
+ if (!block) throw new Error("Need block to checksum")
+
+ // now figure out what it would be if the cksum was " "
+ var sum = 0
+ , start = fieldOffs[fields.cksum]
+ , end = fieldEnds[fields.cksum]
+
+ for (var i = 0; i < fieldOffs[fields.cksum]; i ++) {
+ sum += block[i]
+ }
+
+ for (var i = start; i < end; i ++) {
+ sum += space
+ }
+
+ for (var i = end; i < 512; i ++) {
+ sum += block[i]
+ }
+
+ return sum
+}
+
+
+function checkSum (block) {
+ var sum = calcSum.call(this, block)
+ block = block || this.block
+
+ var cksum = block.slice(fieldOffs[fields.cksum], fieldEnds[fields.cksum])
+ cksum = parseNumeric(cksum)
+
+ return cksum === sum
+}
+
+function decode (block) {
+ block = block || this.block
+ assert(Buffer.isBuffer(block) && block.length === 512)
+
+ this.block = block
+ this.cksumValid = this.checkSum()
+
+ var prefix = null
+
+ // slice off each field.
+ for (var f = 0; fields[f] !== null; f ++) {
+ var field = fields[f]
+ , val = block.slice(fieldOffs[f], fieldEnds[f])
+
+ switch (field) {
+ case "ustar":
+ // if not ustar, then everything after that is just padding.
+ if (val.toString() !== "ustar\0") {
+ this.ustar = false
+ return
+ } else {
+ // console.error("ustar:", val, val.toString())
+ this.ustar = val.toString()
+ }
+ break
+
+ // prefix is special, since it might signal the xstar header
+ case "prefix":
+ var atime = parseNumeric(val.slice(131, 131 + 12))
+ , ctime = parseNumeric(val.slice(131 + 12, 131 + 12 + 12))
+ if ((val[130] === 0 || val[130] === space) &&
+ typeof atime === "number" &&
+ typeof ctime === "number" &&
+ val[131 + 12] === space &&
+ val[131 + 12 + 12] === space) {
+ this.atime = atime
+ this.ctime = ctime
+ val = val.slice(0, 130)
+ }
+ prefix = val.toString("utf8").replace(/\0+$/, "")
+ // console.error("%% header reading prefix", prefix)
+ break
+
+ // all other fields are null-padding text
+ // or a number.
+ default:
+ if (numeric[field]) {
+ this[field] = parseNumeric(val)
+ } else {
+ this[field] = val.toString("utf8").replace(/\0+$/, "")
+ }
+ break
+ }
+ }
+
+ // if we got a prefix, then prepend it to the path.
+ if (prefix) {
+ this.path = prefix + "/" + this.path
+ // console.error("%% header got a prefix", this.path)
+ }
+}
+
+function parse256 (buf) {
+ // first byte MUST be either 80 or FF
+ // 80 for positive, FF for 2's comp
+ var positive
+ if (buf[0] === 0x80) positive = true
+ else if (buf[0] === 0xFF) positive = false
+ else return null
+
+ // build up a base-256 tuple from the least sig to the highest
+ var zero = false
+ , tuple = []
+ for (var i = buf.length - 1; i > 0; i --) {
+ var byte = buf[i]
+ if (positive) tuple.push(byte)
+ else if (zero && byte === 0) tuple.push(0)
+ else if (zero) {
+ zero = false
+ tuple.push(0x100 - byte)
+ } else tuple.push(0xFF - byte)
+ }
+
+ for (var sum = 0, i = 0, l = tuple.length; i < l; i ++) {
+ sum += tuple[i] * Math.pow(256, i)
+ }
+
+ return positive ? sum : -1 * sum
+}
+
+function parseNumeric (f) {
+ if (f[0] & 0x80) return parse256(f)
+
+ var str = f.toString("utf8").split("\0")[0].trim()
+ , res = parseInt(str, 8)
+
+ return isNaN(res) ? null : res
+}
+
diff --git a/lib/pack.js b/lib/pack.js
new file mode 100644
index 0000000..3ff14dd
--- /dev/null
+++ b/lib/pack.js
@@ -0,0 +1,231 @@
+// pipe in an fstream, and it'll make a tarball.
+// key-value pair argument is global extended header props.
+
+module.exports = Pack
+
+var EntryWriter = require("./entry-writer.js")
+ , Stream = require("stream").Stream
+ , path = require("path")
+ , inherits = require("inherits")
+ , GlobalHeaderWriter = require("./global-header-writer.js")
+ , collect = require("fstream").collect
+ , eof = new Buffer(512)
+
+for (var i = 0; i < 512; i ++) eof[i] = 0
+
+inherits(Pack, Stream)
+
+function Pack (props) {
+ // console.error("-- p ctor")
+ var me = this
+ if (!(me instanceof Pack)) return new Pack(props)
+
+ if (props) me._noProprietary = props.noProprietary
+ else me._noProprietary = false
+
+ me._global = props
+
+ me.readable = true
+ me.writable = true
+ me._buffer = []
+ // console.error("-- -- set current to null in ctor")
+ me._currentEntry = null
+ me._processing = false
+
+ me._pipeRoot = null
+ me.on("pipe", function (src) {
+ if (src.root === me._pipeRoot) return
+ me._pipeRoot = src
+ src.on("end", function () {
+ me._pipeRoot = null
+ })
+ me.add(src)
+ })
+}
+
+Pack.prototype.addGlobal = function (props) {
+ // console.error("-- p addGlobal")
+ if (this._didGlobal) return
+ this._didGlobal = true
+
+ var me = this
+ GlobalHeaderWriter(props)
+ .on("data", function (c) {
+ me.emit("data", c)
+ })
+ .end()
+}
+
+Pack.prototype.add = function (stream) {
+ if (this._global && !this._didGlobal) this.addGlobal(this._global)
+
+ if (this._ended) return this.emit("error", new Error("add after end"))
+
+ collect(stream)
+ this._buffer.push(stream)
+ this._process()
+ this._needDrain = this._buffer.length > 0
+ return !this._needDrain
+}
+
+Pack.prototype.pause = function () {
+ this._paused = true
+ if (this._currentEntry) this._currentEntry.pause()
+ this.emit("pause")
+}
+
+Pack.prototype.resume = function () {
+ this._paused = false
+ if (this._currentEntry) this._currentEntry.resume()
+ this.emit("resume")
+ this._process()
+}
+
+Pack.prototype.end = function () {
+ this._ended = true
+ this._buffer.push(eof)
+ this._process()
+}
+
+Pack.prototype._process = function () {
+ var me = this
+ if (me._paused || me._processing) {
+ return
+ }
+
+ var entry = me._buffer.shift()
+
+ if (!entry) {
+ if (me._needDrain) {
+ me.emit("drain")
+ }
+ return
+ }
+
+ if (entry.ready === false) {
+ // console.error("-- entry is not ready", entry)
+ me._buffer.unshift(entry)
+ entry.on("ready", function () {
+ // console.error("-- -- ready!", entry)
+ me._process()
+ })
+ return
+ }
+
+ me._processing = true
+
+ if (entry === eof) {
+ // need 2 ending null blocks.
+ me.emit("data", eof)
+ me.emit("data", eof)
+ me.emit("end")
+ me.emit("close")
+ return
+ }
+
+ // Change the path to be relative to the root dir that was
+ // added to the tarball.
+ //
+ // XXX This should be more like how -C works, so you can
+ // explicitly set a root dir, and also explicitly set a pathname
+ // in the tarball to use. That way we can skip a lot of extra
+ // work when resolving symlinks for bundled dependencies in npm.
+
+ var root = path.dirname((entry.root || entry).path)
+ var wprops = {}
+
+ Object.keys(entry.props || {}).forEach(function (k) {
+ wprops[k] = entry.props[k]
+ })
+
+ if (me._noProprietary) wprops.noProprietary = true
+
+ wprops.path = path.relative(root, entry.path || '')
+
+ // actually not a matter of opinion or taste.
+ if (process.platform === "win32") {
+ wprops.path = wprops.path.replace(/\\/g, "/")
+ }
+
+ if (!wprops.type)
+ wprops.type = 'Directory'
+
+ switch (wprops.type) {
+ // sockets not supported
+ case "Socket":
+ return
+
+ case "Directory":
+ wprops.path += "/"
+ wprops.size = 0
+ break
+
+ case "Link":
+ var lp = path.resolve(path.dirname(entry.path), entry.linkpath)
+ wprops.linkpath = path.relative(root, lp) || "."
+ wprops.size = 0
+ break
+
+ case "SymbolicLink":
+ var lp = path.resolve(path.dirname(entry.path), entry.linkpath)
+ wprops.linkpath = path.relative(path.dirname(entry.path), lp) || "."
+ wprops.size = 0
+ break
+ }
+
+ // console.error("-- new writer", wprops)
+ // if (!wprops.type) {
+ // // console.error("-- no type?", entry.constructor.name, entry)
+ // }
+
+ // console.error("-- -- set current to new writer", wprops.path)
+ var writer = me._currentEntry = EntryWriter(wprops)
+
+ writer.parent = me
+
+ // writer.on("end", function () {
+ // // console.error("-- -- writer end", writer.path)
+ // })
+
+ writer.on("data", function (c) {
+ me.emit("data", c)
+ })
+
+ writer.on("header", function () {
+ Buffer.prototype.toJSON = function () {
+ return this.toString().split(/\0/).join(".")
+ }
+ // console.error("-- -- writer header %j", writer.props)
+ if (writer.props.size === 0) nextEntry()
+ })
+ writer.on("close", nextEntry)
+
+ var ended = false
+ function nextEntry () {
+ if (ended) return
+ ended = true
+
+ // console.error("-- -- writer close", writer.path)
+ // console.error("-- -- set current to null", wprops.path)
+ me._currentEntry = null
+ me._processing = false
+ me._process()
+ }
+
+ writer.on("error", function (er) {
+ // console.error("-- -- writer error", writer.path)
+ me.emit("error", er)
+ })
+
+ // if it's the root, then there's no need to add its entries,
+ // or data, since they'll be added directly.
+ if (entry === me._pipeRoot) {
+ // console.error("-- is the root, don't auto-add")
+ writer.add = null
+ }
+
+ entry.pipe(writer)
+}
+
+Pack.prototype.destroy = function () {}
+Pack.prototype.write = function () {}
diff --git a/lib/parse.js b/lib/parse.js
new file mode 100644
index 0000000..8517c48
--- /dev/null
+++ b/lib/parse.js
@@ -0,0 +1,271 @@
+
+// A writable stream.
+// It emits "entry" events, which provide a readable stream that has
+// header info attached.
+
+module.exports = Parse.create = Parse
+
+var stream = require("stream")
+ , Stream = stream.Stream
+ , BlockStream = require("block-stream")
+ , tar = require("../tar.js")
+ , TarHeader = require("./header.js")
+ , Entry = require("./entry.js")
+ , BufferEntry = require("./buffer-entry.js")
+ , ExtendedHeader = require("./extended-header.js")
+ , assert = require("assert").ok
+ , inherits = require("inherits")
+ , fstream = require("fstream")
+
+// reading a tar is a lot like reading a directory
+// However, we're actually not going to run the ctor,
+// since it does a stat and various other stuff.
+// This inheritance gives us the pause/resume/pipe
+// behavior that is desired.
+inherits(Parse, fstream.Reader)
+
+function Parse () {
+ var me = this
+ if (!(me instanceof Parse)) return new Parse()
+
+ // doesn't apply fstream.Reader ctor?
+ // no, becasue we don't want to stat/etc, we just
+ // want to get the entry/add logic from .pipe()
+ Stream.apply(me)
+
+ me.writable = true
+ me.readable = true
+ me._stream = new BlockStream(512)
+ me.position = 0
+ me._ended = false
+
+ me._stream.on("error", function (e) {
+ me.emit("error", e)
+ })
+
+ me._stream.on("data", function (c) {
+ me._process(c)
+ })
+
+ me._stream.on("end", function () {
+ me._streamEnd()
+ })
+
+ me._stream.on("drain", function () {
+ me.emit("drain")
+ })
+}
+
+// overridden in Extract class, since it needs to
+// wait for its DirWriter part to finish before
+// emitting "end"
+Parse.prototype._streamEnd = function () {
+ var me = this
+ if (!me._ended) me.error("unexpected eof")
+ me.emit("end")
+}
+
+// a tar reader is actually a filter, not just a readable stream.
+// So, you should pipe a tarball stream into it, and it needs these
+// write/end methods to do that.
+Parse.prototype.write = function (c) {
+ if (this._ended) {
+ // gnutar puts a LOT of nulls at the end.
+ // you can keep writing these things forever.
+ // Just ignore them.
+ for (var i = 0, l = c.length; i > l; i ++) {
+ if (c[i] !== 0) return this.error("write() after end()")
+ }
+ return
+ }
+ return this._stream.write(c)
+}
+
+Parse.prototype.end = function (c) {
+ this._ended = true
+ return this._stream.end(c)
+}
+
+// don't need to do anything, since we're just
+// proxying the data up from the _stream.
+// Just need to override the parent's "Not Implemented"
+// error-thrower.
+Parse.prototype._read = function () {}
+
+Parse.prototype._process = function (c) {
+ assert(c && c.length === 512, "block size should be 512")
+
+ // one of three cases.
+ // 1. A new header
+ // 2. A part of a file/extended header
+ // 3. One of two or more EOF null blocks
+
+ if (this._entry) {
+ var entry = this._entry
+ entry.write(c)
+ if (entry._remaining === 0) {
+ entry.end()
+ this._entry = null
+ }
+ } else {
+ // either zeroes or a header
+ var zero = true
+ for (var i = 0; i < 512 && zero; i ++) {
+ zero = c[i] === 0
+ }
+
+ // eof is *at least* 2 blocks of nulls, and then the end of the
+ // file. you can put blocks of nulls between entries anywhere,
+ // so appending one tarball to another is technically valid.
+ // ending without the eof null blocks is not allowed, however.
+ if (zero) {
+ if (this._eofStarted)
+ this._ended = true
+ this._eofStarted = true
+ } else {
+ this._eofStarted = false
+ this._startEntry(c)
+ }
+ }
+
+ this.position += 512
+}
+
+// take a header chunk, start the right kind of entry.
+Parse.prototype._startEntry = function (c) {
+ var header = new TarHeader(c)
+ , self = this
+ , entry
+ , ev
+ , EntryType
+ , onend
+ , meta = false
+
+ if (null === header.size || !header.cksumValid) {
+ var e = new Error("invalid tar file")
+ e.header = header
+ e.tar_file_offset = this.position
+ e.tar_block = this.position / 512
+ return this.emit("error", e)
+ }
+
+ switch (tar.types[header.type]) {
+ case "File":
+ case "OldFile":
+ case "Link":
+ case "SymbolicLink":
+ case "CharacterDevice":
+ case "BlockDevice":
+ case "Directory":
+ case "FIFO":
+ case "ContiguousFile":
+ case "GNUDumpDir":
+ // start a file.
+ // pass in any extended headers
+ // These ones consumers are typically most interested in.
+ EntryType = Entry
+ ev = "entry"
+ break
+
+ case "GlobalExtendedHeader":
+ // extended headers that apply to the rest of the tarball
+ EntryType = ExtendedHeader
+ onend = function () {
+ self._global = self._global || {}
+ Object.keys(entry.fields).forEach(function (k) {
+ self._global[k] = entry.fields[k]
+ })
+ }
+ ev = "globalExtendedHeader"
+ meta = true
+ break
+
+ case "ExtendedHeader":
+ case "OldExtendedHeader":
+ // extended headers that apply to the next entry
+ EntryType = ExtendedHeader
+ onend = function () {
+ self._extended = entry.fields
+ }
+ ev = "extendedHeader"
+ meta = true
+ break
+
+ case "NextFileHasLongLinkpath":
+ // set linkpath=<contents> in extended header
+ EntryType = BufferEntry
+ onend = function () {
+ self._extended = self._extended || {}
+ self._extended.linkpath = entry.body
+ }
+ ev = "longLinkpath"
+ meta = true
+ break
+
+ case "NextFileHasLongPath":
+ case "OldGnuLongPath":
+ // set path=<contents> in file-extended header
+ EntryType = BufferEntry
+ onend = function () {
+ self._extended = self._extended || {}
+ self._extended.path = entry.body
+ }
+ ev = "longPath"
+ meta = true
+ break
+
+ default:
+ // all the rest we skip, but still set the _entry
+ // member, so that we can skip over their data appropriately.
+ // emit an event to say that this is an ignored entry type?
+ EntryType = Entry
+ ev = "ignoredEntry"
+ break
+ }
+
+ var global, extended
+ if (meta) {
+ global = extended = null
+ } else {
+ var global = this._global
+ var extended = this._extended
+
+ // extendedHeader only applies to one entry, so once we start
+ // an entry, it's over.
+ this._extended = null
+ }
+ entry = new EntryType(header, extended, global)
+ entry.meta = meta
+
+ // only proxy data events of normal files.
+ if (!meta) {
+ entry.on("data", function (c) {
+ me.emit("data", c)
+ })
+ }
+
+ if (onend) entry.on("end", onend)
+
+ this._entry = entry
+ var me = this
+
+ entry.on("pause", function () {
+ me.pause()
+ })
+
+ entry.on("resume", function () {
+ me.resume()
+ })
+
+ if (this.listeners("*").length) {
+ this.emit("*", ev, entry)
+ }
+
+ this.emit(ev, entry)
+
+ // Zero-byte entry. End immediately.
+ if (entry.props.size === 0) {
+ entry.end()
+ this._entry = null
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..61fc2f0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,26 @@
+{
+ "author": "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me/)",
+ "name": "tar",
+ "description": "tar for node",
+ "version": "1.0.3",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/isaacs/node-tar.git"
+ },
+ "main": "tar.js",
+ "scripts": {
+ "test": "tap test/*.js"
+ },
+ "dependencies": {
+ "block-stream": "*",
+ "fstream": "^1.0.2",
+ "inherits": "2"
+ },
+ "devDependencies": {
+ "graceful-fs": "^3.0.2",
+ "rimraf": "1.x",
+ "tap": "0.x",
+ "mkdirp": "^0.5.0"
+ },
+ "license": "BSD"
+}
diff --git a/tar.js b/tar.js
new file mode 100644
index 0000000..a81298b
--- /dev/null
+++ b/tar.js
@@ -0,0 +1,173 @@
+// field paths that every tar file must have.
+// header is padded to 512 bytes.
+var f = 0
+ , fields = {}
+ , path = fields.path = f++
+ , mode = fields.mode = f++
+ , uid = fields.uid = f++
+ , gid = fields.gid = f++
+ , size = fields.size = f++
+ , mtime = fields.mtime = f++
+ , cksum = fields.cksum = f++
+ , type = fields.type = f++
+ , linkpath = fields.linkpath = f++
+ , headerSize = 512
+ , blockSize = 512
+ , fieldSize = []
+
+fieldSize[path] = 100
+fieldSize[mode] = 8
+fieldSize[uid] = 8
+fieldSize[gid] = 8
+fieldSize[size] = 12
+fieldSize[mtime] = 12
+fieldSize[cksum] = 8
+fieldSize[type] = 1
+fieldSize[linkpath] = 100
+
+// "ustar\0" may introduce another bunch of headers.
+// these are optional, and will be nulled out if not present.
+
+var ustar = fields.ustar = f++
+ , ustarver = fields.ustarver = f++
+ , uname = fields.uname = f++
+ , gname = fields.gname = f++
+ , devmaj = fields.devmaj = f++
+ , devmin = fields.devmin = f++
+ , prefix = fields.prefix = f++
+ , fill = fields.fill = f++
+
+// terminate fields.
+fields[f] = null
+
+fieldSize[ustar] = 6
+fieldSize[ustarver] = 2
+fieldSize[uname] = 32
+fieldSize[gname] = 32
+fieldSize[devmaj] = 8
+fieldSize[devmin] = 8
+fieldSize[prefix] = 155
+fieldSize[fill] = 12
+
+// nb: prefix field may in fact be 130 bytes of prefix,
+// a null char, 12 bytes for atime, 12 bytes for ctime.
+//
+// To recognize this format:
+// 1. prefix[130] === ' ' or '\0'
+// 2. atime and ctime are octal numeric values
+// 3. atime and ctime have ' ' in their last byte
+
+var fieldEnds = {}
+ , fieldOffs = {}
+ , fe = 0
+for (var i = 0; i < f; i ++) {
+ fieldOffs[i] = fe
+ fieldEnds[i] = (fe += fieldSize[i])
+}
+
+// build a translation table of field paths.
+Object.keys(fields).forEach(function (f) {
+ if (fields[f] !== null) fields[fields[f]] = f
+})
+
+// different values of the 'type' field
+// paths match the values of Stats.isX() functions, where appropriate
+var types =
+ { 0: "File"
+ , "\0": "OldFile" // like 0
+ , "": "OldFile"
+ , 1: "Link"
+ , 2: "SymbolicLink"
+ , 3: "CharacterDevice"
+ , 4: "BlockDevice"
+ , 5: "Directory"
+ , 6: "FIFO"
+ , 7: "ContiguousFile" // like 0
+ // posix headers
+ , g: "GlobalExtendedHeader" // k=v for the rest of the archive
+ , x: "ExtendedHeader" // k=v for the next file
+ // vendor-specific stuff
+ , A: "SolarisACL" // skip
+ , D: "GNUDumpDir" // like 5, but with data, which should be skipped
+ , I: "Inode" // metadata only, skip
+ , K: "NextFileHasLongLinkpath" // data = link path of next file
+ , L: "NextFileHasLongPath" // data = path of next file
+ , M: "ContinuationFile" // skip
+ , N: "OldGnuLongPath" // like L
+ , S: "SparseFile" // skip
+ , V: "TapeVolumeHeader" // skip
+ , X: "OldExtendedHeader" // like x
+ }
+
+Object.keys(types).forEach(function (t) {
+ types[types[t]] = types[types[t]] || t
+})
+
+// values for the mode field
+var modes =
+ { suid: 04000 // set uid on extraction
+ , sgid: 02000 // set gid on extraction
+ , svtx: 01000 // set restricted deletion flag on dirs on extraction
+ , uread: 0400
+ , uwrite: 0200
+ , uexec: 0100
+ , gread: 040
+ , gwrite: 020
+ , gexec: 010
+ , oread: 4
+ , owrite: 2
+ , oexec: 1
+ , all: 07777
+ }
+
+var numeric =
+ { mode: true
+ , uid: true
+ , gid: true
+ , size: true
+ , mtime: true
+ , devmaj: true
+ , devmin: true
+ , cksum: true
+ , atime: true
+ , ctime: true
+ , dev: true
+ , ino: true
+ , nlink: true
+ }
+
+Object.keys(modes).forEach(function (t) {
+ modes[modes[t]] = modes[modes[t]] || t
+})
+
+var knownExtended =
+ { atime: true
+ , charset: true
+ , comment: true
+ , ctime: true
+ , gid: true
+ , gname: true
+ , linkpath: true
+ , mtime: true
+ , path: true
+ , realtime: true
+ , security: true
+ , size: true
+ , uid: true
+ , uname: true }
+
+
+exports.fields = fields
+exports.fieldSize = fieldSize
+exports.fieldOffs = fieldOffs
+exports.fieldEnds = fieldEnds
+exports.types = types
+exports.modes = modes
+exports.numeric = numeric
+exports.headerSize = headerSize
+exports.blockSize = blockSize
+exports.knownExtended = knownExtended
+
+exports.Pack = require("./lib/pack.js")
+exports.Parse = require("./lib/parse.js")
+exports.Extract = require("./lib/extract.js")
diff --git a/test/00-setup-fixtures.js b/test/00-setup-fixtures.js
new file mode 100644
index 0000000..1524ff7
--- /dev/null
+++ b/test/00-setup-fixtures.js
@@ -0,0 +1,53 @@
+// the fixtures have some weird stuff that is painful
+// to include directly in the repo for various reasons.
+//
+// So, unpack the fixtures with the system tar first.
+//
+// This means, of course, that it'll only work if you
+// already have a tar implementation, and some of them
+// will not properly unpack the fixtures anyway.
+//
+// But, since usually those tests will fail on Windows
+// and other systems with less capable filesystems anyway,
+// at least this way we don't cause inconveniences by
+// merely cloning the repo or installing the package.
+
+var tap = require("tap")
+, child_process = require("child_process")
+, rimraf = require("rimraf")
+, test = tap.test
+, path = require("path")
+
+test("clean fixtures", function (t) {
+ rimraf(path.resolve(__dirname, "fixtures"), function (er) {
+ t.ifError(er, "rimraf ./fixtures/")
+ t.end()
+ })
+})
+
+test("clean tmp", function (t) {
+ rimraf(path.resolve(__dirname, "tmp"), function (er) {
+ t.ifError(er, "rimraf ./tmp/")
+ t.end()
+ })
+})
+
+test("extract fixtures", function (t) {
+ var c = child_process.spawn("tar"
+ ,["xzvf", "fixtures.tgz"]
+ ,{ cwd: __dirname })
+
+ c.stdout.on("data", errwrite)
+ c.stderr.on("data", errwrite)
+ function errwrite (chunk) {
+ process.stderr.write(chunk)
+ }
+
+ c.on("exit", function (code) {
+ t.equal(code, 0, "extract fixtures should exit with 0")
+ if (code) {
+ t.comment("Note, all tests from here on out will fail because of this.")
+ }
+ t.end()
+ })
+})
diff --git a/test/extract-move.js b/test/extract-move.js
new file mode 100644
index 0000000..45400cd
--- /dev/null
+++ b/test/extract-move.js
@@ -0,0 +1,132 @@
+// Set the umask, so that it works the same everywhere.
+process.umask(parseInt('22', 8))
+
+var tap = require("tap")
+ , tar = require("../tar.js")
+ , fs = require("fs")
+ , gfs = require("graceful-fs")
+ , path = require("path")
+ , file = path.resolve(__dirname, "fixtures/dir.tar")
+ , target = path.resolve(__dirname, "tmp/extract-test")
+ , index = 0
+ , fstream = require("fstream")
+ , rimraf = require("rimraf")
+ , mkdirp = require("mkdirp")
+
+ , ee = 0
+ , expectEntries = [
+ {
+ "path" : "dir/",
+ "mode" : "750",
+ "type" : "5",
+ "depth" : undefined,
+ "size" : 0,
+ "linkpath" : "",
+ "nlink" : undefined,
+ "dev" : undefined,
+ "ino" : undefined
+ },
+ {
+ "path" : "dir/sub/",
+ "mode" : "750",
+ "type" : "5",
+ "depth" : undefined,
+ "size" : 0,
+ "linkpath" : "",
+ "nlink" : undefined,
+ "dev" : undefined,
+ "ino" : undefined
+ } ]
+
+function slow (fs, method, t1, t2) {
+ var orig = fs[method]
+ if (!orig) return null
+ fs[method] = function () {
+ var args = [].slice.call(arguments)
+ console.error("slow", method, args[0])
+ var cb = args.pop()
+
+ setTimeout(function () {
+ orig.apply(fs, args.concat(function(er, data) {
+ setTimeout(function() {
+ cb(er, data)
+ }, t2)
+ }))
+ }, t1)
+ }
+}
+
+// Make sure we get the graceful-fs that fstream is using.
+var gfs2
+try {
+ gfs2 = require("fstream/node_modules/graceful-fs")
+} catch (er) {}
+
+var slowMethods = ["chown", "chmod", "utimes", "lutimes"]
+slowMethods.forEach(function (method) {
+ var t1 = 500
+ var t2 = 0
+ slow(fs, method, t1, t2)
+ slow(gfs, method, t1, t2)
+ if (gfs2) {
+ slow(gfs2, method, t1, t2)
+ }
+})
+
+
+
+// The extract class basically just pipes the input
+// to a Reader, and then to a fstream.DirWriter
+
+// So, this is as much a test of fstream.Reader and fstream.Writer
+// as it is of tar.Extract, but it sort of makes sense.
+
+tap.test("preclean", function (t) {
+ rimraf.sync(target)
+ /mkdirp.sync(target)
+ t.pass("cleaned!")
+ t.end()
+})
+
+tap.test("extract test", function (t) {
+ var extract = tar.Extract(target)
+ var inp = fs.createReadStream(file)
+
+ // give it a weird buffer size to try to break in odd places
+ inp.bufferSize = 1234
+
+ inp.pipe(extract)
+
+ extract.on("end", function () {
+ rimraf.sync(target)
+
+ t.equal(ee, expectEntries.length, "should see "+ee+" entries")
+
+ // should get no more entries after end
+ extract.removeAllListeners("entry")
+ extract.on("entry", function (e) {
+ t.fail("Should not get entries after end!")
+ })
+
+ t.end()
+ })
+
+
+ extract.on("entry", function (entry) {
+ var found =
+ { path: entry.path
+ , mode: entry.props.mode.toString(8)
+ , type: entry.props.type
+ , depth: entry.props.depth
+ , size: entry.props.size
+ , linkpath: entry.props.linkpath
+ , nlink: entry.props.nlink
+ , dev: entry.props.dev
+ , ino: entry.props.ino
+ }
+
+ var wanted = expectEntries[ee ++]
+
+ t.equivalent(found, wanted, "tar entry " + ee + " " + wanted.path)
+ })
+})
diff --git a/test/extract.js b/test/extract.js
new file mode 100644
index 0000000..eca4e7c
--- /dev/null
+++ b/test/extract.js
@@ -0,0 +1,367 @@
+// Set the umask, so that it works the same everywhere.
+process.umask(parseInt('22', 8))
+
+var tap = require("tap")
+ , tar = require("../tar.js")
+ , fs = require("fs")
+ , path = require("path")
+ , file = path.resolve(__dirname, "fixtures/c.tar")
+ , target = path.resolve(__dirname, "tmp/extract-test")
+ , index = 0
+ , fstream = require("fstream")
+
+ , ee = 0
+ , expectEntries =
+[ { path: 'c.txt',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 513,
+ linkpath: '',
+ nlink: undefined,
+ dev: undefined,
+ ino: undefined },
+ { path: 'cc.txt',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 513,
+ linkpath: '',
+ nlink: undefined,
+ dev: undefined,
+ ino: undefined },
+ { path: 'r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 100,
+ linkpath: '',
+ nlink: undefined,
+ dev: undefined,
+ ino: undefined },
+ { path: 'Ω.txt',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 2,
+ linkpath: '',
+ nlink: undefined,
+ dev: undefined,
+ ino: undefined },
+ { path: 'Ω.txt',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 2,
+ linkpath: '',
+ nlink: 1,
+ dev: 234881026,
+ ino: 51693379 },
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 200,
+ linkpath: '',
+ nlink: 1,
+ dev: 234881026,
+ ino: 51681874 },
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 201,
+ linkpath: '',
+ nlink: undefined,
+ dev: undefined,
+ ino: undefined },
+ { path: '200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL',
+ mode: '777',
+ type: '2',
+ depth: undefined,
+ size: 0,
+ linkpath: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ nlink: undefined,
+ dev: undefined,
+ ino: undefined },
+ { path: '200-hard',
+ mode: '644',
+ type: '0',
+ depth: undefined,
+ size: 200,
+ linkpath: '',
+ nlink: 2,
+ dev: 234881026,
+ ino: 51681874 },
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: '644',
+ type: '1',
+ depth: undefined,
+ size: 0,
+ linkpath: path.resolve(target, '200-hard'),
+ nlink: 2,
+ dev: 234881026,
+ ino: 51681874 } ]
+
+ , ef = 0
+ , expectFiles =
+[ { path: '',
+ mode: '40755',
+ type: 'Directory',
+ depth: 0,
+ linkpath: undefined },
+ { path: '/200-hard',
+ mode: '100644',
+ type: 'File',
+ depth: 1,
+ size: 200,
+ linkpath: undefined,
+ nlink: 2 },
+ { path: '/200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL',
+ mode: '120777',
+ type: 'SymbolicLink',
+ depth: 1,
+ size: 200,
+ linkpath: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ nlink: 1 },
+ { path: '/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: '100644',
+ type: 'Link',
+ depth: 1,
+ size: 200,
+ linkpath: path.join(target, '200-hard'),
+ nlink: 2 },
+ { path: '/c.txt',
+ mode: '100644',
+ type: 'File',
+ depth: 1,
+ size: 513,
+ linkpath: undefined,
+ nlink: 1 },
+ { path: '/cc.txt',
+ mode: '100644',
+ type: 'File',
+ depth: 1,
+ size: 513,
+ linkpath: undefined,
+ nlink: 1 },
+ { path: '/r',
+ mode: '40755',
+ type: 'Directory',
+ depth: 1,
+ linkpath: undefined },
+ { path: '/r/e',
+ mode: '40755',
+ type: 'Directory',
+ depth: 2,
+ linkpath: undefined },
+ { path: '/r/e/a',
+ mode: '40755',
+ type: 'Directory',
+ depth: 3,
+ linkpath: undefined },
+ { path: '/r/e/a/l',
+ mode: '40755',
+ type: 'Directory',
+ depth: 4,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l',
+ mode: '40755',
+ type: 'Directory',
+ depth: 5,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y',
+ mode: '40755',
+ type: 'Directory',
+ depth: 6,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-',
+ mode: '40755',
+ type: 'Directory',
+ depth: 7,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d',
+ mode: '40755',
+ type: 'Directory',
+ depth: 8,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e',
+ mode: '40755',
+ type: 'Directory',
+ depth: 9,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e',
+ mode: '40755',
+ type: 'Directory',
+ depth: 10,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p',
+ mode: '40755',
+ type: 'Directory',
+ depth: 11,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-',
+ mode: '40755',
+ type: 'Directory',
+ depth: 12,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f',
+ mode: '40755',
+ type: 'Directory',
+ depth: 13,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o',
+ mode: '40755',
+ type: 'Directory',
+ depth: 14,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l',
+ mode: '40755',
+ type: 'Directory',
+ depth: 15,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d',
+ mode: '40755',
+ type: 'Directory',
+ depth: 16,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e',
+ mode: '40755',
+ type: 'Directory',
+ depth: 17,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r',
+ mode: '40755',
+ type: 'Directory',
+ depth: 18,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-',
+ mode: '40755',
+ type: 'Directory',
+ depth: 19,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p',
+ mode: '40755',
+ type: 'Directory',
+ depth: 20,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a',
+ mode: '40755',
+ type: 'Directory',
+ depth: 21,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t',
+ mode: '40755',
+ type: 'Directory',
+ depth: 22,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h',
+ mode: '40755',
+ type: 'Directory',
+ depth: 23,
+ linkpath: undefined },
+ { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: '100644',
+ type: 'File',
+ depth: 24,
+ size: 100,
+ linkpath: undefined,
+ nlink: 1 },
+ { path: '/Ω.txt',
+ mode: '100644',
+ type: 'File',
+ depth: 1,
+ size: 2,
+ linkpath: undefined,
+ nlink: 1 } ]
+
+
+
+// The extract class basically just pipes the input
+// to a Reader, and then to a fstream.DirWriter
+
+// So, this is as much a test of fstream.Reader and fstream.Writer
+// as it is of tar.Extract, but it sort of makes sense.
+
+tap.test("preclean", function (t) {
+ require("rimraf").sync(__dirname + "/tmp/extract-test")
+ t.pass("cleaned!")
+ t.end()
+})
+
+tap.test("extract test", function (t) {
+ var extract = tar.Extract(target)
+ var inp = fs.createReadStream(file)
+
+ // give it a weird buffer size to try to break in odd places
+ inp.bufferSize = 1234
+
+ inp.pipe(extract)
+
+ extract.on("end", function () {
+ t.equal(ee, expectEntries.length, "should see "+ee+" entries")
+
+ // should get no more entries after end
+ extract.removeAllListeners("entry")
+ extract.on("entry", function (e) {
+ t.fail("Should not get entries after end!")
+ })
+
+ next()
+ })
+
+ extract.on("entry", function (entry) {
+ var found =
+ { path: entry.path
+ , mode: entry.props.mode.toString(8)
+ , type: entry.props.type
+ , depth: entry.props.depth
+ , size: entry.props.size
+ , linkpath: entry.props.linkpath
+ , nlink: entry.props.nlink
+ , dev: entry.props.dev
+ , ino: entry.props.ino
+ }
+
+ var wanted = expectEntries[ee ++]
+
+ t.equivalent(found, wanted, "tar entry " + ee + " " + wanted.path)
+ })
+
+ function next () {
+ var r = fstream.Reader({ path: target
+ , type: "Directory"
+ // this is just to encourage consistency
+ , sort: "alpha" })
+
+ r.on("ready", function () {
+ foundEntry(r)
+ })
+
+ r.on("end", finish)
+
+ function foundEntry (entry) {
+ var p = entry.path.substr(target.length)
+ var found =
+ { path: p
+ , mode: entry.props.mode.toString(8)
+ , type: entry.props.type
+ , depth: entry.props.depth
+ , size: entry.props.size
+ , linkpath: entry.props.linkpath
+ , nlink: entry.props.nlink
+ }
+
+ var wanted = expectFiles[ef ++]
+
+ t.has(found, wanted, "unpacked file " + ef + " " + wanted.path)
+
+ entry.on("entry", foundEntry)
+ }
+
+ function finish () {
+ t.equal(ef, expectFiles.length, "should have "+ef+" items")
+ t.end()
+ }
+ }
+})
diff --git a/test/fixtures.tgz b/test/fixtures.tgz
new file mode 100644
index 0000000..f167602
Binary files /dev/null and b/test/fixtures.tgz differ
diff --git a/test/header.js b/test/header.js
new file mode 100644
index 0000000..8ea6f79
--- /dev/null
+++ b/test/header.js
@@ -0,0 +1,183 @@
+var tap = require("tap")
+var TarHeader = require("../lib/header.js")
+var tar = require("../tar.js")
+var fs = require("fs")
+
+
+var headers =
+ { "a.txt file header":
+ [ "612e7478740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000303030363434200030353737363120003030303032342000303030303030303034303120313136353133363033333320303132343531002030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [...]
+ , { cksumValid: true
+ , path: 'a.txt'
+ , mode: 420
+ , uid: 24561
+ , gid: 20
+ , size: 257
+ , mtime: 1319493851
+ , cksum: 5417
+ , type: '0'
+ , linkpath: ''
+ , ustar: 'ustar\0'
+ , ustarver: '00'
+ , uname: 'isaacs'
+ , gname: 'staff'
+ , devmaj: 0
+ , devmin: 0
+ , fill: '' }
+ ]
+
+ , "omega pax": // the extended header from omega tar.
+ [ "5061784865616465722fcea92e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000303030363434200030353737363120003030303032342000303030303030303031373020313135343337313036313120303135303531002078000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [...]
+ , { cksumValid: true
+ , path: 'PaxHeader/Ω.txt'
+ , mode: 420
+ , uid: 24561
+ , gid: 20
+ , size: 120
+ , mtime: 1301254537
+ , cksum: 6697
+ , type: 'x'
+ , linkpath: ''
+ , ustar: 'ustar\0'
+ , ustarver: '00'
+ , uname: 'isaacs'
+ , gname: 'staff'
+ , devmaj: 0
+ , devmin: 0
+ , fill: '' } ]
+
+ , "omega file header":
+ [ "cea92e74787400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000303030363434200030353737363120003030303032342000303030303030303030303220313135343337313036313120303133303732002030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [...]
+ , { cksumValid: true
+ , path: 'Ω.txt'
+ , mode: 420
+ , uid: 24561
+ , gid: 20
+ , size: 2
+ , mtime: 1301254537
+ , cksum: 5690
+ , type: '0'
+ , linkpath: ''
+ , ustar: 'ustar\0'
+ , ustarver: '00'
+ , uname: 'isaacs'
+ , gname: 'staff'
+ , devmaj: 0
+ , devmin: 0
+ , fill: '' } ]
+
+ , "foo.js file header":
+ [ "666f6f2e6a7300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000303030363434200030353737363120003030303032342000303030303030303030303420313135343336373037343120303132363137002030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [...]
+ , { cksumValid: true
+ , path: 'foo.js'
+ , mode: 420
+ , uid: 24561
+ , gid: 20
+ , size: 4
+ , mtime: 1301246433
+ , cksum: 5519
+ , type: '0'
+ , linkpath: ''
+ , ustar: 'ustar\0'
+ , ustarver: '00'
+ , uname: 'isaacs'
+ , gname: 'staff'
+ , devmaj: 0
+ , devmin: 0
+ , fill: '' }
+ ]
+
+ , "b.txt file header":
+ [ "622e7478740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000303030363434200030353737363120003030303032342000303030303030303130303020313136353133363036373720303132343631002030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [...]
+ , { cksumValid: true
+ , path: 'b.txt'
+ , mode: 420
+ , uid: 24561
+ , gid: 20
+ , size: 512
+ , mtime: 1319494079
+ , cksum: 5425
+ , type: '0'
+ , linkpath: ''
+ , ustar: 'ustar\0'
+ , ustarver: '00'
+ , uname: 'isaacs'
+ , gname: 'staff'
+ , devmaj: 0
+ , devmin: 0
+ , fill: '' }
+ ]
+
+ , "deep nested file":
+ [ "63636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363303030363434200030353737363120003030303032342000303030303030303031343420313136353231353135333320303433333134002030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [...]
+ , { cksumValid: true,
+ path: 'r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
+ , mode: 420
+ , uid: 24561
+ , gid: 20
+ , size: 100
+ , mtime: 1319687003
+ , cksum: 18124
+ , type: '0'
+ , linkpath: ''
+ , ustar: 'ustar\0'
+ , ustarver: '00'
+ , uname: 'isaacs'
+ , gname: 'staff'
+ , devmaj: 0
+ , devmin: 0
+ , fill: '' }
+ ]
+ }
+
+tap.test("parsing", function (t) {
+ Object.keys(headers).forEach(function (name) {
+ var h = headers[name]
+ , header = new Buffer(h[0], "hex")
+ , expect = h[1]
+ , parsed = new TarHeader(header)
+
+ // console.error(parsed)
+ t.has(parsed, expect, "parse " + name)
+ })
+ t.end()
+})
+
+tap.test("encoding", function (t) {
+ Object.keys(headers).forEach(function (name) {
+ var h = headers[name]
+ , expect = new Buffer(h[0], "hex")
+ , encoded = TarHeader.encode(h[1])
+
+ // might have slightly different bytes, since the standard
+ // isn't very strict, but should have the same semantics
+ // checkSum will be different, but cksumValid will be true
+
+ var th = new TarHeader(encoded)
+ delete h[1].block
+ delete h[1].needExtended
+ delete h[1].cksum
+ t.has(th, h[1], "fields "+name)
+ })
+ t.end()
+})
+
+// test these manually. they're a bit rare to find in the wild
+tap.test("parseNumeric tests", function (t) {
+ var parseNumeric = TarHeader.parseNumeric
+ , numbers =
+ { "303737373737373700": 2097151
+ , "30373737373737373737373700": 8589934591
+ , "303030303036343400": 420
+ , "800000ffffffffffff": 281474976710655
+ , "ffffff000000000001": -281474976710654
+ , "ffffff000000000000": -281474976710655
+ , "800000000000200000": 2097152
+ , "8000000000001544c5": 1393861
+ , "ffffffffffff1544c5": -15383354 }
+ Object.keys(numbers).forEach(function (n) {
+ var b = new Buffer(n, "hex")
+ t.equal(parseNumeric(b), numbers[n], n + " === " + numbers[n])
+ })
+ t.end()
+})
diff --git a/test/pack-no-proprietary.js b/test/pack-no-proprietary.js
new file mode 100644
index 0000000..d4b03a1
--- /dev/null
+++ b/test/pack-no-proprietary.js
@@ -0,0 +1,886 @@
+// This is exactly like test/pack.js, except that it's excluding
+// any proprietary headers.
+//
+// This loses some information about the filesystem, but creates
+// tarballs that are supported by more versions of tar, especially
+// old non-spec-compliant copies of gnutar.
+
+// the symlink file is excluded from git, because it makes
+// windows freak the hell out.
+var fs = require("fs")
+ , path = require("path")
+ , symlink = path.resolve(__dirname, "fixtures/symlink")
+try { fs.unlinkSync(symlink) } catch (e) {}
+fs.symlinkSync("./hardlink-1", symlink)
+process.on("exit", function () {
+ fs.unlinkSync(symlink)
+})
+
+var tap = require("tap")
+ , tar = require("../tar.js")
+ , pkg = require("../package.json")
+ , Pack = tar.Pack
+ , fstream = require("fstream")
+ , Reader = fstream.Reader
+ , Writer = fstream.Writer
+ , input = path.resolve(__dirname, "fixtures/")
+ , target = path.resolve(__dirname, "tmp/pack.tar")
+ , uid = process.getuid ? process.getuid() : 0
+ , gid = process.getgid ? process.getgid() : 0
+
+ , entries =
+
+ // the global header and root fixtures/ dir are going to get
+ // a different date each time, so omit that bit.
+ // Also, dev/ino values differ across machines, so that's not
+ // included.
+ [ [ 'entry',
+ { path: 'fixtures/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'extendedHeader',
+ { path: 'PaxHeader/fixtures/200cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ uid: uid,
+ gid: gid,
+ size: 200 } ]
+
+ , [ 'entry',
+ { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 200,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/a.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 257,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/b.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 512,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/c.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 513,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/cc.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 513,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/dir/',
+ mode: 488,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/dir/sub/',
+ mode: 488,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/foo.js',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 4,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/hardlink-1',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 200,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/hardlink-2',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '1',
+ linkpath: 'fixtures/hardlink-1',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/omega.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/omega.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/star.4.html',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 54081,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'extendedHeader',
+ { path: 'PaxHeader/fixtures/packtest/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: 'fixtures/packtest/Ω.txt',
+ uid: uid,
+ gid: gid,
+ size: 2 } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 100,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/symlink',
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '2',
+ linkpath: 'hardlink-1',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'extendedHeader',
+ { path: 'PaxHeader/fixtures/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: "fixtures/Ω.txt"
+ , uid: uid
+ , gid: gid
+ , size: 2 } ]
+
+ , [ 'entry',
+ { path: 'fixtures/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+ ]
+
+
+// first, make sure that the hardlinks are actually hardlinks, or this
+// won't work. Git has a way of replacing them with a copy.
+var hard1 = path.resolve(__dirname, "fixtures/hardlink-1")
+ , hard2 = path.resolve(__dirname, "fixtures/hardlink-2")
+ , fs = require("fs")
+
+try { fs.unlinkSync(hard2) } catch (e) {}
+fs.linkSync(hard1, hard2)
+
+tap.test("with global header", { timeout: 10000 }, function (t) {
+ runTest(t, true)
+})
+
+tap.test("without global header", { timeout: 10000 }, function (t) {
+ runTest(t, false)
+})
+
+function alphasort (a, b) {
+ return a === b ? 0
+ : a.toLowerCase() > b.toLowerCase() ? 1
+ : a.toLowerCase() < b.toLowerCase() ? -1
+ : a > b ? 1
+ : -1
+}
+
+
+function runTest (t, doGH) {
+ var reader = Reader({ path: input
+ , filter: function () {
+ return !this.path.match(/\.(tar|hex)$/)
+ }
+ , sort: alphasort
+ })
+
+ var props = doGH ? pkg : {}
+ props.noProprietary = true
+ var pack = Pack(props)
+ var writer = Writer(target)
+
+ // global header should be skipped regardless, since it has no content.
+ var entry = 0
+
+ t.ok(reader, "reader ok")
+ t.ok(pack, "pack ok")
+ t.ok(writer, "writer ok")
+
+ pack.pipe(writer)
+
+ var parse = tar.Parse()
+ t.ok(parse, "parser should be ok")
+
+ pack.on("data", function (c) {
+ // console.error("PACK DATA")
+ if (c.length !== 512) {
+ // this one is too noisy, only assert if it'll be relevant
+ t.equal(c.length, 512, "parser should emit data in 512byte blocks")
+ }
+ parse.write(c)
+ })
+
+ pack.on("end", function () {
+ // console.error("PACK END")
+ t.pass("parser ends")
+ parse.end()
+ })
+
+ pack.on("error", function (er) {
+ t.fail("pack error", er)
+ })
+
+ parse.on("error", function (er) {
+ t.fail("parse error", er)
+ })
+
+ writer.on("error", function (er) {
+ t.fail("writer error", er)
+ })
+
+ reader.on("error", function (er) {
+ t.fail("reader error", er)
+ })
+
+ parse.on("*", function (ev, e) {
+ var wanted = entries[entry++]
+ if (!wanted) {
+ t.fail("unexpected event: "+ev)
+ return
+ }
+ t.equal(ev, wanted[0], "event type should be "+wanted[0])
+
+ if (ev !== wanted[0] || e.path !== wanted[1].path) {
+ console.error("wanted", wanted)
+ console.error([ev, e.props])
+ e.on("end", function () {
+ console.error(e.fields)
+ throw "break"
+ })
+ }
+
+ t.has(e.props, wanted[1], "properties "+wanted[1].path)
+ if (wanted[2]) {
+ e.on("end", function () {
+ if (!e.fields) {
+ t.ok(e.fields, "should get fields")
+ } else {
+ t.has(e.fields, wanted[2], "should get expected fields")
+ }
+ })
+ }
+ })
+
+ reader.pipe(pack)
+
+ writer.on("close", function () {
+ t.equal(entry, entries.length, "should get all expected entries")
+ t.pass("it finished")
+ t.end()
+ })
+
+}
diff --git a/test/pack.js b/test/pack.js
new file mode 100644
index 0000000..bf033c1
--- /dev/null
+++ b/test/pack.js
@@ -0,0 +1,934 @@
+
+// the symlink file is excluded from git, because it makes
+// windows freak the hell out.
+var fs = require("fs")
+ , path = require("path")
+ , symlink = path.resolve(__dirname, "fixtures/symlink")
+try { fs.unlinkSync(symlink) } catch (e) {}
+fs.symlinkSync("./hardlink-1", symlink)
+process.on("exit", function () {
+ fs.unlinkSync(symlink)
+})
+
+
+var tap = require("tap")
+ , tar = require("../tar.js")
+ , pkg = require("../package.json")
+ , Pack = tar.Pack
+ , fstream = require("fstream")
+ , Reader = fstream.Reader
+ , Writer = fstream.Writer
+ , input = path.resolve(__dirname, "fixtures/")
+ , target = path.resolve(__dirname, "tmp/pack.tar")
+ , uid = process.getuid ? process.getuid() : 0
+ , gid = process.getgid ? process.getgid() : 0
+
+ , entries =
+
+ // the global header and root fixtures/ dir are going to get
+ // a different date each time, so omit that bit.
+ // Also, dev/ino values differ across machines, so that's not
+ // included.
+ [ [ 'globalExtendedHeader',
+ { path: 'PaxHeader/',
+ mode: 438,
+ uid: 0,
+ gid: 0,
+ type: 'g',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { "NODETAR.author": pkg.author,
+ "NODETAR.name": pkg.name,
+ "NODETAR.description": pkg.description,
+ "NODETAR.version": pkg.version,
+ "NODETAR.repository.type": pkg.repository.type,
+ "NODETAR.repository.url": pkg.repository.url,
+ "NODETAR.main": pkg.main,
+ "NODETAR.scripts.test": pkg.scripts.test } ]
+
+ , [ 'entry',
+ { path: 'fixtures/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'extendedHeader',
+ { path: 'PaxHeader/fixtures/200cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ 'NODETAR.depth': '1',
+ 'NODETAR.type': 'File',
+ nlink: 1,
+ uid: uid,
+ gid: gid,
+ size: 200,
+ 'NODETAR.blksize': '4096',
+ 'NODETAR.blocks': '8' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 200,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '',
+ 'NODETAR.depth': '1',
+ 'NODETAR.type': 'File',
+ nlink: 1,
+ 'NODETAR.blksize': '4096',
+ 'NODETAR.blocks': '8' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/a.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 257,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/b.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 512,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/c.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 513,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/cc.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 513,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/dir/',
+ mode: 488,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/dir/sub/',
+ mode: 488,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+
+ , [ 'entry',
+ { path: 'fixtures/foo.js',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 4,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/hardlink-1',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 200,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/hardlink-2',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '1',
+ linkpath: 'fixtures/hardlink-1',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/omega.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/omega.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/star.4.html',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 54081,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'extendedHeader',
+ { path: 'PaxHeader/fixtures/packtest/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: 'fixtures/packtest/Ω.txt',
+ 'NODETAR.depth': '2',
+ 'NODETAR.type': 'File',
+ nlink: 1,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ 'NODETAR.blksize': '4096',
+ 'NODETAR.blocks': '8' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/packtest/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '',
+ 'NODETAR.depth': '2',
+ 'NODETAR.type': 'File',
+ nlink: 1,
+ 'NODETAR.blksize': '4096',
+ 'NODETAR.blocks': '8' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/',
+ mode: 493,
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '5',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 100,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'entry',
+ { path: 'fixtures/symlink',
+ uid: uid,
+ gid: gid,
+ size: 0,
+ type: '2',
+ linkpath: 'hardlink-1',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' } ]
+
+ , [ 'extendedHeader',
+ { path: 'PaxHeader/fixtures/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: "fixtures/Ω.txt"
+ , "NODETAR.depth": "1"
+ , "NODETAR.type": "File"
+ , nlink: 1
+ , uid: uid
+ , gid: gid
+ , size: 2
+ , "NODETAR.blksize": "4096"
+ , "NODETAR.blocks": "8" } ]
+
+ , [ 'entry',
+ { path: 'fixtures/Ω.txt',
+ mode: 420,
+ uid: uid,
+ gid: gid,
+ size: 2,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\u0000',
+ ustarver: '00',
+ uname: '',
+ gname: '',
+ devmaj: 0,
+ devmin: 0,
+ fill: '',
+ 'NODETAR.depth': '1',
+ 'NODETAR.type': 'File',
+ nlink: 1,
+ 'NODETAR.blksize': '4096',
+ 'NODETAR.blocks': '8' } ]
+ ]
+
+
+// first, make sure that the hardlinks are actually hardlinks, or this
+// won't work. Git has a way of replacing them with a copy.
+var hard1 = path.resolve(__dirname, "fixtures/hardlink-1")
+ , hard2 = path.resolve(__dirname, "fixtures/hardlink-2")
+ , fs = require("fs")
+
+try { fs.unlinkSync(hard2) } catch (e) {}
+fs.linkSync(hard1, hard2)
+
+tap.test("with global header", { timeout: 10000 }, function (t) {
+ runTest(t, true)
+})
+
+tap.test("without global header", { timeout: 10000 }, function (t) {
+ runTest(t, false)
+})
+
+function alphasort (a, b) {
+ return a === b ? 0
+ : a.toLowerCase() > b.toLowerCase() ? 1
+ : a.toLowerCase() < b.toLowerCase() ? -1
+ : a > b ? 1
+ : -1
+}
+
+
+function runTest (t, doGH) {
+ var reader = Reader({ path: input
+ , filter: function () {
+ return !this.path.match(/\.(tar|hex)$/)
+ }
+ , sort: alphasort
+ })
+
+ var pack = Pack(doGH ? pkg : null)
+ var writer = Writer(target)
+
+ // skip the global header if we're not doing that.
+ var entry = doGH ? 0 : 1
+
+ t.ok(reader, "reader ok")
+ t.ok(pack, "pack ok")
+ t.ok(writer, "writer ok")
+
+ pack.pipe(writer)
+
+ var parse = tar.Parse()
+ t.ok(parse, "parser should be ok")
+
+ pack.on("data", function (c) {
+ // console.error("PACK DATA")
+ if (c.length !== 512) {
+ // this one is too noisy, only assert if it'll be relevant
+ t.equal(c.length, 512, "parser should emit data in 512byte blocks")
+ }
+ parse.write(c)
+ })
+
+ pack.on("end", function () {
+ // console.error("PACK END")
+ t.pass("parser ends")
+ parse.end()
+ })
+
+ pack.on("error", function (er) {
+ t.fail("pack error", er)
+ })
+
+ parse.on("error", function (er) {
+ t.fail("parse error", er)
+ })
+
+ writer.on("error", function (er) {
+ t.fail("writer error", er)
+ })
+
+ reader.on("error", function (er) {
+ t.fail("reader error", er)
+ })
+
+ parse.on("*", function (ev, e) {
+ var wanted = entries[entry++]
+ if (!wanted) {
+ t.fail("unexpected event: "+ev)
+ return
+ }
+ t.equal(ev, wanted[0], "event type should be "+wanted[0])
+
+ if (ev !== wanted[0] || e.path !== wanted[1].path) {
+ console.error("wanted", wanted)
+ console.error([ev, e.props])
+ e.on("end", function () {
+ console.error(e.fields)
+ throw "break"
+ })
+ }
+
+
+ t.has(e.props, wanted[1], "properties "+wanted[1].path)
+ if (wanted[2]) {
+ e.on("end", function () {
+ if (!e.fields) {
+ t.ok(e.fields, "should get fields")
+ } else {
+ t.has(e.fields, wanted[2], "should get expected fields")
+ }
+ })
+ }
+ })
+
+ reader.pipe(pack)
+
+ writer.on("close", function () {
+ t.equal(entry, entries.length, "should get all expected entries")
+ t.pass("it finished")
+ t.end()
+ })
+
+}
diff --git a/test/parse.js b/test/parse.js
new file mode 100644
index 0000000..f765a50
--- /dev/null
+++ b/test/parse.js
@@ -0,0 +1,359 @@
+var tap = require("tap")
+ , tar = require("../tar.js")
+ , fs = require("fs")
+ , path = require("path")
+ , file = path.resolve(__dirname, "fixtures/c.tar")
+ , index = 0
+
+ , expect =
+[ [ 'entry',
+ { path: 'c.txt',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 513,
+ mtime: new Date('Wed, 26 Oct 2011 01:10:58 GMT'),
+ cksum: 5422,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ undefined ],
+ [ 'entry',
+ { path: 'cc.txt',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 513,
+ mtime: new Date('Wed, 26 Oct 2011 01:11:02 GMT'),
+ cksum: 5525,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ undefined ],
+ [ 'entry',
+ { path: 'r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 100,
+ mtime: new Date('Thu, 27 Oct 2011 03:43:23 GMT'),
+ cksum: 18124,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ undefined ],
+ [ 'entry',
+ { path: 'Ω.txt',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 2,
+ mtime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'),
+ cksum: 5695,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ undefined ],
+ [ 'extendedHeader',
+ { path: 'PaxHeader/Ω.txt',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 120,
+ mtime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'),
+ cksum: 6702,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: 'Ω.txt',
+ ctime: 1319737909,
+ atime: 1319739061,
+ dev: 234881026,
+ ino: 51693379,
+ nlink: 1 } ],
+ [ 'entry',
+ { path: 'Ω.txt',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 2,
+ mtime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'),
+ cksum: 5695,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '',
+ ctime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'),
+ atime: new Date('Thu, 27 Oct 2011 18:11:01 GMT'),
+ dev: 234881026,
+ ino: 51693379,
+ nlink: 1 },
+ undefined ],
+ [ 'extendedHeader',
+ { path: 'PaxHeader/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 353,
+ mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'),
+ cksum: 14488,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ ctime: 1319686868,
+ atime: 1319741254,
+ 'LIBARCHIVE.creationtime': '1319686852',
+ dev: 234881026,
+ ino: 51681874,
+ nlink: 1 } ],
+ [ 'entry',
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 200,
+ mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'),
+ cksum: 14570,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '',
+ ctime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'),
+ atime: new Date('Thu, 27 Oct 2011 18:47:34 GMT'),
+ 'LIBARCHIVE.creationtime': '1319686852',
+ dev: 234881026,
+ ino: 51681874,
+ nlink: 1 },
+ undefined ],
+ [ 'longPath',
+ { path: '././@LongLink',
+ mode: 0,
+ uid: 0,
+ gid: 0,
+ size: 201,
+ mtime: new Date('Thu, 01 Jan 1970 00:00:00 GMT'),
+ cksum: 4976,
+ type: 'L',
+ linkpath: '',
+ ustar: false },
+ '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc' ],
+ [ 'entry',
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: 1000,
+ gid: 1000,
+ size: 201,
+ mtime: new Date('Thu, 27 Oct 2011 22:21:50 GMT'),
+ cksum: 14086,
+ type: '0',
+ linkpath: '',
+ ustar: false },
+ undefined ],
+ [ 'longLinkpath',
+ { path: '././@LongLink',
+ mode: 0,
+ uid: 0,
+ gid: 0,
+ size: 201,
+ mtime: new Date('Thu, 01 Jan 1970 00:00:00 GMT'),
+ cksum: 4975,
+ type: 'K',
+ linkpath: '',
+ ustar: false },
+ '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc' ],
+ [ 'longPath',
+ { path: '././@LongLink',
+ mode: 0,
+ uid: 0,
+ gid: 0,
+ size: 201,
+ mtime: new Date('Thu, 01 Jan 1970 00:00:00 GMT'),
+ cksum: 4976,
+ type: 'L',
+ linkpath: '',
+ ustar: false },
+ '200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL' ],
+ [ 'entry',
+ { path: '200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL',
+ mode: 511,
+ uid: 1000,
+ gid: 1000,
+ size: 0,
+ mtime: new Date('Fri, 28 Oct 2011 23:05:17 GMT'),
+ cksum: 21603,
+ type: '2',
+ linkpath: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ ustar: false },
+ undefined ],
+ [ 'extendedHeader',
+ { path: 'PaxHeader/200-hard',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 143,
+ mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'),
+ cksum: 6533,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { ctime: 1320617144,
+ atime: 1320617232,
+ 'LIBARCHIVE.creationtime': '1319686852',
+ dev: 234881026,
+ ino: 51681874,
+ nlink: 2 } ],
+ [ 'entry',
+ { path: '200-hard',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 200,
+ mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'),
+ cksum: 5526,
+ type: '0',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '',
+ ctime: new Date('Sun, 06 Nov 2011 22:05:44 GMT'),
+ atime: new Date('Sun, 06 Nov 2011 22:07:12 GMT'),
+ 'LIBARCHIVE.creationtime': '1319686852',
+ dev: 234881026,
+ ino: 51681874,
+ nlink: 2 },
+ undefined ],
+ [ 'extendedHeader',
+ { path: 'PaxHeader/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 353,
+ mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'),
+ cksum: 14488,
+ type: 'x',
+ linkpath: '',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '' },
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ ctime: 1320617144,
+ atime: 1320617406,
+ 'LIBARCHIVE.creationtime': '1319686852',
+ dev: 234881026,
+ ino: 51681874,
+ nlink: 2 } ],
+ [ 'entry',
+ { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
+ mode: 420,
+ uid: 24561,
+ gid: 20,
+ size: 0,
+ mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'),
+ cksum: 15173,
+ type: '1',
+ linkpath: '200-hard',
+ ustar: 'ustar\0',
+ ustarver: '00',
+ uname: 'isaacs',
+ gname: 'staff',
+ devmaj: 0,
+ devmin: 0,
+ fill: '',
+ ctime: new Date('Sun, 06 Nov 2011 22:05:44 GMT'),
+ atime: new Date('Sun, 06 Nov 2011 22:10:06 GMT'),
+ 'LIBARCHIVE.creationtime': '1319686852',
+ dev: 234881026,
+ ino: 51681874,
+ nlink: 2 },
+ undefined ] ]
+
+
+tap.test("parser test", function (t) {
+ var parser = tar.Parse()
+
+ parser.on("end", function () {
+ t.equal(index, expect.length, "saw all expected events")
+ t.end()
+ })
+
+ fs.createReadStream(file)
+ .pipe(parser)
+ .on("*", function (ev, entry) {
+ var wanted = expect[index]
+ if (!wanted) {
+ return t.fail("Unexpected event: " + ev)
+ }
+ var result = [ev, entry.props]
+ entry.on("end", function () {
+ result.push(entry.fields || entry.body)
+
+ t.equal(ev, wanted[0], index + " event type")
+ t.equivalent(entry.props, wanted[1], wanted[1].path + " entry properties")
+ if (wanted[2]) {
+ t.equivalent(result[2], wanted[2], "metadata values")
+ }
+ index ++
+ })
+ })
+})
diff --git a/test/zz-cleanup.js b/test/zz-cleanup.js
new file mode 100644
index 0000000..a00ff7f
--- /dev/null
+++ b/test/zz-cleanup.js
@@ -0,0 +1,20 @@
+// clean up the fixtures
+
+var tap = require("tap")
+, rimraf = require("rimraf")
+, test = tap.test
+, path = require("path")
+
+test("clean fixtures", function (t) {
+ rimraf(path.resolve(__dirname, "fixtures"), function (er) {
+ t.ifError(er, "rimraf ./fixtures/")
+ t.end()
+ })
+})
+
+test("clean tmp", function (t) {
+ rimraf(path.resolve(__dirname, "tmp"), function (er) {
+ t.ifError(er, "rimraf ./tmp/")
+ t.end()
+ })
+})
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-tar.git
More information about the Pkg-javascript-commits
mailing list