Improve gnu/oldgnu format support

- gnu/oldgnu formats do not have a prefix field.
- Explicitly validate magic and version when parsing. Throw an exception
if unsupported/invalid magic is encountered.
This commit is contained in:
Mike Ryan 2019-05-30 15:29:05 -05:00 committed by Mathias Buus
parent b6392bdc8a
commit 6dcda69247
6 changed files with 118 additions and 7 deletions

View file

@ -3,8 +3,13 @@ var alloc = Buffer.alloc
var ZEROS = '0000000000000000000' var ZEROS = '0000000000000000000'
var SEVENS = '7777777777777777777' var SEVENS = '7777777777777777777'
var ZERO_OFFSET = '0'.charCodeAt(0) 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 MASK = parseInt('7777', 8)
var MAGIC_OFFSET = 257
var VERSION_OFFSET = 263
var clamp = function (index, len, defaultValue) { var clamp = function (index, len, defaultValue) {
if (typeof index !== 'number') return defaultValue if (typeof index !== 'number') return defaultValue
@ -223,7 +228,8 @@ exports.encode = function (opts) {
if (opts.linkname) buf.write(opts.linkname, 157) 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.uname) buf.write(opts.uname, 265)
if (opts.gname) buf.write(opts.gname, 297) if (opts.gname) buf.write(opts.gname, 297)
buf.write(encodeOct(opts.devmajor || 0, 6), 329) buf.write(encodeOct(opts.devmajor || 0, 6), 329)
@ -252,11 +258,6 @@ exports.decode = function (buf, filenameEncoding) {
var devmajor = decodeOct(buf, 329, 8) var devmajor = decodeOct(buf, 329, 8)
var devminor = decodeOct(buf, 337, 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) var c = cksum(buf)
// checksum is still initial value if header was null. // checksum is still initial value if header was null.
@ -265,6 +266,21 @@ exports.decode = function (buf, filenameEncoding) {
// valid checksum // 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 (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 { return {
name: name, name: name,
mode: mode, mode: mode,

View file

@ -590,3 +590,92 @@ test('incomplete', function (t) {
extract.end(fs.readFileSync(fixtures.INCOMPLETE_TAR)) 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))
})

BIN
test/fixtures/gnu-incremental.tar vendored Normal file

Binary file not shown.

BIN
test/fixtures/gnu.tar vendored Normal file

Binary file not shown.

View file

@ -17,3 +17,9 @@ exports.BASE_256_SIZE = path.join(__dirname, 'base-256-size.tar')
exports.HUGE = path.join(__dirname, 'huge.tar.gz') exports.HUGE = path.join(__dirname, 'huge.tar.gz')
exports.LATIN1_TAR = path.join(__dirname, 'latin1.tar') exports.LATIN1_TAR = path.join(__dirname, 'latin1.tar')
exports.INCOMPLETE_TAR = path.join(__dirname, 'incomplete.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')

BIN
test/fixtures/v7.tar vendored Normal file

Binary file not shown.