You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							385 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							385 lines
						
					
					
						
							11 KiB
						
					
					
				// 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 = TarHeader | 
						|
 | 
						|
var 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) : null | 
						|
 | 
						|
function 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 = parseNumeric | 
						|
TarHeader.encode = encode | 
						|
TarHeader.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 | 
						|
} | 
						|
 | 
						|
 |