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