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