Initial commit
[yaffs-website] / node_modules / tar / lib / header.js
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.
4
5 module.exports = TarHeader
6
7 var tar = require("../tar.js")
8   , fields = tar.fields
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
17
18 function TarHeader (block) {
19   if (!(this instanceof TarHeader)) return new TarHeader(block)
20   if (block) this.decode(block)
21 }
22
23 TarHeader.prototype =
24   { decode : decode
25   , encode: encode
26   , calcSum: calcSum
27   , checkSum: checkSum
28   }
29
30 TarHeader.parseNumeric = parseNumeric
31 TarHeader.encode = encode
32 TarHeader.decode = decode
33
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")
41
42   obj = obj || this
43   var block = obj.block = new Buffer(512)
44
45   // if the object has a "prefix", then that's actually an extension of
46   // the path field.
47   if (obj.prefix) {
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)
51     obj.prefix = ""
52   }
53
54   obj.needExtended = false
55
56   if (obj.mode) {
57     if (typeof obj.mode === "string") obj.mode = parseInt(obj.mode, 8)
58     obj.mode = obj.mode & 0777
59   }
60
61   for (var f = 0; fields[f] !== null; f ++) {
62     var field = fields[f]
63       , off = fieldOffs[f]
64       , end = fieldEnds[f]
65       , ret
66
67     switch (field) {
68       case "cksum":
69         // special, done below, after all the others
70         break
71
72       case "prefix":
73         // special, this is an extension of the "path" field.
74         // console.error("%% header encoding, skip prefix later")
75         break
76
77       case "type":
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]
82           if (!type) type = "0"
83         }
84         writeText(block, off, end, type)
85         break
86
87       case "path":
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]
92
93         // paths between 100 and 255 should use the prefix field.
94         // longer than 255
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
101             , found = false
102             , pathBuf = new Buffer(obj.path)
103
104           for ( var s = searchStart
105               ; (s <= searchEnd)
106               ; s ++ ) {
107             if (pathBuf[s] === slash || pathBuf[s] === bslash) {
108               found = s
109               break
110             }
111           }
112
113           if (found !== false) {
114             prefix = pathBuf.slice(0, found).toString("utf8")
115             path = pathBuf.slice(found + 1).toString("utf8")
116
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
122             break
123           }
124         }
125
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, "")
134         // fallthrough
135
136       // all other fields are numeric or text
137       default:
138         ret = numeric[field]
139             ? writeNumeric(block, off, end, obj[field])
140             : writeText(block, off, end, obj[field] || "")
141         break
142     }
143     obj.needExtended = obj.needExtended || ret
144   }
145
146   var off = fieldOffs[fields.cksum]
147     , end = fieldEnds[fields.cksum]
148
149   writeNumeric(block, off, end, calcSum.call(this, block))
150
151   return block
152 }
153
154 // if it's a negative number, or greater than will fit,
155 // then use write256.
156 var MAXNUM = { 12: 077777777777
157              , 11: 07777777777
158              , 8 : 07777777
159              , 7 : 0777777 }
160 function writeNumeric (block, off, end, num) {
161   var writeLen = end - off
162     , maxNum = MAXNUM[writeLen] || 0
163
164   num = num || 0
165   // console.error("  numeric", num)
166
167   if (num instanceof Date ||
168       Object.prototype.toString.call(num) === "[object Date]") {
169     num = num.getTime() / 1000
170   }
171
172   if (num > maxNum || num < 0) {
173     write256(block, off, end, num)
174     // need an extended header if negative or too big.
175     return true
176   }
177
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
181   // fit, then don't.
182   var numStr = Math.floor(num).toString(8)
183   if (num < MAXNUM[writeLen - 1]) numStr += " "
184
185   // pad with "0" chars
186   if (numStr.length < writeLen) {
187     numStr = (new Array(writeLen - numStr.length).join("0")) + numStr
188   }
189
190   if (numStr.length !== writeLen - 1) {
191     throw new Error("invalid length: " + JSON.stringify(numStr) + "\n" +
192                     "expected: "+writeLen)
193   }
194   block.write(numStr, off, writeLen, "utf8")
195   block[end - 1] = 0
196 }
197
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
202
203   // get the number as a base-256 tuple
204   if (!positive) num *= -1
205   var tuple = []
206   do {
207     var n = num % 256
208     tuple.push(n)
209     num = (num - n) / 256
210   } while (num)
211
212   var bytes = tuple.length
213
214   var fill = buf.length - bytes
215   for (var i = 1; i < fill; i ++) {
216     buf[i] = positive ? 0 : 0xFF
217   }
218
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).
223   var zero = true
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
228     else if (zero) {
229       zero = false
230       buf[fill + i] = 0x100 - byte
231     } else buf[fill + i] = 0xFF - byte
232   }
233 }
234
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
242
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
246
247   return needExtended
248 }
249
250 function calcSum (block) {
251   block = block || this.block
252   assert(Buffer.isBuffer(block) && block.length === 512)
253
254   if (!block) throw new Error("Need block to checksum")
255
256   // now figure out what it would be if the cksum was "        "
257   var sum = 0
258     , start = fieldOffs[fields.cksum]
259     , end = fieldEnds[fields.cksum]
260
261   for (var i = 0; i < fieldOffs[fields.cksum]; i ++) {
262     sum += block[i]
263   }
264
265   for (var i = start; i < end; i ++) {
266     sum += space
267   }
268
269   for (var i = end; i < 512; i ++) {
270     sum += block[i]
271   }
272
273   return sum
274 }
275
276
277 function checkSum (block) {
278   var sum = calcSum.call(this, block)
279   block = block || this.block
280
281   var cksum = block.slice(fieldOffs[fields.cksum], fieldEnds[fields.cksum])
282   cksum = parseNumeric(cksum)
283
284   return cksum === sum
285 }
286
287 function decode (block) {
288   block = block || this.block
289   assert(Buffer.isBuffer(block) && block.length === 512)
290
291   this.block = block
292   this.cksumValid = this.checkSum()
293
294   var prefix = null
295
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])
300
301     switch (field) {
302       case "ustar":
303         // if not ustar, then everything after that is just padding.
304         if (val.toString() !== "ustar\0") {
305           this.ustar = false
306           return
307         } else {
308           // console.error("ustar:", val, val.toString())
309           this.ustar = val.toString()
310         }
311         break
312
313       // prefix is special, since it might signal the xstar header
314       case "prefix":
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) {
322           this.atime = atime
323           this.ctime = ctime
324           val = val.slice(0, 130)
325         }
326         prefix = val.toString("utf8").replace(/\0+$/, "")
327         // console.error("%% header reading prefix", prefix)
328         break
329
330       // all other fields are null-padding text
331       // or a number.
332       default:
333         if (numeric[field]) {
334           this[field] = parseNumeric(val)
335         } else {
336           this[field] = val.toString("utf8").replace(/\0+$/, "")
337         }
338         break
339     }
340   }
341
342   // if we got a prefix, then prepend it to the path.
343   if (prefix) {
344     this.path = prefix + "/" + this.path
345     // console.error("%% header got a prefix", this.path)
346   }
347 }
348
349 function parse256 (buf) {
350   // first byte MUST be either 80 or FF
351   // 80 for positive, FF for 2's comp
352   var positive
353   if (buf[0] === 0x80) positive = true
354   else if (buf[0] === 0xFF) positive = false
355   else return null
356
357   // build up a base-256 tuple from the least sig to the highest
358   var zero = false
359     , tuple = []
360   for (var i = buf.length - 1; i > 0; i --) {
361     var byte = buf[i]
362     if (positive) tuple.push(byte)
363     else if (zero && byte === 0) tuple.push(0)
364     else if (zero) {
365       zero = false
366       tuple.push(0x100 - byte)
367     } else tuple.push(0xFF - byte)
368   }
369
370   for (var sum = 0, i = 0, l = tuple.length; i < l; i ++) {
371     sum += tuple[i] * Math.pow(256, i)
372   }
373
374   return positive ? sum : -1 * sum
375 }
376
377 function parseNumeric (f) {
378   if (f[0] & 0x80) return parse256(f)
379
380   var str = f.toString("utf8").split("\0")[0].trim()
381     , res = parseInt(str, 8)
382
383   return isNaN(res) ? null : res
384 }
385