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