| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 | // parse a 512-byte header block to a data object, or vice-versa// If the data won't fit nicely in a simple header, then generate// the appropriate extended header file, and return that.module.exports = TarHeadervar tar = require("../tar.js")  , fields = tar.fields  , fieldOffs = tar.fieldOffs  , fieldEnds = tar.fieldEnds  , fieldSize = tar.fieldSize  , numeric = tar.numeric  , assert = require("assert").ok  , space = " ".charCodeAt(0)  , slash = "/".charCodeAt(0)  , bslash = process.platform === "win32" ? "\\".charCodeAt(0) : nullfunction TarHeader (block) {  if (!(this instanceof TarHeader)) return new TarHeader(block)  if (block) this.decode(block)}TarHeader.prototype =  { decode : decode  , encode: encode  , calcSum: calcSum  , checkSum: checkSum  }TarHeader.parseNumeric = parseNumericTarHeader.encode = encodeTarHeader.decode = decode// note that this will only do the normal ustar header, not any kind// of extended posix header file.  If something doesn't fit comfortably,// then it will set obj.needExtended = true, and set the block to// the closest approximation.function encode (obj) {  if (!obj && !(this instanceof TarHeader)) throw new Error(    "encode must be called on a TarHeader, or supplied an object")  obj = obj || this  var block = obj.block = new Buffer(512)  // if the object has a "prefix", then that's actually an extension of  // the path field.  if (obj.prefix) {    // console.error("%% header encoding, got a prefix", obj.prefix)    obj.path = obj.prefix + "/" + obj.path    // console.error("%% header encoding, prefixed path", obj.path)    obj.prefix = ""  }  obj.needExtended = false  if (obj.mode) {    if (typeof obj.mode === "string") obj.mode = parseInt(obj.mode, 8)    obj.mode = obj.mode & 0777  }  for (var f = 0; fields[f] !== null; f ++) {    var field = fields[f]      , off = fieldOffs[f]      , end = fieldEnds[f]      , ret    switch (field) {      case "cksum":        // special, done below, after all the others        break      case "prefix":        // special, this is an extension of the "path" field.        // console.error("%% header encoding, skip prefix later")        break      case "type":        // convert from long name to a single char.        var type = obj.type || "0"        if (type.length > 1) {          type = tar.types[obj.type]          if (!type) type = "0"        }        writeText(block, off, end, type)        break      case "path":        // uses the "prefix" field if > 100 bytes, but <= 255        var pathLen = Buffer.byteLength(obj.path)          , pathFSize = fieldSize[fields.path]          , prefFSize = fieldSize[fields.prefix]        // paths between 100 and 255 should use the prefix field.        // longer than 255        if (pathLen > pathFSize &&            pathLen <= pathFSize + prefFSize) {          // need to find a slash somewhere in the middle so that          // path and prefix both fit in their respective fields          var searchStart = pathLen - 1 - pathFSize            , searchEnd = prefFSize            , found = false            , pathBuf = new Buffer(obj.path)          for ( var s = searchStart              ; (s <= searchEnd)              ; s ++ ) {            if (pathBuf[s] === slash || pathBuf[s] === bslash) {              found = s              break            }          }          if (found !== false) {            prefix = pathBuf.slice(0, found).toString("utf8")            path = pathBuf.slice(found + 1).toString("utf8")            ret = writeText(block, off, end, path)            off = fieldOffs[fields.prefix]            end = fieldEnds[fields.prefix]            // console.error("%% header writing prefix", off, end, prefix)            ret = writeText(block, off, end, prefix) || ret            break          }        }        // paths less than 100 chars don't need a prefix        // and paths longer than 255 need an extended header and will fail        // on old implementations no matter what we do here.        // Null out the prefix, and fallthrough to default.        // console.error("%% header writing no prefix")        var poff = fieldOffs[fields.prefix]          , pend = fieldEnds[fields.prefix]        writeText(block, poff, pend, "")        // fallthrough      // all other fields are numeric or text      default:        ret = numeric[field]            ? writeNumeric(block, off, end, obj[field])            : writeText(block, off, end, obj[field] || "")        break    }    obj.needExtended = obj.needExtended || ret  }  var off = fieldOffs[fields.cksum]    , end = fieldEnds[fields.cksum]  writeNumeric(block, off, end, calcSum.call(this, block))  return block}// if it's a negative number, or greater than will fit,// then use write256.var MAXNUM = { 12: 077777777777             , 11: 07777777777             , 8 : 07777777             , 7 : 0777777 }function writeNumeric (block, off, end, num) {  var writeLen = end - off    , maxNum = MAXNUM[writeLen] || 0  num = num || 0  // console.error("  numeric", num)  if (num instanceof Date ||      Object.prototype.toString.call(num) === "[object Date]") {    num = num.getTime() / 1000  }  if (num > maxNum || num < 0) {    write256(block, off, end, num)    // need an extended header if negative or too big.    return true  }  // god, tar is so annoying  // if the string is small enough, you should put a space  // between the octal string and the \0, but if it doesn't  // fit, then don't.  var numStr = Math.floor(num).toString(8)  if (num < MAXNUM[writeLen - 1]) numStr += " "  // pad with "0" chars  if (numStr.length < writeLen) {    numStr = (new Array(writeLen - numStr.length).join("0")) + numStr  }  if (numStr.length !== writeLen - 1) {    throw new Error("invalid length: " + JSON.stringify(numStr) + "\n" +                    "expected: "+writeLen)  }  block.write(numStr, off, writeLen, "utf8")  block[end - 1] = 0}function write256 (block, off, end, num) {  var buf = block.slice(off, end)  var positive = num >= 0  buf[0] = positive ? 0x80 : 0xFF  // get the number as a base-256 tuple  if (!positive) num *= -1  var tuple = []  do {    var n = num % 256    tuple.push(n)    num = (num - n) / 256  } while (num)  var bytes = tuple.length  var fill = buf.length - bytes  for (var i = 1; i < fill; i ++) {    buf[i] = positive ? 0 : 0xFF  }  // tuple is a base256 number, with [0] as the *least* significant byte  // if it's negative, then we need to flip all the bits once we hit the  // first non-zero bit.  The 2's-complement is (0x100 - n), and the 1's-  // complement is (0xFF - n).  var zero = true  for (i = bytes; i > 0; i --) {    var byte = tuple[bytes - i]    if (positive) buf[fill + i] = byte    else if (zero && byte === 0) buf[fill + i] = 0    else if (zero) {      zero = false      buf[fill + i] = 0x100 - byte    } else buf[fill + i] = 0xFF - byte  }}function writeText (block, off, end, str) {  // strings are written as utf8, then padded with \0  var strLen = Buffer.byteLength(str)    , writeLen = Math.min(strLen, end - off)    // non-ascii fields need extended headers    // long fields get truncated    , needExtended = strLen !== str.length || strLen > writeLen  // write the string, and null-pad  if (writeLen > 0) block.write(str, off, writeLen, "utf8")  for (var i = off + writeLen; i < end; i ++) block[i] = 0  return needExtended}function calcSum (block) {  block = block || this.block  assert(Buffer.isBuffer(block) && block.length === 512)  if (!block) throw new Error("Need block to checksum")  // now figure out what it would be if the cksum was "        "  var sum = 0    , start = fieldOffs[fields.cksum]    , end = fieldEnds[fields.cksum]  for (var i = 0; i < fieldOffs[fields.cksum]; i ++) {    sum += block[i]  }  for (var i = start; i < end; i ++) {    sum += space  }  for (var i = end; i < 512; i ++) {    sum += block[i]  }  return sum}function checkSum (block) {  var sum = calcSum.call(this, block)  block = block || this.block  var cksum = block.slice(fieldOffs[fields.cksum], fieldEnds[fields.cksum])  cksum = parseNumeric(cksum)  return cksum === sum}function decode (block) {  block = block || this.block  assert(Buffer.isBuffer(block) && block.length === 512)  this.block = block  this.cksumValid = this.checkSum()  var prefix = null  // slice off each field.  for (var f = 0; fields[f] !== null; f ++) {    var field = fields[f]      , val = block.slice(fieldOffs[f], fieldEnds[f])    switch (field) {      case "ustar":        // if not ustar, then everything after that is just padding.        if (val.toString() !== "ustar\0") {          this.ustar = false          return        } else {          // console.error("ustar:", val, val.toString())          this.ustar = val.toString()        }        break      // prefix is special, since it might signal the xstar header      case "prefix":        var atime = parseNumeric(val.slice(131, 131 + 12))          , ctime = parseNumeric(val.slice(131 + 12, 131 + 12 + 12))        if ((val[130] === 0 || val[130] === space) &&            typeof atime === "number" &&            typeof ctime === "number" &&            val[131 + 12] === space &&            val[131 + 12 + 12] === space) {          this.atime = atime          this.ctime = ctime          val = val.slice(0, 130)        }        prefix = val.toString("utf8").replace(/\0+$/, "")        // console.error("%% header reading prefix", prefix)        break      // all other fields are null-padding text      // or a number.      default:        if (numeric[field]) {          this[field] = parseNumeric(val)        } else {          this[field] = val.toString("utf8").replace(/\0+$/, "")        }        break    }  }  // if we got a prefix, then prepend it to the path.  if (prefix) {    this.path = prefix + "/" + this.path    // console.error("%% header got a prefix", this.path)  }}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 zero = false    , tuple = []  for (var i = buf.length - 1; i > 0; i --) {    var byte = buf[i]    if (positive) tuple.push(byte)    else if (zero && byte === 0) tuple.push(0)    else if (zero) {      zero = false      tuple.push(0x100 - byte)    } else tuple.push(0xFF - byte)  }  for (var sum = 0, i = 0, l = tuple.length; i < l; i ++) {    sum += tuple[i] * Math.pow(256, i)  }  return positive ? sum : -1 * sum}function parseNumeric (f) {  if (f[0] & 0x80) return parse256(f)  var str = f.toString("utf8").split("\0")[0].trim()    , res = parseInt(str, 8)  return isNaN(res) ? null : res}
 |