You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					151 lines
				
				4.0 KiB
			
		
		
			
		
	
	
					151 lines
				
				4.0 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								const Header = require('./header.js')
							 | 
						||
| 
								 | 
							
								const path = require('path')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Pax {
							 | 
						||
| 
								 | 
							
								  constructor (obj, global) {
							 | 
						||
| 
								 | 
							
								    this.atime = obj.atime || null
							 | 
						||
| 
								 | 
							
								    this.charset = obj.charset || null
							 | 
						||
| 
								 | 
							
								    this.comment = obj.comment || null
							 | 
						||
| 
								 | 
							
								    this.ctime = obj.ctime || null
							 | 
						||
| 
								 | 
							
								    this.gid = obj.gid || null
							 | 
						||
| 
								 | 
							
								    this.gname = obj.gname || null
							 | 
						||
| 
								 | 
							
								    this.linkpath = obj.linkpath || null
							 | 
						||
| 
								 | 
							
								    this.mtime = obj.mtime || null
							 | 
						||
| 
								 | 
							
								    this.path = obj.path || null
							 | 
						||
| 
								 | 
							
								    this.size = obj.size || null
							 | 
						||
| 
								 | 
							
								    this.uid = obj.uid || null
							 | 
						||
| 
								 | 
							
								    this.uname = obj.uname || null
							 | 
						||
| 
								 | 
							
								    this.dev = obj.dev || null
							 | 
						||
| 
								 | 
							
								    this.ino = obj.ino || null
							 | 
						||
| 
								 | 
							
								    this.nlink = obj.nlink || null
							 | 
						||
| 
								 | 
							
								    this.global = global || false
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  encode () {
							 | 
						||
| 
								 | 
							
								    const body = this.encodeBody()
							 | 
						||
| 
								 | 
							
								    if (body === '') {
							 | 
						||
| 
								 | 
							
								      return null
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const bodyLen = Buffer.byteLength(body)
							 | 
						||
| 
								 | 
							
								    // round up to 512 bytes
							 | 
						||
| 
								 | 
							
								    // add 512 for header
							 | 
						||
| 
								 | 
							
								    const bufLen = 512 * Math.ceil(1 + bodyLen / 512)
							 | 
						||
| 
								 | 
							
								    const buf = Buffer.allocUnsafe(bufLen)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // 0-fill the header section, it might not hit every field
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < 512; i++) {
							 | 
						||
| 
								 | 
							
								      buf[i] = 0
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    new Header({
							 | 
						||
| 
								 | 
							
								      // XXX split the path
							 | 
						||
| 
								 | 
							
								      // then the path should be PaxHeader + basename, but less than 99,
							 | 
						||
| 
								 | 
							
								      // prepend with the dirname
							 | 
						||
| 
								 | 
							
								      path: ('PaxHeader/' + path.basename(this.path)).slice(0, 99),
							 | 
						||
| 
								 | 
							
								      mode: this.mode || 0o644,
							 | 
						||
| 
								 | 
							
								      uid: this.uid || null,
							 | 
						||
| 
								 | 
							
								      gid: this.gid || null,
							 | 
						||
| 
								 | 
							
								      size: bodyLen,
							 | 
						||
| 
								 | 
							
								      mtime: this.mtime || null,
							 | 
						||
| 
								 | 
							
								      type: this.global ? 'GlobalExtendedHeader' : 'ExtendedHeader',
							 | 
						||
| 
								 | 
							
								      linkpath: '',
							 | 
						||
| 
								 | 
							
								      uname: this.uname || '',
							 | 
						||
| 
								 | 
							
								      gname: this.gname || '',
							 | 
						||
| 
								 | 
							
								      devmaj: 0,
							 | 
						||
| 
								 | 
							
								      devmin: 0,
							 | 
						||
| 
								 | 
							
								      atime: this.atime || null,
							 | 
						||
| 
								 | 
							
								      ctime: this.ctime || null,
							 | 
						||
| 
								 | 
							
								    }).encode(buf)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    buf.write(body, 512, bodyLen, 'utf8')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // null pad after the body
							 | 
						||
| 
								 | 
							
								    for (let i = bodyLen + 512; i < buf.length; i++) {
							 | 
						||
| 
								 | 
							
								      buf[i] = 0
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return buf
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  encodeBody () {
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								      this.encodeField('path') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('ctime') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('atime') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('dev') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('ino') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('nlink') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('charset') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('comment') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('gid') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('gname') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('linkpath') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('mtime') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('size') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('uid') +
							 | 
						||
| 
								 | 
							
								      this.encodeField('uname')
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  encodeField (field) {
							 | 
						||
| 
								 | 
							
								    if (this[field] === null || this[field] === undefined) {
							 | 
						||
| 
								 | 
							
								      return ''
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const v = this[field] instanceof Date ? this[field].getTime() / 1000
							 | 
						||
| 
								 | 
							
								      : this[field]
							 | 
						||
| 
								 | 
							
								    const s = ' ' +
							 | 
						||
| 
								 | 
							
								      (field === 'dev' || field === 'ino' || field === 'nlink'
							 | 
						||
| 
								 | 
							
								        ? 'SCHILY.' : '') +
							 | 
						||
| 
								 | 
							
								      field + '=' + v + '\n'
							 | 
						||
| 
								 | 
							
								    const byteLen = Buffer.byteLength(s)
							 | 
						||
| 
								 | 
							
								    // the digits includes the length of the digits in ascii base-10
							 | 
						||
| 
								 | 
							
								    // so if it's 9 characters, then adding 1 for the 9 makes it 10
							 | 
						||
| 
								 | 
							
								    // which makes it 11 chars.
							 | 
						||
| 
								 | 
							
								    let digits = Math.floor(Math.log(byteLen) / Math.log(10)) + 1
							 | 
						||
| 
								 | 
							
								    if (byteLen + digits >= Math.pow(10, digits)) {
							 | 
						||
| 
								 | 
							
								      digits += 1
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const len = digits + byteLen
							 | 
						||
| 
								 | 
							
								    return len + s
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Pax.parse = (string, ex, g) => new Pax(merge(parseKV(string), ex), g)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const merge = (a, b) =>
							 | 
						||
| 
								 | 
							
								  b ? Object.keys(a).reduce((s, k) => (s[k] = a[k], s), b) : a
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const parseKV = string =>
							 | 
						||
| 
								 | 
							
								  string
							 | 
						||
| 
								 | 
							
								    .replace(/\n$/, '')
							 | 
						||
| 
								 | 
							
								    .split('\n')
							 | 
						||
| 
								 | 
							
								    .reduce(parseKVLine, Object.create(null))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const parseKVLine = (set, line) => {
							 | 
						||
| 
								 | 
							
								  const n = parseInt(line, 10)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // XXX Values with \n in them will fail this.
							 | 
						||
| 
								 | 
							
								  // Refactor to not be a naive line-by-line parse.
							 | 
						||
| 
								 | 
							
								  if (n !== Buffer.byteLength(line) + 1) {
							 | 
						||
| 
								 | 
							
								    return set
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  line = line.slice((n + ' ').length)
							 | 
						||
| 
								 | 
							
								  const kv = line.split('=')
							 | 
						||
| 
								 | 
							
								  const k = kv.shift().replace(/^SCHILY\.(dev|ino|nlink)/, '$1')
							 | 
						||
| 
								 | 
							
								  if (!k) {
							 | 
						||
| 
								 | 
							
								    return set
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const v = kv.join('=')
							 | 
						||
| 
								 | 
							
								  set[k] = /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k)
							 | 
						||
| 
								 | 
							
								    ? new Date(v * 1000)
							 | 
						||
| 
								 | 
							
								    : /^[0-9]+$/.test(v) ? +v
							 | 
						||
| 
								 | 
							
								    : v
							 | 
						||
| 
								 | 
							
								  return set
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = Pax
							 |