Code coverage report for tar/lib/header.js

# All files / tar/lib header.js

100% Statements 161/161
100% Branches 120/120
100% Functions 20/20
100% Lines 159/159

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273

    13x
    13x
    13x

    13x

    13121x
    13121x
    13121x

    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x
    13121x

    13121x
    6393x
    6728x
    6726x

    6393x
    2795x

    6393x
    1x

    6392x
    6392x
    6392x
    6392x
    6392x
    6392x
    6392x

    6392x
    6392x
    1990x
    6392x
    2x

    6392x
    1594x

    6392x
    6392x
    4275x
    4275x
    4275x
    4275x
    4275x

    2x
    2x

    4273x
    4273x
    295x
    4273x
    4273x

    6392x
    6392x
    946016x

    6392x
    2275552x

    6392x
    6392x
    1860x

    6719x
    6600x
    6600x

    6719x
    6717x

    6719x
    1x

    6718x
    6718x
    6718x
    6718x
    6718x

    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    6718x
    2x

    6716x
    6716x
    6716x

    6718x
    6718x
    994264x

    6718x
    2391608x

    6718x
    6718x
    6718x

    6718x

    6728x
    73451x
    66740x

    8178x

    4x

    6723x
    6719x

    4x

    13x
    6718x
    6718x
    6718x

    6718x

    6718x
    6683x

    35x
    35x

    35x

    49x

    21x

    28x

    13x

    15x
    15x

    35x
    1x

    6718x

    13x
    32001x

    13x
    14938x

    14938x

    13x
    55448x

    55411x

    13x
    55411x

    13x

    13x
    66910x

    13x
    66615x

    13x
    66615x

    13x
    66615x

    13x
    20150x

    13x

    13x
    40308x

    13x
     'use strict'
    // parse a 512-byte header block to a data object, or vice-versa
    // encode returns `true` if a pax extended header is needed, because
    // the data could not be faithfully encoded in a simple header.
    // (Also, check header.needPax to see if it needs a pax header.)

    const types = require('./types.js')
    const pathModule = require('path')
    const large = require('./large-numbers.js')

    const TYPE = Symbol('type')

    class Header {
      constructor (data, off) {
        this.cksumValid = false
        this.needPax = false
        this.nullBlock = false

        this.block = null
        this.path = null
        this.mode = null
        this.uid = null
        this.gid = null
        this.size = null
        this.mtime = null
        this.cksum = null
        this[TYPE] = '0'
        this.linkpath = null
        this.uname = null
        this.gname = null
        this.devmaj = 0
        this.devmin = 0
        this.atime = null
        this.ctime = null

        if (Buffer.isBuffer(data)) {
          this.decode(data, off || 0)
        } else if (data)
          this.set(data)
      }

      decode (buf, off) {
        if (!off)
          off = 0

        if (!buf || !(buf.length >= off + 512))
          throw new Error('need 512 bytes for header')

        this.path = decString(buf, off, 100)
        this.mode = decNumber(buf, off + 100, 8)
        this.uid = decNumber(buf, off + 108, 8)
        this.gid = decNumber(buf, off + 116, 8)
        this.size = decNumber(buf, off + 124, 12)
        this.mtime = decDate(buf, off + 136, 12)
        this.cksum = decNumber(buf, off + 148, 12)

        // old tar versions marked dirs as a file with a trailing /
        this[TYPE] = decString(buf, off + 156, 1)
        if (this[TYPE] === '')
          this[TYPE] = '0'
        if (this[TYPE] === '0' && this.path.substr(-1) === '/')
          this[TYPE] = '5'

        // tar implementations sometimes incorrectly put the stat(dir).size
        // as the size in the tarball, even though Directory entries are
        // not able to have any body at all.  In the very rare chance that
        // it actually DOES have a body, we weren't going to do anything with
        // it anyway, and it'll just be a warning about an invalid header.
        if (this[TYPE] === '5')
          this.size = 0

        this.linkpath = decString(buf, off + 157, 100)
        if (buf.slice(off + 257, off + 265).toString() === 'ustar\u000000') {
          this.uname = decString(buf, off + 265, 32)
          this.gname = decString(buf, off + 297, 32)
          this.devmaj = decNumber(buf, off + 329, 8)
          this.devmin = decNumber(buf, off + 337, 8)
          if (buf[off + 475] !== 0) {
            // definitely a prefix, definitely >130 chars.
            const prefix = decString(buf, off + 345, 155)
            this.path = prefix + '/' + this.path
          } else {
            const prefix = decString(buf, off + 345, 130)
            if (prefix)
              this.path = prefix + '/' + this.path
            this.atime = decDate(buf, off + 476, 12)
            this.ctime = decDate(buf, off + 488, 12)
          }
        }

        let sum = 8 * 0x20
        for (let i = off; i < off + 148; i++) {
          sum += buf[i]
        }
        for (let i = off + 156; i < off + 512; i++) {
          sum += buf[i]
        }
        this.cksumValid = sum === this.cksum
        if (this.cksum === null && sum === 8 * 0x20)
          this.nullBlock = true
      }

      encode (buf, off) {
        if (!buf) {
          buf = this.block = Buffer.alloc(512)
          off = 0
        }

        if (!off)
          off = 0

        if (!(buf.length >= off + 512))
          throw new Error('need 512 bytes for header')

        const prefixSize = this.ctime || this.atime ? 130 : 155
        const split = splitPrefix(this.path || '', prefixSize)
        const path = split[0]
        const prefix = split[1]
        this.needPax = split[2]

        this.needPax = encString(buf, off, 100, path) || this.needPax
        this.needPax = encNumber(buf, off + 100, 8, this.mode) || this.needPax
        this.needPax = encNumber(buf, off + 108, 8, this.uid) || this.needPax
        this.needPax = encNumber(buf, off + 116, 8, this.gid) || this.needPax
        this.needPax = encNumber(buf, off + 124, 12, this.size) || this.needPax
        this.needPax = encDate(buf, off + 136, 12, this.mtime) || this.needPax
        buf[off + 156] = this[TYPE].charCodeAt(0)
        this.needPax = encString(buf, off + 157, 100, this.linkpath) || this.needPax
        buf.write('ustar\u000000', off + 257, 8)
        this.needPax = encString(buf, off + 265, 32, this.uname) || this.needPax
        this.needPax = encString(buf, off + 297, 32, this.gname) || this.needPax
        this.needPax = encNumber(buf, off + 329, 8, this.devmaj) || this.needPax
        this.needPax = encNumber(buf, off + 337, 8, this.devmin) || this.needPax
        this.needPax = encString(buf, off + 345, prefixSize, prefix) || this.needPax
        if (buf[off + 475] !== 0)
          this.needPax = encString(buf, off + 345, 155, prefix) || this.needPax
        else {
          this.needPax = encString(buf, off + 345, 130, prefix) || this.needPax
          this.needPax = encDate(buf, off + 476, 12, this.atime) || this.needPax
          this.needPax = encDate(buf, off + 488, 12, this.ctime) || this.needPax
        }

        let sum = 8 * 0x20
        for (let i = off; i < off + 148; i++) {
          sum += buf[i]
        }
        for (let i = off + 156; i < off + 512; i++) {
          sum += buf[i]
        }
        this.cksum = sum
        encNumber(buf, off + 148, 8, this.cksum)
        this.cksumValid = true

        return this.needPax
      }

      set (data) {
        for (let i in data) {
          if (data[i] !== null && data[i] !== undefined)
            this[i] = data[i]
        }
      }

      get type () {
        return types.name.get(this[TYPE]) || this[TYPE]
      }

      get typeKey () {
        return this[TYPE]
      }

      set type (type) {
        if (types.code.has(type))
          this[TYPE] = types.code.get(type)
        else
          this[TYPE] = type
      }
    }

    const splitPrefix = (p, prefixSize) => {
      const pathSize = 100
      let pp = p
      let prefix = ''
      let ret
      const root = pathModule.parse(p).root || '.'

      if (Buffer.byteLength(pp) < pathSize)
        ret = [pp, prefix, false]
      else {
        // first set prefix to the dir, and path to the base
        prefix = pathModule.dirname(pp)
        pp = pathModule.basename(pp)

        do {
          // both fit!
          if (Buffer.byteLength(pp) <= pathSize &&
              Buffer.byteLength(prefix) <= prefixSize)
            ret = [pp, prefix, false]

          // prefix fits in prefix, but path doesn't fit in path
          else if (Buffer.byteLength(pp) > pathSize &&
              Buffer.byteLength(prefix) <= prefixSize)
            ret = [pp.substr(0, pathSize - 1), prefix, true]

          else {
            // make path take a bit from prefix
            pp = pathModule.join(pathModule.basename(prefix), pp)
            prefix = pathModule.dirname(prefix)
          }
        } while (prefix !== root && !ret)

        // at this point, found no resolution, just truncate
        if (!ret)
          ret = [p.substr(0, pathSize - 1), '', true]
      }
      return ret
    }

    const decString = (buf, off, size) =>
      buf.slice(off, off + size).toString('utf8').replace(/\0.*/, '')

    const decDate = (buf, off, size) =>
      numToDate(decNumber(buf, off, size))

    const numToDate = num => num === null ? null : new Date(num * 1000)

    const decNumber = (buf, off, size) =>
      buf[off] & 0x80 ? large.parse(buf.slice(off, off + size))
        : decSmallNumber(buf, off, size)

    const nanNull = value => isNaN(value) ? null : value

    const decSmallNumber = (buf, off, size) =>
      nanNull(parseInt(
        buf.slice(off, off + size)
          .toString('utf8').replace(/\0.*$/, '').trim(), 8))

    // the maximum encodable as a null-terminated octal, by field size
    const MAXNUM = {
      12: 0o77777777777,
      8 : 0o7777777
    }

    const encNumber = (buf, off, size, number) =>
      number === null ? false :
      number > MAXNUM[size] || number < 0
        ? (large.encode(number, buf.slice(off, off + size)), true)
        : (encSmallNumber(buf, off, size, number), false)

    const encSmallNumber = (buf, off, size, number) =>
      buf.write(octalString(number, size), off, size, 'ascii')

    const octalString = (number, size) =>
      padOctal(Math.floor(number).toString(8), size)

    const padOctal = (string, size) =>
      (string.length === size - 1 ? string
      : new Array(size - string.length - 1).join('0') + string + ' ') + '\0'

    const encDate = (buf, off, size, date) =>
      date === null ? false :
      encNumber(buf, off, size, date.getTime() / 1000)

    // enough to fill the longest string we've got
    const NULLS = new Array(156).join('\0')
    // pad with nulls, return true if it's longer or non-ascii
    const encString = (buf, off, size, string) =>
      string === null ? false :
      (buf.write(string + NULLS, off, size, 'utf8'),
       string.length !== Buffer.byteLength(string) || string.length > size)

    module.exports = Header