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, }; };