var alloc = Buffer.alloc var ZEROS = '0000000000000000000' var SEVENS = '7777777777777777777' var ZERO_OFFSET = '0'.charCodeAt(0) 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 index = ~~index // Coerce to integer. if (index >= len) return len if (index >= 0) return index index += len if (index >= 0) return index return 0 } var toType = function (flag) { switch (flag) { case 0: return 'file' case 1: return 'link' case 2: return 'symlink' case 3: return 'character-device' case 4: return 'block-device' case 5: return 'directory' case 6: return 'fifo' case 7: return 'contiguous-file' case 72: return 'pax-header' case 55: return 'pax-global-header' case 27: return 'gnu-long-link-path' case 28: case 30: return 'gnu-long-path' } return null } var toTypeflag = function (flag) { switch (flag) { case 'file': return 0 case 'link': return 1 case 'symlink': return 2 case 'character-device': return 3 case 'block-device': return 4 case 'directory': return 5 case 'fifo': return 6 case 'contiguous-file': return 7 case 'pax-header': return 72 } return 0 } var indexOf = function (block, num, offset, end) { for (; offset < end; offset++) { if (block[offset] === num) return offset } return end } var cksum = function (block) { var sum = 8 * 32 for (var i = 0; i < 148; i++) sum += block[i] for (var j = 156; j < 512; j++) sum += block[j] return sum } var encodeOct = function (val, n) { val = val.toString(8) if (val.length > n) return SEVENS.slice(0, n) + ' ' else return ZEROS.slice(0, n - val.length) + val + ' ' } /* Copied from the node-tar repo and modified to meet * tar-stream coding standard. * * Source: https://github.com/npm/node-tar/blob/51b6627a1f357d2eb433e7378e5f05e83b7aa6cd/lib/header.js#L349 */ 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 tuple = [] for (var i = buf.length - 1; i > 0; i--) { var byte = buf[i] if (positive) tuple.push(byte) else tuple.push(0xFF - byte) } var sum = 0 var l = tuple.length for (i = 0; i < l; i++) { sum += tuple[i] * Math.pow(256, i) } return positive ? sum : -1 * sum } var decodeOct = function (val, offset, length) { val = val.slice(offset, offset + length) offset = 0 // If prefixed with 0x80 then parse as a base-256 integer if (val[offset] & 0x80) { return parse256(val) } else { // Older versions of tar can prefix with spaces while (offset < val.length && val[offset] === 32) offset++ var end = clamp(indexOf(val, 32, offset, val.length), val.length, val.length) while (offset < end && val[offset] === 0) offset++ if (end === offset) return 0 return parseInt(val.slice(offset, end).toString(), 8) } } var decodeStr = function (val, offset, length, encoding) { return val.slice(offset, indexOf(val, 0, offset, offset + length)).toString(encoding) } var addLength = function (str) { var len = Buffer.byteLength(str) var digits = Math.floor(Math.log(len) / Math.log(10)) + 1 if (len + digits >= Math.pow(10, digits)) digits++ return (len + digits) + str } exports.decodeLongPath = function (buf, encoding) { return decodeStr(buf, 0, buf.length, encoding) } exports.encodePax = function (opts) { // TODO: encode more stuff in pax var result = '' if (opts.name) result += addLength(' path=' + opts.name + '\n') if (opts.linkname) result += addLength(' linkpath=' + opts.linkname + '\n') var pax = opts.pax if (pax) { for (var key in pax) { result += addLength(' ' + key + '=' + pax[key] + '\n') } } return Buffer.from(result) } exports.decodePax = function (buf) { var result = {} while (buf.length) { var i = 0 while (i < buf.length && buf[i] !== 32) i++ var len = parseInt(buf.slice(0, i).toString(), 10) if (!len) return result var b = buf.slice(i + 1, len - 1).toString() var keyIndex = b.indexOf('=') if (keyIndex === -1) return result result[b.slice(0, keyIndex)] = b.slice(keyIndex + 1) buf = buf.slice(len) } return result } exports.encode = function (opts) { var buf = alloc(512) var name = opts.name var prefix = '' if (opts.typeflag === 5 && name[name.length - 1] !== '/') name += '/' if (Buffer.byteLength(name) !== name.length) return null // utf-8 while (Buffer.byteLength(name) > 100) { var i = name.indexOf('/') if (i === -1) return null prefix += prefix ? '/' + name.slice(0, i) : name.slice(0, i) name = name.slice(i + 1) } if (Buffer.byteLength(name) > 100 || Buffer.byteLength(prefix) > 155) return null if (opts.linkname && Buffer.byteLength(opts.linkname) > 100) return null buf.write(name) buf.write(encodeOct(opts.mode & MASK, 6), 100) buf.write(encodeOct(opts.uid, 6), 108) buf.write(encodeOct(opts.gid, 6), 116) buf.write(encodeOct(opts.size, 11), 124) buf.write(encodeOct((opts.mtime.getTime() / 1000) | 0, 11), 136) buf[156] = ZERO_OFFSET + toTypeflag(opts.type) if (opts.linkname) buf.write(opts.linkname, 157) 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) buf.write(encodeOct(opts.devminor || 0, 6), 337) if (prefix) buf.write(prefix, 345) buf.write(encodeOct(cksum(buf), 6), 148) return buf } exports.decode = function (buf, filenameEncoding, allowUnknownFormat) { var typeflag = buf[156] === 0 ? 0 : buf[156] - ZERO_OFFSET var name = decodeStr(buf, 0, 100, filenameEncoding) var mode = decodeOct(buf, 100, 8) var uid = decodeOct(buf, 108, 8) var gid = decodeOct(buf, 116, 8) var size = decodeOct(buf, 124, 12) var mtime = decodeOct(buf, 136, 12) var type = toType(typeflag) var linkname = buf[157] === 0 ? null : decodeStr(buf, 157, 100, filenameEncoding) var uname = decodeStr(buf, 265, 32) var gname = decodeStr(buf, 297, 32) var devmajor = decodeOct(buf, 329, 8) var devminor = decodeOct(buf, 337, 8) var c = cksum(buf) // checksum is still initial value if header was null. if (c === 8 * 32) return null // 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 { if (!allowUnknownFormat) { 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, mode, uid, gid, size, mtime: new Date(1000 * mtime), type, linkname, uname, gname, devmajor, devminor } }