var ZEROS = '0000000000000000000' var ZERO_OFFSET = '0'.charCodeAt(0) var USTAR = 'ustar\x0000' var MASK = parseInt('7777', 8) 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 alloc = function (size) { var buf = new Buffer(size) buf.fill(0) return buf } 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) return ZEROS.slice(0, n - val.length) + val + ' ' } var decodeOct = function (val, offset) { // 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) { return val.slice(offset, indexOf(val, 0, offset, offset + length)).toString() } 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) { return decodeStr(buf, 0, buf.length) } 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') return new Buffer(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) buf.write(USTAR, 257) 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) { var typeflag = buf[156] === 0 ? 0 : buf[156] - ZERO_OFFSET var name = decodeStr(buf, 0, 100) var mode = decodeOct(buf, 100) var uid = decodeOct(buf, 108) var gid = decodeOct(buf, 116) var size = decodeOct(buf, 124) var mtime = decodeOct(buf, 136) var type = toType(typeflag) var linkname = buf[157] === 0 ? null : decodeStr(buf, 157, 100) var uname = decodeStr(buf, 265, 32) var gname = decodeStr(buf, 297, 32) var devmajor = decodeOct(buf, 329) var devminor = decodeOct(buf, 337) if (buf[345]) name = decodeStr(buf, 345, 155) + '/' + 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. if (c === 8 * 32) return null // valid checksum if (c !== decodeOct(buf, 148)) throw new Error('Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?') return { name: name, mode: mode, uid: uid, gid: gid, size: size, mtime: new Date(1000 * mtime), type: type, linkname: linkname, uname: uname, gname: gname, devmajor: devmajor, devminor: devminor } }