| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 | 'use strict'// parse a 512-byte header block to a data object, or vice-versa// encode returns `true` if a pax extended header is needed, because// the data could not be faithfully encoded in a simple header.// (Also, check header.needPax to see if it needs a pax header.)const types = require('./types.js')const pathModule = require('path').posixconst large = require('./large-numbers.js')const SLURP = Symbol('slurp')const TYPE = Symbol('type')class Header {  constructor (data, off, ex, gex) {    this.cksumValid = false    this.needPax = false    this.nullBlock = false    this.block = null    this.path = null    this.mode = null    this.uid = null    this.gid = null    this.size = null    this.mtime = null    this.cksum = null    this[TYPE] = '0'    this.linkpath = null    this.uname = null    this.gname = null    this.devmaj = 0    this.devmin = 0    this.atime = null    this.ctime = null    if (Buffer.isBuffer(data))      this.decode(data, off || 0, ex, gex)    else if (data)      this.set(data)  }  decode (buf, off, ex, gex) {    if (!off)      off = 0    if (!buf || !(buf.length >= off + 512))      throw new Error('need 512 bytes for header')    this.path = decString(buf, off, 100)    this.mode = decNumber(buf, off + 100, 8)    this.uid = decNumber(buf, off + 108, 8)    this.gid = decNumber(buf, off + 116, 8)    this.size = decNumber(buf, off + 124, 12)    this.mtime = decDate(buf, off + 136, 12)    this.cksum = decNumber(buf, off + 148, 12)    // if we have extended or global extended headers, apply them now    // See https://github.com/npm/node-tar/pull/187    this[SLURP](ex)    this[SLURP](gex, true)    // old tar versions marked dirs as a file with a trailing /    this[TYPE] = decString(buf, off + 156, 1)    if (this[TYPE] === '')      this[TYPE] = '0'    if (this[TYPE] === '0' && this.path.substr(-1) === '/')      this[TYPE] = '5'    // tar implementations sometimes incorrectly put the stat(dir).size    // as the size in the tarball, even though Directory entries are    // not able to have any body at all.  In the very rare chance that    // it actually DOES have a body, we weren't going to do anything with    // it anyway, and it'll just be a warning about an invalid header.    if (this[TYPE] === '5')      this.size = 0    this.linkpath = decString(buf, off + 157, 100)    if (buf.slice(off + 257, off + 265).toString() === 'ustar\u000000') {      this.uname = decString(buf, off + 265, 32)      this.gname = decString(buf, off + 297, 32)      this.devmaj = decNumber(buf, off + 329, 8)      this.devmin = decNumber(buf, off + 337, 8)      if (buf[off + 475] !== 0) {        // definitely a prefix, definitely >130 chars.        const prefix = decString(buf, off + 345, 155)        this.path = prefix + '/' + this.path      } else {        const prefix = decString(buf, off + 345, 130)        if (prefix)          this.path = prefix + '/' + this.path        this.atime = decDate(buf, off + 476, 12)        this.ctime = decDate(buf, off + 488, 12)      }    }    let sum = 8 * 0x20    for (let i = off; i < off + 148; i++)      sum += buf[i]    for (let i = off + 156; i < off + 512; i++)      sum += buf[i]    this.cksumValid = sum === this.cksum    if (this.cksum === null && sum === 8 * 0x20)      this.nullBlock = true  }  [SLURP] (ex, global) {    for (const k in ex) {      // we slurp in everything except for the path attribute in      // a global extended header, because that's weird.      if (ex[k] !== null && ex[k] !== undefined &&          !(global && k === 'path'))        this[k] = ex[k]    }  }  encode (buf, off) {    if (!buf) {      buf = this.block = Buffer.alloc(512)      off = 0    }    if (!off)      off = 0    if (!(buf.length >= off + 512))      throw new Error('need 512 bytes for header')    const prefixSize = this.ctime || this.atime ? 130 : 155    const split = splitPrefix(this.path || '', prefixSize)    const path = split[0]    const prefix = split[1]    this.needPax = split[2]    this.needPax = encString(buf, off, 100, path) || this.needPax    this.needPax = encNumber(buf, off + 100, 8, this.mode) || this.needPax    this.needPax = encNumber(buf, off + 108, 8, this.uid) || this.needPax    this.needPax = encNumber(buf, off + 116, 8, this.gid) || this.needPax    this.needPax = encNumber(buf, off + 124, 12, this.size) || this.needPax    this.needPax = encDate(buf, off + 136, 12, this.mtime) || this.needPax    buf[off + 156] = this[TYPE].charCodeAt(0)    this.needPax = encString(buf, off + 157, 100, this.linkpath) || this.needPax    buf.write('ustar\u000000', off + 257, 8)    this.needPax = encString(buf, off + 265, 32, this.uname) || this.needPax    this.needPax = encString(buf, off + 297, 32, this.gname) || this.needPax    this.needPax = encNumber(buf, off + 329, 8, this.devmaj) || this.needPax    this.needPax = encNumber(buf, off + 337, 8, this.devmin) || this.needPax    this.needPax = encString(buf, off + 345, prefixSize, prefix) || this.needPax    if (buf[off + 475] !== 0)      this.needPax = encString(buf, off + 345, 155, prefix) || this.needPax    else {      this.needPax = encString(buf, off + 345, 130, prefix) || this.needPax      this.needPax = encDate(buf, off + 476, 12, this.atime) || this.needPax      this.needPax = encDate(buf, off + 488, 12, this.ctime) || this.needPax    }    let sum = 8 * 0x20    for (let i = off; i < off + 148; i++)      sum += buf[i]    for (let i = off + 156; i < off + 512; i++)      sum += buf[i]    this.cksum = sum    encNumber(buf, off + 148, 8, this.cksum)    this.cksumValid = true    return this.needPax  }  set (data) {    for (const i in data) {      if (data[i] !== null && data[i] !== undefined)        this[i] = data[i]    }  }  get type () {    return types.name.get(this[TYPE]) || this[TYPE]  }  get typeKey () {    return this[TYPE]  }  set type (type) {    if (types.code.has(type))      this[TYPE] = types.code.get(type)    else      this[TYPE] = type  }}const splitPrefix = (p, prefixSize) => {  const pathSize = 100  let pp = p  let prefix = ''  let ret  const root = pathModule.parse(p).root || '.'  if (Buffer.byteLength(pp) < pathSize)    ret = [pp, prefix, false]  else {    // first set prefix to the dir, and path to the base    prefix = pathModule.dirname(pp)    pp = pathModule.basename(pp)    do {      // both fit!      if (Buffer.byteLength(pp) <= pathSize &&          Buffer.byteLength(prefix) <= prefixSize)        ret = [pp, prefix, false]      // prefix fits in prefix, but path doesn't fit in path      else if (Buffer.byteLength(pp) > pathSize &&          Buffer.byteLength(prefix) <= prefixSize)        ret = [pp.substr(0, pathSize - 1), prefix, true]      else {        // make path take a bit from prefix        pp = pathModule.join(pathModule.basename(prefix), pp)        prefix = pathModule.dirname(prefix)      }    } while (prefix !== root && !ret)    // at this point, found no resolution, just truncate    if (!ret)      ret = [p.substr(0, pathSize - 1), '', true]  }  return ret}const decString = (buf, off, size) =>  buf.slice(off, off + size).toString('utf8').replace(/\0.*/, '')const decDate = (buf, off, size) =>  numToDate(decNumber(buf, off, size))const numToDate = num => num === null ? null : new Date(num * 1000)const decNumber = (buf, off, size) =>  buf[off] & 0x80 ? large.parse(buf.slice(off, off + size))  : decSmallNumber(buf, off, size)const nanNull = value => isNaN(value) ? null : valueconst decSmallNumber = (buf, off, size) =>  nanNull(parseInt(    buf.slice(off, off + size)      .toString('utf8').replace(/\0.*$/, '').trim(), 8))// the maximum encodable as a null-terminated octal, by field sizeconst MAXNUM = {  12: 0o77777777777,  8: 0o7777777,}const encNumber = (buf, off, size, number) =>  number === null ? false :  number > MAXNUM[size] || number < 0    ? (large.encode(number, buf.slice(off, off + size)), true)    : (encSmallNumber(buf, off, size, number), false)const encSmallNumber = (buf, off, size, number) =>  buf.write(octalString(number, size), off, size, 'ascii')const octalString = (number, size) =>  padOctal(Math.floor(number).toString(8), size)const padOctal = (string, size) =>  (string.length === size - 1 ? string  : new Array(size - string.length - 1).join('0') + string + ' ') + '\0'const encDate = (buf, off, size, date) =>  date === null ? false :  encNumber(buf, off, size, date.getTime() / 1000)// enough to fill the longest string we've gotconst NULLS = new Array(156).join('\0')// pad with nulls, return true if it's longer or non-asciiconst encString = (buf, off, size, string) =>  string === null ? false :  (buf.write(string + NULLS, off, size, 'utf8'),  string.length !== Buffer.byteLength(string) || string.length > size)module.exports = Header
 |