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