1 // parse a 512-byte header block to a data object, or vice-versa
2 // If the data won't fit nicely in a simple header, then generate
3 // the appropriate extended header file, and return that.
5 module.exports = TarHeader
7 var tar = require("../tar.js")
9 , fieldOffs = tar.fieldOffs
10 , fieldEnds = tar.fieldEnds
11 , fieldSize = tar.fieldSize
12 , numeric = tar.numeric
13 , assert = require("assert").ok
14 , space = " ".charCodeAt(0)
15 , slash = "/".charCodeAt(0)
16 , bslash = process.platform === "win32" ? "\\".charCodeAt(0) : null
18 function TarHeader (block) {
19 if (!(this instanceof TarHeader)) return new TarHeader(block)
20 if (block) this.decode(block)
30 TarHeader.parseNumeric = parseNumeric
31 TarHeader.encode = encode
32 TarHeader.decode = decode
34 // note that this will only do the normal ustar header, not any kind
35 // of extended posix header file. If something doesn't fit comfortably,
36 // then it will set obj.needExtended = true, and set the block to
37 // the closest approximation.
38 function encode (obj) {
39 if (!obj && !(this instanceof TarHeader)) throw new Error(
40 "encode must be called on a TarHeader, or supplied an object")
43 var block = obj.block = new Buffer(512)
45 // if the object has a "prefix", then that's actually an extension of
48 // console.error("%% header encoding, got a prefix", obj.prefix)
49 obj.path = obj.prefix + "/" + obj.path
50 // console.error("%% header encoding, prefixed path", obj.path)
54 obj.needExtended = false
57 if (typeof obj.mode === "string") obj.mode = parseInt(obj.mode, 8)
58 obj.mode = obj.mode & 0777
61 for (var f = 0; fields[f] !== null; f ++) {
69 // special, done below, after all the others
73 // special, this is an extension of the "path" field.
74 // console.error("%% header encoding, skip prefix later")
78 // convert from long name to a single char.
79 var type = obj.type || "0"
80 if (type.length > 1) {
81 type = tar.types[obj.type]
84 writeText(block, off, end, type)
88 // uses the "prefix" field if > 100 bytes, but <= 255
89 var pathLen = Buffer.byteLength(obj.path)
90 , pathFSize = fieldSize[fields.path]
91 , prefFSize = fieldSize[fields.prefix]
93 // paths between 100 and 255 should use the prefix field.
95 if (pathLen > pathFSize &&
96 pathLen <= pathFSize + prefFSize) {
97 // need to find a slash somewhere in the middle so that
98 // path and prefix both fit in their respective fields
99 var searchStart = pathLen - 1 - pathFSize
100 , searchEnd = prefFSize
102 , pathBuf = new Buffer(obj.path)
104 for ( var s = searchStart
107 if (pathBuf[s] === slash || pathBuf[s] === bslash) {
113 if (found !== false) {
114 prefix = pathBuf.slice(0, found).toString("utf8")
115 path = pathBuf.slice(found + 1).toString("utf8")
117 ret = writeText(block, off, end, path)
118 off = fieldOffs[fields.prefix]
119 end = fieldEnds[fields.prefix]
120 // console.error("%% header writing prefix", off, end, prefix)
121 ret = writeText(block, off, end, prefix) || ret
126 // paths less than 100 chars don't need a prefix
127 // and paths longer than 255 need an extended header and will fail
128 // on old implementations no matter what we do here.
129 // Null out the prefix, and fallthrough to default.
130 // console.error("%% header writing no prefix")
131 var poff = fieldOffs[fields.prefix]
132 , pend = fieldEnds[fields.prefix]
133 writeText(block, poff, pend, "")
136 // all other fields are numeric or text
139 ? writeNumeric(block, off, end, obj[field])
140 : writeText(block, off, end, obj[field] || "")
143 obj.needExtended = obj.needExtended || ret
146 var off = fieldOffs[fields.cksum]
147 , end = fieldEnds[fields.cksum]
149 writeNumeric(block, off, end, calcSum.call(this, block))
154 // if it's a negative number, or greater than will fit,
155 // then use write256.
156 var MAXNUM = { 12: 077777777777
160 function writeNumeric (block, off, end, num) {
161 var writeLen = end - off
162 , maxNum = MAXNUM[writeLen] || 0
165 // console.error(" numeric", num)
167 if (num instanceof Date ||
168 Object.prototype.toString.call(num) === "[object Date]") {
169 num = num.getTime() / 1000
172 if (num > maxNum || num < 0) {
173 write256(block, off, end, num)
174 // need an extended header if negative or too big.
178 // god, tar is so annoying
179 // if the string is small enough, you should put a space
180 // between the octal string and the \0, but if it doesn't
182 var numStr = Math.floor(num).toString(8)
183 if (num < MAXNUM[writeLen - 1]) numStr += " "
185 // pad with "0" chars
186 if (numStr.length < writeLen) {
187 numStr = (new Array(writeLen - numStr.length).join("0")) + numStr
190 if (numStr.length !== writeLen - 1) {
191 throw new Error("invalid length: " + JSON.stringify(numStr) + "\n" +
192 "expected: "+writeLen)
194 block.write(numStr, off, writeLen, "utf8")
198 function write256 (block, off, end, num) {
199 var buf = block.slice(off, end)
200 var positive = num >= 0
201 buf[0] = positive ? 0x80 : 0xFF
203 // get the number as a base-256 tuple
204 if (!positive) num *= -1
209 num = (num - n) / 256
212 var bytes = tuple.length
214 var fill = buf.length - bytes
215 for (var i = 1; i < fill; i ++) {
216 buf[i] = positive ? 0 : 0xFF
219 // tuple is a base256 number, with [0] as the *least* significant byte
220 // if it's negative, then we need to flip all the bits once we hit the
221 // first non-zero bit. The 2's-complement is (0x100 - n), and the 1's-
222 // complement is (0xFF - n).
224 for (i = bytes; i > 0; i --) {
225 var byte = tuple[bytes - i]
226 if (positive) buf[fill + i] = byte
227 else if (zero && byte === 0) buf[fill + i] = 0
230 buf[fill + i] = 0x100 - byte
231 } else buf[fill + i] = 0xFF - byte
235 function writeText (block, off, end, str) {
236 // strings are written as utf8, then padded with \0
237 var strLen = Buffer.byteLength(str)
238 , writeLen = Math.min(strLen, end - off)
239 // non-ascii fields need extended headers
240 // long fields get truncated
241 , needExtended = strLen !== str.length || strLen > writeLen
243 // write the string, and null-pad
244 if (writeLen > 0) block.write(str, off, writeLen, "utf8")
245 for (var i = off + writeLen; i < end; i ++) block[i] = 0
250 function calcSum (block) {
251 block = block || this.block
252 assert(Buffer.isBuffer(block) && block.length === 512)
254 if (!block) throw new Error("Need block to checksum")
256 // now figure out what it would be if the cksum was " "
258 , start = fieldOffs[fields.cksum]
259 , end = fieldEnds[fields.cksum]
261 for (var i = 0; i < fieldOffs[fields.cksum]; i ++) {
265 for (var i = start; i < end; i ++) {
269 for (var i = end; i < 512; i ++) {
277 function checkSum (block) {
278 var sum = calcSum.call(this, block)
279 block = block || this.block
281 var cksum = block.slice(fieldOffs[fields.cksum], fieldEnds[fields.cksum])
282 cksum = parseNumeric(cksum)
287 function decode (block) {
288 block = block || this.block
289 assert(Buffer.isBuffer(block) && block.length === 512)
292 this.cksumValid = this.checkSum()
296 // slice off each field.
297 for (var f = 0; fields[f] !== null; f ++) {
298 var field = fields[f]
299 , val = block.slice(fieldOffs[f], fieldEnds[f])
303 // if not ustar, then everything after that is just padding.
304 if (val.toString() !== "ustar\0") {
308 // console.error("ustar:", val, val.toString())
309 this.ustar = val.toString()
313 // prefix is special, since it might signal the xstar header
315 var atime = parseNumeric(val.slice(131, 131 + 12))
316 , ctime = parseNumeric(val.slice(131 + 12, 131 + 12 + 12))
317 if ((val[130] === 0 || val[130] === space) &&
318 typeof atime === "number" &&
319 typeof ctime === "number" &&
320 val[131 + 12] === space &&
321 val[131 + 12 + 12] === space) {
324 val = val.slice(0, 130)
326 prefix = val.toString("utf8").replace(/\0+$/, "")
327 // console.error("%% header reading prefix", prefix)
330 // all other fields are null-padding text
333 if (numeric[field]) {
334 this[field] = parseNumeric(val)
336 this[field] = val.toString("utf8").replace(/\0+$/, "")
342 // if we got a prefix, then prepend it to the path.
344 this.path = prefix + "/" + this.path
345 // console.error("%% header got a prefix", this.path)
349 function parse256 (buf) {
350 // first byte MUST be either 80 or FF
351 // 80 for positive, FF for 2's comp
353 if (buf[0] === 0x80) positive = true
354 else if (buf[0] === 0xFF) positive = false
357 // build up a base-256 tuple from the least sig to the highest
360 for (var i = buf.length - 1; i > 0; i --) {
362 if (positive) tuple.push(byte)
363 else if (zero && byte === 0) tuple.push(0)
366 tuple.push(0x100 - byte)
367 } else tuple.push(0xFF - byte)
370 for (var sum = 0, i = 0, l = tuple.length; i < l; i ++) {
371 sum += tuple[i] * Math.pow(256, i)
374 return positive ? sum : -1 * sum
377 function parseNumeric (f) {
378 if (f[0] & 0x80) return parse256(f)
380 var str = f.toString("utf8").split("\0")[0].trim()
381 , res = parseInt(str, 8)
383 return isNaN(res) ? null : res