Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve gnu/oldgnu format support #105

Merged
merged 1 commit into from Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 23 additions & 7 deletions headers.js
Expand Up @@ -3,8 +3,13 @@ var alloc = Buffer.alloc
var ZEROS = '0000000000000000000'
var SEVENS = '7777777777777777777'
var ZERO_OFFSET = '0'.charCodeAt(0)
var USTAR = 'ustar\x0000'
var USTAR_MAGIC = Buffer.from('ustar\x00', 'binary')
var USTAR_VER = Buffer.from('00', 'binary')
var GNU_MAGIC = Buffer.from('ustar\x20', 'binary')
var GNU_VER = Buffer.from('\x20\x00', 'binary')
var MASK = parseInt('7777', 8)
var MAGIC_OFFSET = 257
var VERSION_OFFSET = 263

var clamp = function (index, len, defaultValue) {
if (typeof index !== 'number') return defaultValue
Expand Down Expand Up @@ -223,7 +228,8 @@ exports.encode = function (opts) {

if (opts.linkname) buf.write(opts.linkname, 157)

buf.write(USTAR, 257)
USTAR_MAGIC.copy(buf, MAGIC_OFFSET)
USTAR_VER.copy(buf, VERSION_OFFSET)
if (opts.uname) buf.write(opts.uname, 265)
if (opts.gname) buf.write(opts.gname, 297)
buf.write(encodeOct(opts.devmajor || 0, 6), 329)
Expand Down Expand Up @@ -252,11 +258,6 @@ exports.decode = function (buf, filenameEncoding) {
var devmajor = decodeOct(buf, 329, 8)
var devminor = decodeOct(buf, 337, 8)

if (buf[345]) name = decodeStr(buf, 345, 155, filenameEncoding) + '/' + name

// to support old tar versions that use trailing / to indicate dirs
if (typeflag === 0 && name && name[name.length - 1] === '/') typeflag = 5

var c = cksum(buf)

// checksum is still initial value if header was null.
Expand All @@ -265,6 +266,21 @@ exports.decode = function (buf, filenameEncoding) {
// valid checksum
if (c !== decodeOct(buf, 148, 8)) throw new Error('Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?')

if (USTAR_MAGIC.compare(buf, MAGIC_OFFSET, MAGIC_OFFSET + 6) === 0) {
// ustar (posix) format.
// prepend prefix, if present.
if (buf[345]) name = decodeStr(buf, 345, 155, filenameEncoding) + '/' + name
} else if (GNU_MAGIC.compare(buf, MAGIC_OFFSET, MAGIC_OFFSET + 6) === 0 &&
GNU_VER.compare(buf, VERSION_OFFSET, VERSION_OFFSET + 2) === 0) {
// 'gnu'/'oldgnu' format. Similar to ustar, but has support for incremental and
// multi-volume tarballs.
} else {
throw new Error('Invalid tar header: unknown format.')
}

// to support old tar versions that use trailing / to indicate dirs
if (typeflag === 0 && name && name[name.length - 1] === '/') typeflag = 5

return {
name: name,
mode: mode,
Expand Down
89 changes: 89 additions & 0 deletions test/extract.js
Expand Up @@ -590,3 +590,92 @@ test('incomplete', function (t) {

extract.end(fs.readFileSync(fixtures.INCOMPLETE_TAR))
})

test('gnu', function (t) { // can correctly unpack gnu-tar format
t.plan(3)

var extract = tar.extract()
var noEntries = false

extract.on('entry', function (header, stream, callback) {
t.deepEqual(header, {
name: 'test.txt',
mode: parseInt('644', 8),
uid: 12345,
gid: 67890,
size: 14,
mtime: new Date(1559239869000),
type: 'file',
linkname: null,
uname: 'myuser',
gname: 'mygroup',
devmajor: 0,
devminor: 0
})

stream.pipe(concat(function (data) {
noEntries = true
t.same(data.toString(), 'Hello, world!\n')
callback()
}))
})

extract.on('finish', function () {
t.ok(noEntries)
})

extract.end(fs.readFileSync(fixtures.GNU_TAR))
})

test('gnu-incremental', function (t) {
// can correctly unpack gnu-tar incremental format. In this situation,
// the tarball will have additional ctime and atime values in the header,
// and without awareness of the 'gnu' tar format, the atime (offset 345) is mistaken
// for a directory prefix (also offset 345).
t.plan(3)

var extract = tar.extract()
var noEntries = false

extract.on('entry', function (header, stream, callback) {
t.deepEqual(header, {
name: 'test.txt',
mode: parseInt('644', 8),
uid: 12345,
gid: 67890,
size: 14,
mtime: new Date(1559239869000),
type: 'file',
linkname: null,
uname: 'myuser',
gname: 'mygroup',
devmajor: 0,
devminor: 0
})

stream.pipe(concat(function (data) {
noEntries = true
t.same(data.toString(), 'Hello, world!\n')
callback()
}))
})

extract.on('finish', function () {
t.ok(noEntries)
})

extract.end(fs.readFileSync(fixtures.GNU_INCREMENTAL_TAR))
})

test('v7 unsupported', function (t) { // correctly fails to parse v7 tarballs
t.plan(1)

var extract = tar.extract()

extract.on('error', function (err) {
t.ok(!!err)
extract.destroy()
})

extract.end(fs.readFileSync(fixtures.V7_TAR))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats a v7 tar?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, saw it below

})
Binary file added test/fixtures/gnu-incremental.tar
Binary file not shown.
Binary file added test/fixtures/gnu.tar
Binary file not shown.
6 changes: 6 additions & 0 deletions test/fixtures/index.js
Expand Up @@ -17,3 +17,9 @@ exports.BASE_256_SIZE = path.join(__dirname, 'base-256-size.tar')
exports.HUGE = path.join(__dirname, 'huge.tar.gz')
exports.LATIN1_TAR = path.join(__dirname, 'latin1.tar')
exports.INCOMPLETE_TAR = path.join(__dirname, 'incomplete.tar')
// Created using gnu tar: tar cf gnu-incremental.tar --format gnu --owner=myuser:12345 --group=mygroup:67890 test.txt
exports.GNU_TAR = path.join(__dirname, 'gnu.tar')
// Created using gnu tar: tar cf gnu-incremental.tar -G --format gnu --owner=myuser:12345 --group=mygroup:67890 test.txt
exports.GNU_INCREMENTAL_TAR = path.join(__dirname, 'gnu-incremental.tar')
// Created using gnu tar: tar cf v7.tar --format v7 test.txt
exports.V7_TAR = path.join(__dirname, 'v7.tar')
Binary file added test/fixtures/v7.tar
Binary file not shown.