headers.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. const b4a = require('b4a')
  2. const ZEROS = '0000000000000000000'
  3. const SEVENS = '7777777777777777777'
  4. const ZERO_OFFSET = '0'.charCodeAt(0)
  5. const USTAR_MAGIC = b4a.from([0x75, 0x73, 0x74, 0x61, 0x72, 0x00]) // ustar\x00
  6. const USTAR_VER = b4a.from([ZERO_OFFSET, ZERO_OFFSET])
  7. const GNU_MAGIC = b4a.from([0x75, 0x73, 0x74, 0x61, 0x72, 0x20]) // ustar\x20
  8. const GNU_VER = b4a.from([0x20, 0x00])
  9. const MASK = 0o7777
  10. const MAGIC_OFFSET = 257
  11. const VERSION_OFFSET = 263
  12. exports.decodeLongPath = function decodeLongPath (buf, encoding) {
  13. return decodeStr(buf, 0, buf.length, encoding)
  14. }
  15. exports.encodePax = function encodePax (opts) { // TODO: encode more stuff in pax
  16. let result = ''
  17. if (opts.name) result += addLength(' path=' + opts.name + '\n')
  18. if (opts.linkname) result += addLength(' linkpath=' + opts.linkname + '\n')
  19. const pax = opts.pax
  20. if (pax) {
  21. for (const key in pax) {
  22. result += addLength(' ' + key + '=' + pax[key] + '\n')
  23. }
  24. }
  25. return b4a.from(result)
  26. }
  27. exports.decodePax = function decodePax (buf) {
  28. const result = {}
  29. while (buf.length) {
  30. let i = 0
  31. while (i < buf.length && buf[i] !== 32) i++
  32. const len = parseInt(b4a.toString(buf.subarray(0, i)), 10)
  33. if (!len) return result
  34. const b = b4a.toString(buf.subarray(i + 1, len - 1))
  35. const keyIndex = b.indexOf('=')
  36. if (keyIndex === -1) return result
  37. result[b.slice(0, keyIndex)] = b.slice(keyIndex + 1)
  38. buf = buf.subarray(len)
  39. }
  40. return result
  41. }
  42. exports.encode = function encode (opts) {
  43. const buf = b4a.alloc(512)
  44. let name = opts.name
  45. let prefix = ''
  46. if (opts.typeflag === 5 && name[name.length - 1] !== '/') name += '/'
  47. if (b4a.byteLength(name) !== name.length) return null // utf-8
  48. while (b4a.byteLength(name) > 100) {
  49. const i = name.indexOf('/')
  50. if (i === -1) return null
  51. prefix += prefix ? '/' + name.slice(0, i) : name.slice(0, i)
  52. name = name.slice(i + 1)
  53. }
  54. if (b4a.byteLength(name) > 100 || b4a.byteLength(prefix) > 155) return null
  55. if (opts.linkname && b4a.byteLength(opts.linkname) > 100) return null
  56. b4a.write(buf, name)
  57. b4a.write(buf, encodeOct(opts.mode & MASK, 6), 100)
  58. b4a.write(buf, encodeOct(opts.uid, 6), 108)
  59. b4a.write(buf, encodeOct(opts.gid, 6), 116)
  60. encodeSize(opts.size, buf, 124)
  61. b4a.write(buf, encodeOct((opts.mtime.getTime() / 1000) | 0, 11), 136)
  62. buf[156] = ZERO_OFFSET + toTypeflag(opts.type)
  63. if (opts.linkname) b4a.write(buf, opts.linkname, 157)
  64. b4a.copy(USTAR_MAGIC, buf, MAGIC_OFFSET)
  65. b4a.copy(USTAR_VER, buf, VERSION_OFFSET)
  66. if (opts.uname) b4a.write(buf, opts.uname, 265)
  67. if (opts.gname) b4a.write(buf, opts.gname, 297)
  68. b4a.write(buf, encodeOct(opts.devmajor || 0, 6), 329)
  69. b4a.write(buf, encodeOct(opts.devminor || 0, 6), 337)
  70. if (prefix) b4a.write(buf, prefix, 345)
  71. b4a.write(buf, encodeOct(cksum(buf), 6), 148)
  72. return buf
  73. }
  74. exports.decode = function decode (buf, filenameEncoding, allowUnknownFormat) {
  75. let typeflag = buf[156] === 0 ? 0 : buf[156] - ZERO_OFFSET
  76. let name = decodeStr(buf, 0, 100, filenameEncoding)
  77. const mode = decodeOct(buf, 100, 8)
  78. const uid = decodeOct(buf, 108, 8)
  79. const gid = decodeOct(buf, 116, 8)
  80. const size = decodeOct(buf, 124, 12)
  81. const mtime = decodeOct(buf, 136, 12)
  82. const type = toType(typeflag)
  83. const linkname = buf[157] === 0 ? null : decodeStr(buf, 157, 100, filenameEncoding)
  84. const uname = decodeStr(buf, 265, 32)
  85. const gname = decodeStr(buf, 297, 32)
  86. const devmajor = decodeOct(buf, 329, 8)
  87. const devminor = decodeOct(buf, 337, 8)
  88. const c = cksum(buf)
  89. // checksum is still initial value if header was null.
  90. if (c === 8 * 32) return null
  91. // valid checksum
  92. if (c !== decodeOct(buf, 148, 8)) throw new Error('Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?')
  93. if (isUSTAR(buf)) {
  94. // ustar (posix) format.
  95. // prepend prefix, if present.
  96. if (buf[345]) name = decodeStr(buf, 345, 155, filenameEncoding) + '/' + name
  97. } else if (isGNU(buf)) {
  98. // 'gnu'/'oldgnu' format. Similar to ustar, but has support for incremental and
  99. // multi-volume tarballs.
  100. } else {
  101. if (!allowUnknownFormat) {
  102. throw new Error('Invalid tar header: unknown format.')
  103. }
  104. }
  105. // to support old tar versions that use trailing / to indicate dirs
  106. if (typeflag === 0 && name && name[name.length - 1] === '/') typeflag = 5
  107. return {
  108. name,
  109. mode,
  110. uid,
  111. gid,
  112. size,
  113. mtime: new Date(1000 * mtime),
  114. type,
  115. linkname,
  116. uname,
  117. gname,
  118. devmajor,
  119. devminor,
  120. pax: null
  121. }
  122. }
  123. function isUSTAR (buf) {
  124. return b4a.equals(USTAR_MAGIC, buf.subarray(MAGIC_OFFSET, MAGIC_OFFSET + 6))
  125. }
  126. function isGNU (buf) {
  127. return b4a.equals(GNU_MAGIC, buf.subarray(MAGIC_OFFSET, MAGIC_OFFSET + 6)) &&
  128. b4a.equals(GNU_VER, buf.subarray(VERSION_OFFSET, VERSION_OFFSET + 2))
  129. }
  130. function clamp (index, len, defaultValue) {
  131. if (typeof index !== 'number') return defaultValue
  132. index = ~~index // Coerce to integer.
  133. if (index >= len) return len
  134. if (index >= 0) return index
  135. index += len
  136. if (index >= 0) return index
  137. return 0
  138. }
  139. function toType (flag) {
  140. switch (flag) {
  141. case 0:
  142. return 'file'
  143. case 1:
  144. return 'link'
  145. case 2:
  146. return 'symlink'
  147. case 3:
  148. return 'character-device'
  149. case 4:
  150. return 'block-device'
  151. case 5:
  152. return 'directory'
  153. case 6:
  154. return 'fifo'
  155. case 7:
  156. return 'contiguous-file'
  157. case 72:
  158. return 'pax-header'
  159. case 55:
  160. return 'pax-global-header'
  161. case 27:
  162. return 'gnu-long-link-path'
  163. case 28:
  164. case 30:
  165. return 'gnu-long-path'
  166. }
  167. return null
  168. }
  169. function toTypeflag (flag) {
  170. switch (flag) {
  171. case 'file':
  172. return 0
  173. case 'link':
  174. return 1
  175. case 'symlink':
  176. return 2
  177. case 'character-device':
  178. return 3
  179. case 'block-device':
  180. return 4
  181. case 'directory':
  182. return 5
  183. case 'fifo':
  184. return 6
  185. case 'contiguous-file':
  186. return 7
  187. case 'pax-header':
  188. return 72
  189. }
  190. return 0
  191. }
  192. function indexOf (block, num, offset, end) {
  193. for (; offset < end; offset++) {
  194. if (block[offset] === num) return offset
  195. }
  196. return end
  197. }
  198. function cksum (block) {
  199. let sum = 8 * 32
  200. for (let i = 0; i < 148; i++) sum += block[i]
  201. for (let j = 156; j < 512; j++) sum += block[j]
  202. return sum
  203. }
  204. function encodeOct (val, n) {
  205. val = val.toString(8)
  206. if (val.length > n) return SEVENS.slice(0, n) + ' '
  207. return ZEROS.slice(0, n - val.length) + val + ' '
  208. }
  209. function encodeSizeBin (num, buf, off) {
  210. buf[off] = 0x80
  211. for (let i = 11; i > 0; i--) {
  212. buf[off + i] = num & 0xff
  213. num = Math.floor(num / 0x100)
  214. }
  215. }
  216. function encodeSize (num, buf, off) {
  217. if (num.toString(8).length > 11) {
  218. encodeSizeBin(num, buf, off)
  219. } else {
  220. b4a.write(buf, encodeOct(num, 11), off)
  221. }
  222. }
  223. /* Copied from the node-tar repo and modified to meet
  224. * tar-stream coding standard.
  225. *
  226. * Source: https://github.com/npm/node-tar/blob/51b6627a1f357d2eb433e7378e5f05e83b7aa6cd/lib/header.js#L349
  227. */
  228. function parse256 (buf) {
  229. // first byte MUST be either 80 or FF
  230. // 80 for positive, FF for 2's comp
  231. let positive
  232. if (buf[0] === 0x80) positive = true
  233. else if (buf[0] === 0xFF) positive = false
  234. else return null
  235. // build up a base-256 tuple from the least sig to the highest
  236. const tuple = []
  237. let i
  238. for (i = buf.length - 1; i > 0; i--) {
  239. const byte = buf[i]
  240. if (positive) tuple.push(byte)
  241. else tuple.push(0xFF - byte)
  242. }
  243. let sum = 0
  244. const l = tuple.length
  245. for (i = 0; i < l; i++) {
  246. sum += tuple[i] * Math.pow(256, i)
  247. }
  248. return positive ? sum : -1 * sum
  249. }
  250. function decodeOct (val, offset, length) {
  251. val = val.subarray(offset, offset + length)
  252. offset = 0
  253. // If prefixed with 0x80 then parse as a base-256 integer
  254. if (val[offset] & 0x80) {
  255. return parse256(val)
  256. } else {
  257. // Older versions of tar can prefix with spaces
  258. while (offset < val.length && val[offset] === 32) offset++
  259. const end = clamp(indexOf(val, 32, offset, val.length), val.length, val.length)
  260. while (offset < end && val[offset] === 0) offset++
  261. if (end === offset) return 0
  262. return parseInt(b4a.toString(val.subarray(offset, end)), 8)
  263. }
  264. }
  265. function decodeStr (val, offset, length, encoding) {
  266. return b4a.toString(val.subarray(offset, indexOf(val, 0, offset, offset + length)), encoding)
  267. }
  268. function addLength (str) {
  269. const len = b4a.byteLength(str)
  270. let digits = Math.floor(Math.log(len) / Math.log(10)) + 1
  271. if (len + digits >= Math.pow(10, digits)) digits++
  272. return (len + digits) + str
  273. }