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:
parent
b6392bdc8a
commit
6dcda69247
6 changed files with 118 additions and 7 deletions
30
headers.js
30
headers.js
|
@ -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,
|
||||||
|
|
|
@ -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
BIN
test/fixtures/gnu-incremental.tar
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/gnu.tar
vendored
Normal file
BIN
test/fixtures/gnu.tar
vendored
Normal file
Binary file not shown.
6
test/fixtures/index.js
vendored
6
test/fixtures/index.js
vendored
|
@ -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
BIN
test/fixtures/v7.tar
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue