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