header.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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. module.exports = TarHeader
  5. var tar = require("../tar.js")
  6. , fields = tar.fields
  7. , fieldOffs = tar.fieldOffs
  8. , fieldEnds = tar.fieldEnds
  9. , fieldSize = tar.fieldSize
  10. , numeric = tar.numeric
  11. , assert = require("assert").ok
  12. , space = " ".charCodeAt(0)
  13. , slash = "/".charCodeAt(0)
  14. , bslash = process.platform === "win32" ? "\\".charCodeAt(0) : null
  15. function TarHeader (block) {
  16. if (!(this instanceof TarHeader)) return new TarHeader(block)
  17. if (block) this.decode(block)
  18. }
  19. TarHeader.prototype =
  20. { decode : decode
  21. , encode: encode
  22. , calcSum: calcSum
  23. , checkSum: checkSum
  24. }
  25. TarHeader.parseNumeric = parseNumeric
  26. TarHeader.encode = encode
  27. TarHeader.decode = decode
  28. // note that this will only do the normal ustar header, not any kind
  29. // of extended posix header file. If something doesn't fit comfortably,
  30. // then it will set obj.needExtended = true, and set the block to
  31. // the closest approximation.
  32. function encode (obj) {
  33. if (!obj && !(this instanceof TarHeader)) throw new Error(
  34. "encode must be called on a TarHeader, or supplied an object")
  35. obj = obj || this
  36. var block = obj.block = new Buffer(512)
  37. // if the object has a "prefix", then that's actually an extension of
  38. // the path field.
  39. if (obj.prefix) {
  40. // console.error("%% header encoding, got a prefix", obj.prefix)
  41. obj.path = obj.prefix + "/" + obj.path
  42. // console.error("%% header encoding, prefixed path", obj.path)
  43. obj.prefix = ""
  44. }
  45. obj.needExtended = false
  46. if (obj.mode) {
  47. if (typeof obj.mode === "string") obj.mode = parseInt(obj.mode, 8)
  48. obj.mode = obj.mode & 0777
  49. }
  50. for (var f = 0; fields[f] !== null; f ++) {
  51. var field = fields[f]
  52. , off = fieldOffs[f]
  53. , end = fieldEnds[f]
  54. , ret
  55. switch (field) {
  56. case "cksum":
  57. // special, done below, after all the others
  58. break
  59. case "prefix":
  60. // special, this is an extension of the "path" field.
  61. // console.error("%% header encoding, skip prefix later")
  62. break
  63. case "type":
  64. // convert from long name to a single char.
  65. var type = obj.type || "0"
  66. if (type.length > 1) {
  67. type = tar.types[obj.type]
  68. if (!type) type = "0"
  69. }
  70. writeText(block, off, end, type)
  71. break
  72. case "path":
  73. // uses the "prefix" field if > 100 bytes, but <= 255
  74. var pathLen = Buffer.byteLength(obj.path)
  75. , pathFSize = fieldSize[fields.path]
  76. , prefFSize = fieldSize[fields.prefix]
  77. // paths between 100 and 255 should use the prefix field.
  78. // longer than 255
  79. if (pathLen > pathFSize &&
  80. pathLen <= pathFSize + prefFSize) {
  81. // need to find a slash somewhere in the middle so that
  82. // path and prefix both fit in their respective fields
  83. var searchStart = pathLen - 1 - pathFSize
  84. , searchEnd = prefFSize
  85. , found = false
  86. , pathBuf = new Buffer(obj.path)
  87. for ( var s = searchStart
  88. ; (s <= searchEnd)
  89. ; s ++ ) {
  90. if (pathBuf[s] === slash || pathBuf[s] === bslash) {
  91. found = s
  92. break
  93. }
  94. }
  95. if (found !== false) {
  96. prefix = pathBuf.slice(0, found).toString("utf8")
  97. path = pathBuf.slice(found + 1).toString("utf8")
  98. ret = writeText(block, off, end, path)
  99. off = fieldOffs[fields.prefix]
  100. end = fieldEnds[fields.prefix]
  101. // console.error("%% header writing prefix", off, end, prefix)
  102. ret = writeText(block, off, end, prefix) || ret
  103. break
  104. }
  105. }
  106. // paths less than 100 chars don't need a prefix
  107. // and paths longer than 255 need an extended header and will fail
  108. // on old implementations no matter what we do here.
  109. // Null out the prefix, and fallthrough to default.
  110. // console.error("%% header writing no prefix")
  111. var poff = fieldOffs[fields.prefix]
  112. , pend = fieldEnds[fields.prefix]
  113. writeText(block, poff, pend, "")
  114. // fallthrough
  115. // all other fields are numeric or text
  116. default:
  117. ret = numeric[field]
  118. ? writeNumeric(block, off, end, obj[field])
  119. : writeText(block, off, end, obj[field] || "")
  120. break
  121. }
  122. obj.needExtended = obj.needExtended || ret
  123. }
  124. var off = fieldOffs[fields.cksum]
  125. , end = fieldEnds[fields.cksum]
  126. writeNumeric(block, off, end, calcSum.call(this, block))
  127. return block
  128. }
  129. // if it's a negative number, or greater than will fit,
  130. // then use write256.
  131. var MAXNUM = { 12: 077777777777
  132. , 11: 07777777777
  133. , 8 : 07777777
  134. , 7 : 0777777 }
  135. function writeNumeric (block, off, end, num) {
  136. var writeLen = end - off
  137. , maxNum = MAXNUM[writeLen] || 0
  138. num = num || 0
  139. // console.error(" numeric", num)
  140. if (num instanceof Date ||
  141. Object.prototype.toString.call(num) === "[object Date]") {
  142. num = num.getTime() / 1000
  143. }
  144. if (num > maxNum || num < 0) {
  145. write256(block, off, end, num)
  146. // need an extended header if negative or too big.
  147. return true
  148. }
  149. // god, tar is so annoying
  150. // if the string is small enough, you should put a space
  151. // between the octal string and the \0, but if it doesn't
  152. // fit, then don't.
  153. var numStr = Math.floor(num).toString(8)
  154. if (num < MAXNUM[writeLen - 1]) numStr += " "
  155. // pad with "0" chars
  156. if (numStr.length < writeLen) {
  157. numStr = (new Array(writeLen - numStr.length).join("0")) + numStr
  158. }
  159. if (numStr.length !== writeLen - 1) {
  160. throw new Error("invalid length: " + JSON.stringify(numStr) + "\n" +
  161. "expected: "+writeLen)
  162. }
  163. block.write(numStr, off, writeLen, "utf8")
  164. block[end - 1] = 0
  165. }
  166. function write256 (block, off, end, num) {
  167. var buf = block.slice(off, end)
  168. var positive = num >= 0
  169. buf[0] = positive ? 0x80 : 0xFF
  170. // get the number as a base-256 tuple
  171. if (!positive) num *= -1
  172. var tuple = []
  173. do {
  174. var n = num % 256
  175. tuple.push(n)
  176. num = (num - n) / 256
  177. } while (num)
  178. var bytes = tuple.length
  179. var fill = buf.length - bytes
  180. for (var i = 1; i < fill; i ++) {
  181. buf[i] = positive ? 0 : 0xFF
  182. }
  183. // tuple is a base256 number, with [0] as the *least* significant byte
  184. // if it's negative, then we need to flip all the bits once we hit the
  185. // first non-zero bit. The 2's-complement is (0x100 - n), and the 1's-
  186. // complement is (0xFF - n).
  187. var zero = true
  188. for (i = bytes; i > 0; i --) {
  189. var byte = tuple[bytes - i]
  190. if (positive) buf[fill + i] = byte
  191. else if (zero && byte === 0) buf[fill + i] = 0
  192. else if (zero) {
  193. zero = false
  194. buf[fill + i] = 0x100 - byte
  195. } else buf[fill + i] = 0xFF - byte
  196. }
  197. }
  198. function writeText (block, off, end, str) {
  199. // strings are written as utf8, then padded with \0
  200. var strLen = Buffer.byteLength(str)
  201. , writeLen = Math.min(strLen, end - off)
  202. // non-ascii fields need extended headers
  203. // long fields get truncated
  204. , needExtended = strLen !== str.length || strLen > writeLen
  205. // write the string, and null-pad
  206. if (writeLen > 0) block.write(str, off, writeLen, "utf8")
  207. for (var i = off + writeLen; i < end; i ++) block[i] = 0
  208. return needExtended
  209. }
  210. function calcSum (block) {
  211. block = block || this.block
  212. assert(Buffer.isBuffer(block) && block.length === 512)
  213. if (!block) throw new Error("Need block to checksum")
  214. // now figure out what it would be if the cksum was " "
  215. var sum = 0
  216. , start = fieldOffs[fields.cksum]
  217. , end = fieldEnds[fields.cksum]
  218. for (var i = 0; i < fieldOffs[fields.cksum]; i ++) {
  219. sum += block[i]
  220. }
  221. for (var i = start; i < end; i ++) {
  222. sum += space
  223. }
  224. for (var i = end; i < 512; i ++) {
  225. sum += block[i]
  226. }
  227. return sum
  228. }
  229. function checkSum (block) {
  230. var sum = calcSum.call(this, block)
  231. block = block || this.block
  232. var cksum = block.slice(fieldOffs[fields.cksum], fieldEnds[fields.cksum])
  233. cksum = parseNumeric(cksum)
  234. return cksum === sum
  235. }
  236. function decode (block) {
  237. block = block || this.block
  238. assert(Buffer.isBuffer(block) && block.length === 512)
  239. this.block = block
  240. this.cksumValid = this.checkSum()
  241. var prefix = null
  242. // slice off each field.
  243. for (var f = 0; fields[f] !== null; f ++) {
  244. var field = fields[f]
  245. , val = block.slice(fieldOffs[f], fieldEnds[f])
  246. switch (field) {
  247. case "ustar":
  248. // if not ustar, then everything after that is just padding.
  249. if (val.toString() !== "ustar\0") {
  250. this.ustar = false
  251. return
  252. } else {
  253. // console.error("ustar:", val, val.toString())
  254. this.ustar = val.toString()
  255. }
  256. break
  257. // prefix is special, since it might signal the xstar header
  258. case "prefix":
  259. var atime = parseNumeric(val.slice(131, 131 + 12))
  260. , ctime = parseNumeric(val.slice(131 + 12, 131 + 12 + 12))
  261. if ((val[130] === 0 || val[130] === space) &&
  262. typeof atime === "number" &&
  263. typeof ctime === "number" &&
  264. val[131 + 12] === space &&
  265. val[131 + 12 + 12] === space) {
  266. this.atime = atime
  267. this.ctime = ctime
  268. val = val.slice(0, 130)
  269. }
  270. prefix = val.toString("utf8").replace(/\0+$/, "")
  271. // console.error("%% header reading prefix", prefix)
  272. break
  273. // all other fields are null-padding text
  274. // or a number.
  275. default:
  276. if (numeric[field]) {
  277. this[field] = parseNumeric(val)
  278. } else {
  279. this[field] = val.toString("utf8").replace(/\0+$/, "")
  280. }
  281. break
  282. }
  283. }
  284. // if we got a prefix, then prepend it to the path.
  285. if (prefix) {
  286. this.path = prefix + "/" + this.path
  287. // console.error("%% header got a prefix", this.path)
  288. }
  289. }
  290. function parse256 (buf) {
  291. // first byte MUST be either 80 or FF
  292. // 80 for positive, FF for 2's comp
  293. var positive
  294. if (buf[0] === 0x80) positive = true
  295. else if (buf[0] === 0xFF) positive = false
  296. else return null
  297. // build up a base-256 tuple from the least sig to the highest
  298. var zero = false
  299. , tuple = []
  300. for (var i = buf.length - 1; i > 0; i --) {
  301. var byte = buf[i]
  302. if (positive) tuple.push(byte)
  303. else if (zero && byte === 0) tuple.push(0)
  304. else if (zero) {
  305. zero = false
  306. tuple.push(0x100 - byte)
  307. } else tuple.push(0xFF - byte)
  308. }
  309. for (var sum = 0, i = 0, l = tuple.length; i < l; i ++) {
  310. sum += tuple[i] * Math.pow(256, i)
  311. }
  312. return positive ? sum : -1 * sum
  313. }
  314. function parseNumeric (f) {
  315. if (f[0] & 0x80) return parse256(f)
  316. var str = f.toString("utf8").split("\0")[0].trim()
  317. , res = parseInt(str, 8)
  318. return isNaN(res) ? null : res
  319. }