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.

pax.js 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. 'use strict'
  2. const Header = require('./header.js')
  3. const path = require('path')
  4. class Pax {
  5. constructor (obj, global) {
  6. this.atime = obj.atime || null
  7. this.charset = obj.charset || null
  8. this.comment = obj.comment || null
  9. this.ctime = obj.ctime || null
  10. this.gid = obj.gid || null
  11. this.gname = obj.gname || null
  12. this.linkpath = obj.linkpath || null
  13. this.mtime = obj.mtime || null
  14. this.path = obj.path || null
  15. this.size = obj.size || null
  16. this.uid = obj.uid || null
  17. this.uname = obj.uname || null
  18. this.dev = obj.dev || null
  19. this.ino = obj.ino || null
  20. this.nlink = obj.nlink || null
  21. this.global = global || false
  22. }
  23. encode () {
  24. const body = this.encodeBody()
  25. if (body === '') {
  26. return null
  27. }
  28. const bodyLen = Buffer.byteLength(body)
  29. // round up to 512 bytes
  30. // add 512 for header
  31. const bufLen = 512 * Math.ceil(1 + bodyLen / 512)
  32. const buf = Buffer.allocUnsafe(bufLen)
  33. // 0-fill the header section, it might not hit every field
  34. for (let i = 0; i < 512; i++) {
  35. buf[i] = 0
  36. }
  37. new Header({
  38. // XXX split the path
  39. // then the path should be PaxHeader + basename, but less than 99,
  40. // prepend with the dirname
  41. path: ('PaxHeader/' + path.basename(this.path)).slice(0, 99),
  42. mode: this.mode || 0o644,
  43. uid: this.uid || null,
  44. gid: this.gid || null,
  45. size: bodyLen,
  46. mtime: this.mtime || null,
  47. type: this.global ? 'GlobalExtendedHeader' : 'ExtendedHeader',
  48. linkpath: '',
  49. uname: this.uname || '',
  50. gname: this.gname || '',
  51. devmaj: 0,
  52. devmin: 0,
  53. atime: this.atime || null,
  54. ctime: this.ctime || null,
  55. }).encode(buf)
  56. buf.write(body, 512, bodyLen, 'utf8')
  57. // null pad after the body
  58. for (let i = bodyLen + 512; i < buf.length; i++) {
  59. buf[i] = 0
  60. }
  61. return buf
  62. }
  63. encodeBody () {
  64. return (
  65. this.encodeField('path') +
  66. this.encodeField('ctime') +
  67. this.encodeField('atime') +
  68. this.encodeField('dev') +
  69. this.encodeField('ino') +
  70. this.encodeField('nlink') +
  71. this.encodeField('charset') +
  72. this.encodeField('comment') +
  73. this.encodeField('gid') +
  74. this.encodeField('gname') +
  75. this.encodeField('linkpath') +
  76. this.encodeField('mtime') +
  77. this.encodeField('size') +
  78. this.encodeField('uid') +
  79. this.encodeField('uname')
  80. )
  81. }
  82. encodeField (field) {
  83. if (this[field] === null || this[field] === undefined) {
  84. return ''
  85. }
  86. const v = this[field] instanceof Date ? this[field].getTime() / 1000
  87. : this[field]
  88. const s = ' ' +
  89. (field === 'dev' || field === 'ino' || field === 'nlink'
  90. ? 'SCHILY.' : '') +
  91. field + '=' + v + '\n'
  92. const byteLen = Buffer.byteLength(s)
  93. // the digits includes the length of the digits in ascii base-10
  94. // so if it's 9 characters, then adding 1 for the 9 makes it 10
  95. // which makes it 11 chars.
  96. let digits = Math.floor(Math.log(byteLen) / Math.log(10)) + 1
  97. if (byteLen + digits >= Math.pow(10, digits)) {
  98. digits += 1
  99. }
  100. const len = digits + byteLen
  101. return len + s
  102. }
  103. }
  104. Pax.parse = (string, ex, g) => new Pax(merge(parseKV(string), ex), g)
  105. const merge = (a, b) =>
  106. b ? Object.keys(a).reduce((s, k) => (s[k] = a[k], s), b) : a
  107. const parseKV = string =>
  108. string
  109. .replace(/\n$/, '')
  110. .split('\n')
  111. .reduce(parseKVLine, Object.create(null))
  112. const parseKVLine = (set, line) => {
  113. const n = parseInt(line, 10)
  114. // XXX Values with \n in them will fail this.
  115. // Refactor to not be a naive line-by-line parse.
  116. if (n !== Buffer.byteLength(line) + 1) {
  117. return set
  118. }
  119. line = line.slice((n + ' ').length)
  120. const kv = line.split('=')
  121. const k = kv.shift().replace(/^SCHILY\.(dev|ino|nlink)/, '$1')
  122. if (!k) {
  123. return set
  124. }
  125. const v = kv.join('=')
  126. set[k] = /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k)
  127. ? new Date(v * 1000)
  128. : /^[0-9]+$/.test(v) ? +v
  129. : v
  130. return set
  131. }
  132. module.exports = Pax