| 1 | 'use strict'
|
|---|
| 2 | const Header = require('./header.js')
|
|---|
| 3 | const path = require('path')
|
|---|
| 4 |
|
|---|
| 5 | class Pax {
|
|---|
| 6 | constructor (obj, global) {
|
|---|
| 7 | this.atime = obj.atime || null
|
|---|
| 8 | this.charset = obj.charset || null
|
|---|
| 9 | this.comment = obj.comment || null
|
|---|
| 10 | this.ctime = obj.ctime || null
|
|---|
| 11 | this.gid = obj.gid || null
|
|---|
| 12 | this.gname = obj.gname || null
|
|---|
| 13 | this.linkpath = obj.linkpath || null
|
|---|
| 14 | this.mtime = obj.mtime || null
|
|---|
| 15 | this.path = obj.path || null
|
|---|
| 16 | this.size = obj.size || null
|
|---|
| 17 | this.uid = obj.uid || null
|
|---|
| 18 | this.uname = obj.uname || null
|
|---|
| 19 | this.dev = obj.dev || null
|
|---|
| 20 | this.ino = obj.ino || null
|
|---|
| 21 | this.nlink = obj.nlink || null
|
|---|
| 22 | this.global = global || false
|
|---|
| 23 | }
|
|---|
| 24 |
|
|---|
| 25 | encode () {
|
|---|
| 26 | const body = this.encodeBody()
|
|---|
| 27 | if (body === '') {
|
|---|
| 28 | return null
|
|---|
| 29 | }
|
|---|
| 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 | }
|
|---|
| 96 | const v = this[field] instanceof Date ? this[field].getTime() / 1000
|
|---|
| 97 | : this[field]
|
|---|
| 98 | const s = ' ' +
|
|---|
| 99 | (field === 'dev' || field === 'ino' || field === 'nlink'
|
|---|
| 100 | ? 'SCHILY.' : '') +
|
|---|
| 101 | field + '=' + v + '\n'
|
|---|
| 102 | const byteLen = Buffer.byteLength(s)
|
|---|
| 103 | // the digits includes the length of the digits in ascii base-10
|
|---|
| 104 | // so if it's 9 characters, then adding 1 for the 9 makes it 10
|
|---|
| 105 | // which makes it 11 chars.
|
|---|
| 106 | let digits = Math.floor(Math.log(byteLen) / Math.log(10)) + 1
|
|---|
| 107 | if (byteLen + digits >= Math.pow(10, digits)) {
|
|---|
| 108 | digits += 1
|
|---|
| 109 | }
|
|---|
| 110 | const len = digits + byteLen
|
|---|
| 111 | return len + s
|
|---|
| 112 | }
|
|---|
| 113 | }
|
|---|
| 114 |
|
|---|
| 115 | Pax.parse = (string, ex, g) => new Pax(merge(parseKV(string), ex), g)
|
|---|
| 116 |
|
|---|
| 117 | const merge = (a, b) =>
|
|---|
| 118 | b ? Object.keys(a).reduce((s, k) => (s[k] = a[k], s), b) : a
|
|---|
| 119 |
|
|---|
| 120 | const parseKV = string =>
|
|---|
| 121 | string
|
|---|
| 122 | .replace(/\n$/, '')
|
|---|
| 123 | .split('\n')
|
|---|
| 124 | .reduce(parseKVLine, Object.create(null))
|
|---|
| 125 |
|
|---|
| 126 | const parseKVLine = (set, line) => {
|
|---|
| 127 | const n = parseInt(line, 10)
|
|---|
| 128 |
|
|---|
| 129 | // XXX Values with \n in them will fail this.
|
|---|
| 130 | // Refactor to not be a naive line-by-line parse.
|
|---|
| 131 | if (n !== Buffer.byteLength(line) + 1) {
|
|---|
| 132 | return set
|
|---|
| 133 | }
|
|---|
| 134 |
|
|---|
| 135 | line = line.slice((n + ' ').length)
|
|---|
| 136 | const kv = line.split('=')
|
|---|
| 137 | const k = kv.shift().replace(/^SCHILY\.(dev|ino|nlink)/, '$1')
|
|---|
| 138 | if (!k) {
|
|---|
| 139 | return set
|
|---|
| 140 | }
|
|---|
| 141 |
|
|---|
| 142 | const v = kv.join('=')
|
|---|
| 143 | set[k] = /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k)
|
|---|
| 144 | ? new Date(v * 1000)
|
|---|
| 145 | : /^[0-9]+$/.test(v) ? +v
|
|---|
| 146 | : v
|
|---|
| 147 | return set
|
|---|
| 148 | }
|
|---|
| 149 |
|
|---|
| 150 | module.exports = Pax
|
|---|