1'use strict' 2// parse a 512-byte header block to a data object, or vice-versa 3// encode returns `true` if a pax extended header is needed, because 4// the data could not be faithfully encoded in a simple header. 5// (Also, check header.needPax to see if it needs a pax header.) 6 7const types = require('./types.js') 8const pathModule = require('path').posix 9const large = require('./large-numbers.js') 10 11const SLURP = Symbol('slurp') 12const TYPE = Symbol('type') 13 14class Header { 15 constructor (data, off, ex, gex) { 16 this.cksumValid = false 17 this.needPax = false 18 this.nullBlock = false 19 20 this.block = null 21 this.path = null 22 this.mode = null 23 this.uid = null 24 this.gid = null 25 this.size = null 26 this.mtime = null 27 this.cksum = null 28 this[TYPE] = '0' 29 this.linkpath = null 30 this.uname = null 31 this.gname = null 32 this.devmaj = 0 33 this.devmin = 0 34 this.atime = null 35 this.ctime = null 36 37 if (Buffer.isBuffer(data)) { 38 this.decode(data, off || 0, ex, gex) 39 } else if (data) { 40 this.set(data) 41 } 42 } 43 44 decode (buf, off, ex, gex) { 45 if (!off) { 46 off = 0 47 } 48 49 if (!buf || !(buf.length >= off + 512)) { 50 throw new Error('need 512 bytes for header') 51 } 52 53 this.path = decString(buf, off, 100) 54 this.mode = decNumber(buf, off + 100, 8) 55 this.uid = decNumber(buf, off + 108, 8) 56 this.gid = decNumber(buf, off + 116, 8) 57 this.size = decNumber(buf, off + 124, 12) 58 this.mtime = decDate(buf, off + 136, 12) 59 this.cksum = decNumber(buf, off + 148, 12) 60 61 // if we have extended or global extended headers, apply them now 62 // See https://github.com/npm/node-tar/pull/187 63 this[SLURP](ex) 64 this[SLURP](gex, true) 65 66 // old tar versions marked dirs as a file with a trailing / 67 this[TYPE] = decString(buf, off + 156, 1) 68 if (this[TYPE] === '') { 69 this[TYPE] = '0' 70 } 71 if (this[TYPE] === '0' && this.path.slice(-1) === '/') { 72 this[TYPE] = '5' 73 } 74 75 // tar implementations sometimes incorrectly put the stat(dir).size 76 // as the size in the tarball, even though Directory entries are 77 // not able to have any body at all. In the very rare chance that 78 // it actually DOES have a body, we weren't going to do anything with 79 // it anyway, and it'll just be a warning about an invalid header. 80 if (this[TYPE] === '5') { 81 this.size = 0 82 } 83 84 this.linkpath = decString(buf, off + 157, 100) 85 if (buf.slice(off + 257, off + 265).toString() === 'ustar\u000000') { 86 this.uname = decString(buf, off + 265, 32) 87 this.gname = decString(buf, off + 297, 32) 88 this.devmaj = decNumber(buf, off + 329, 8) 89 this.devmin = decNumber(buf, off + 337, 8) 90 if (buf[off + 475] !== 0) { 91 // definitely a prefix, definitely >130 chars. 92 const prefix = decString(buf, off + 345, 155) 93 this.path = prefix + '/' + this.path 94 } else { 95 const prefix = decString(buf, off + 345, 130) 96 if (prefix) { 97 this.path = prefix + '/' + this.path 98 } 99 this.atime = decDate(buf, off + 476, 12) 100 this.ctime = decDate(buf, off + 488, 12) 101 } 102 } 103 104 let sum = 8 * 0x20 105 for (let i = off; i < off + 148; i++) { 106 sum += buf[i] 107 } 108 109 for (let i = off + 156; i < off + 512; i++) { 110 sum += buf[i] 111 } 112 113 this.cksumValid = sum === this.cksum 114 if (this.cksum === null && sum === 8 * 0x20) { 115 this.nullBlock = true 116 } 117 } 118 119 [SLURP] (ex, global) { 120 for (const k in ex) { 121 // we slurp in everything except for the path attribute in 122 // a global extended header, because that's weird. 123 if (ex[k] !== null && ex[k] !== undefined && 124 !(global && k === 'path')) { 125 this[k] = ex[k] 126 } 127 } 128 } 129 130 encode (buf, off) { 131 if (!buf) { 132 buf = this.block = Buffer.alloc(512) 133 off = 0 134 } 135 136 if (!off) { 137 off = 0 138 } 139 140 if (!(buf.length >= off + 512)) { 141 throw new Error('need 512 bytes for header') 142 } 143 144 const prefixSize = this.ctime || this.atime ? 130 : 155 145 const split = splitPrefix(this.path || '', prefixSize) 146 const path = split[0] 147 const prefix = split[1] 148 this.needPax = split[2] 149 150 this.needPax = encString(buf, off, 100, path) || this.needPax 151 this.needPax = encNumber(buf, off + 100, 8, this.mode) || this.needPax 152 this.needPax = encNumber(buf, off + 108, 8, this.uid) || this.needPax 153 this.needPax = encNumber(buf, off + 116, 8, this.gid) || this.needPax 154 this.needPax = encNumber(buf, off + 124, 12, this.size) || this.needPax 155 this.needPax = encDate(buf, off + 136, 12, this.mtime) || this.needPax 156 buf[off + 156] = this[TYPE].charCodeAt(0) 157 this.needPax = encString(buf, off + 157, 100, this.linkpath) || this.needPax 158 buf.write('ustar\u000000', off + 257, 8) 159 this.needPax = encString(buf, off + 265, 32, this.uname) || this.needPax 160 this.needPax = encString(buf, off + 297, 32, this.gname) || this.needPax 161 this.needPax = encNumber(buf, off + 329, 8, this.devmaj) || this.needPax 162 this.needPax = encNumber(buf, off + 337, 8, this.devmin) || this.needPax 163 this.needPax = encString(buf, off + 345, prefixSize, prefix) || this.needPax 164 if (buf[off + 475] !== 0) { 165 this.needPax = encString(buf, off + 345, 155, prefix) || this.needPax 166 } else { 167 this.needPax = encString(buf, off + 345, 130, prefix) || this.needPax 168 this.needPax = encDate(buf, off + 476, 12, this.atime) || this.needPax 169 this.needPax = encDate(buf, off + 488, 12, this.ctime) || this.needPax 170 } 171 172 let sum = 8 * 0x20 173 for (let i = off; i < off + 148; i++) { 174 sum += buf[i] 175 } 176 177 for (let i = off + 156; i < off + 512; i++) { 178 sum += buf[i] 179 } 180 181 this.cksum = sum 182 encNumber(buf, off + 148, 8, this.cksum) 183 this.cksumValid = true 184 185 return this.needPax 186 } 187 188 set (data) { 189 for (const i in data) { 190 if (data[i] !== null && data[i] !== undefined) { 191 this[i] = data[i] 192 } 193 } 194 } 195 196 get type () { 197 return types.name.get(this[TYPE]) || this[TYPE] 198 } 199 200 get typeKey () { 201 return this[TYPE] 202 } 203 204 set type (type) { 205 if (types.code.has(type)) { 206 this[TYPE] = types.code.get(type) 207 } else { 208 this[TYPE] = type 209 } 210 } 211} 212 213const splitPrefix = (p, prefixSize) => { 214 const pathSize = 100 215 let pp = p 216 let prefix = '' 217 let ret 218 const root = pathModule.parse(p).root || '.' 219 220 if (Buffer.byteLength(pp) < pathSize) { 221 ret = [pp, prefix, false] 222 } else { 223 // first set prefix to the dir, and path to the base 224 prefix = pathModule.dirname(pp) 225 pp = pathModule.basename(pp) 226 227 do { 228 if (Buffer.byteLength(pp) <= pathSize && 229 Buffer.byteLength(prefix) <= prefixSize) { 230 // both fit! 231 ret = [pp, prefix, false] 232 } else if (Buffer.byteLength(pp) > pathSize && 233 Buffer.byteLength(prefix) <= prefixSize) { 234 // prefix fits in prefix, but path doesn't fit in path 235 ret = [pp.slice(0, pathSize - 1), prefix, true] 236 } else { 237 // make path take a bit from prefix 238 pp = pathModule.join(pathModule.basename(prefix), pp) 239 prefix = pathModule.dirname(prefix) 240 } 241 } while (prefix !== root && !ret) 242 243 // at this point, found no resolution, just truncate 244 if (!ret) { 245 ret = [p.slice(0, pathSize - 1), '', true] 246 } 247 } 248 return ret 249} 250 251const decString = (buf, off, size) => 252 buf.slice(off, off + size).toString('utf8').replace(/\0.*/, '') 253 254const decDate = (buf, off, size) => 255 numToDate(decNumber(buf, off, size)) 256 257const numToDate = num => num === null ? null : new Date(num * 1000) 258 259const decNumber = (buf, off, size) => 260 buf[off] & 0x80 ? large.parse(buf.slice(off, off + size)) 261 : decSmallNumber(buf, off, size) 262 263const nanNull = value => isNaN(value) ? null : value 264 265const decSmallNumber = (buf, off, size) => 266 nanNull(parseInt( 267 buf.slice(off, off + size) 268 .toString('utf8').replace(/\0.*$/, '').trim(), 8)) 269 270// the maximum encodable as a null-terminated octal, by field size 271const MAXNUM = { 272 12: 0o77777777777, 273 8: 0o7777777, 274} 275 276const encNumber = (buf, off, size, number) => 277 number === null ? false : 278 number > MAXNUM[size] || number < 0 279 ? (large.encode(number, buf.slice(off, off + size)), true) 280 : (encSmallNumber(buf, off, size, number), false) 281 282const encSmallNumber = (buf, off, size, number) => 283 buf.write(octalString(number, size), off, size, 'ascii') 284 285const octalString = (number, size) => 286 padOctal(Math.floor(number).toString(8), size) 287 288const padOctal = (string, size) => 289 (string.length === size - 1 ? string 290 : new Array(size - string.length - 1).join('0') + string + ' ') + '\0' 291 292const encDate = (buf, off, size, date) => 293 date === null ? false : 294 encNumber(buf, off, size, date.getTime() / 1000) 295 296// enough to fill the longest string we've got 297const NULLS = new Array(156).join('\0') 298// pad with nulls, return true if it's longer or non-ascii 299const encString = (buf, off, size, string) => 300 string === null ? false : 301 (buf.write(string + NULLS, off, size, 'utf8'), 302 string.length !== Buffer.byteLength(string) || string.length > size) 303 304module.exports = Header 305