mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-03 19:07:00 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			438 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			438 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * node-compress-commons
 | 
						|
 *
 | 
						|
 * Copyright (c) 2014 Chris Talkington, contributors.
 | 
						|
 * Licensed under the MIT license.
 | 
						|
 * https://github.com/archiverjs/node-compress-commons/blob/master/LICENSE-MIT
 | 
						|
 */
 | 
						|
var inherits = require('util').inherits;
 | 
						|
var crc32 = require('../../../buffer-crc32');
 | 
						|
var { CRC32Stream } = require('../../../crc32-stream');
 | 
						|
var { DeflateCRC32Stream } = require('../../../crc32-stream');
 | 
						|
 | 
						|
var ArchiveOutputStream = require('../archive-output-stream');
 | 
						|
 | 
						|
var constants = require('./constants');
 | 
						|
var zipUtil = require('./util');
 | 
						|
 | 
						|
var ZipArchiveOutputStream = module.exports = function (options) {
 | 
						|
  if (!(this instanceof ZipArchiveOutputStream)) {
 | 
						|
    return new ZipArchiveOutputStream(options);
 | 
						|
  }
 | 
						|
 | 
						|
  options = this.options = this._defaults(options);
 | 
						|
 | 
						|
  ArchiveOutputStream.call(this, options);
 | 
						|
 | 
						|
  this._entry = null;
 | 
						|
  this._entries = [];
 | 
						|
  this._archive = {
 | 
						|
    centralLength: 0,
 | 
						|
    centralOffset: 0,
 | 
						|
    comment: '',
 | 
						|
    finish: false,
 | 
						|
    finished: false,
 | 
						|
    processing: false,
 | 
						|
    forceZip64: options.forceZip64,
 | 
						|
    forceLocalTime: options.forceLocalTime
 | 
						|
  };
 | 
						|
};
 | 
						|
 | 
						|
inherits(ZipArchiveOutputStream, ArchiveOutputStream);
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._afterAppend = function (ae) {
 | 
						|
  this._entries.push(ae);
 | 
						|
 | 
						|
  if (ae.getGeneralPurposeBit().usesDataDescriptor()) {
 | 
						|
    this._writeDataDescriptor(ae);
 | 
						|
  }
 | 
						|
 | 
						|
  this._archive.processing = false;
 | 
						|
  this._entry = null;
 | 
						|
 | 
						|
  if (this._archive.finish && !this._archive.finished) {
 | 
						|
    this._finish();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._appendBuffer = function (ae, source, callback) {
 | 
						|
  if (source.length === 0) {
 | 
						|
    ae.setMethod(constants.METHOD_STORED);
 | 
						|
  }
 | 
						|
 | 
						|
  var method = ae.getMethod();
 | 
						|
 | 
						|
  if (method === constants.METHOD_STORED) {
 | 
						|
    ae.setSize(source.length);
 | 
						|
    ae.setCompressedSize(source.length);
 | 
						|
    ae.setCrc(crc32.unsigned(source));
 | 
						|
  }
 | 
						|
 | 
						|
  this._writeLocalFileHeader(ae);
 | 
						|
 | 
						|
  if (method === constants.METHOD_STORED) {
 | 
						|
    this.write(source);
 | 
						|
    this._afterAppend(ae);
 | 
						|
    callback(null, ae);
 | 
						|
    return;
 | 
						|
  } else if (method === constants.METHOD_DEFLATED) {
 | 
						|
    this._smartStream(ae, callback).end(source);
 | 
						|
    return;
 | 
						|
  } else {
 | 
						|
    callback(new Error('compression method ' + method + ' not implemented'));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._appendStream = function (ae, source, callback) {
 | 
						|
  ae.getGeneralPurposeBit().useDataDescriptor(true);
 | 
						|
  ae.setVersionNeededToExtract(constants.MIN_VERSION_DATA_DESCRIPTOR);
 | 
						|
 | 
						|
  this._writeLocalFileHeader(ae);
 | 
						|
 | 
						|
  var smart = this._smartStream(ae, callback);
 | 
						|
  source.once('error', function (err) {
 | 
						|
    smart.emit('error', err);
 | 
						|
    smart.end();
 | 
						|
  })
 | 
						|
  source.pipe(smart);
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._defaults = function (o) {
 | 
						|
  if (typeof o !== 'object') {
 | 
						|
    o = {};
 | 
						|
  }
 | 
						|
 | 
						|
  if (typeof o.zlib !== 'object') {
 | 
						|
    o.zlib = {};
 | 
						|
  }
 | 
						|
 | 
						|
  if (typeof o.zlib.level !== 'number') {
 | 
						|
    o.zlib.level = constants.ZLIB_BEST_SPEED;
 | 
						|
  }
 | 
						|
 | 
						|
  o.forceZip64 = !!o.forceZip64;
 | 
						|
  o.forceLocalTime = !!o.forceLocalTime;
 | 
						|
 | 
						|
  return o;
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._finish = function () {
 | 
						|
  this._archive.centralOffset = this.offset;
 | 
						|
 | 
						|
  this._entries.forEach(function (ae) {
 | 
						|
    this._writeCentralFileHeader(ae);
 | 
						|
  }.bind(this));
 | 
						|
 | 
						|
  this._archive.centralLength = this.offset - this._archive.centralOffset;
 | 
						|
 | 
						|
  if (this.isZip64()) {
 | 
						|
    this._writeCentralDirectoryZip64();
 | 
						|
  }
 | 
						|
 | 
						|
  this._writeCentralDirectoryEnd();
 | 
						|
 | 
						|
  this._archive.processing = false;
 | 
						|
  this._archive.finish = true;
 | 
						|
  this._archive.finished = true;
 | 
						|
  this.end();
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._normalizeEntry = function (ae) {
 | 
						|
  if (ae.getMethod() === -1) {
 | 
						|
    ae.setMethod(constants.METHOD_DEFLATED);
 | 
						|
  }
 | 
						|
 | 
						|
  if (ae.getMethod() === constants.METHOD_DEFLATED) {
 | 
						|
    ae.getGeneralPurposeBit().useDataDescriptor(true);
 | 
						|
    ae.setVersionNeededToExtract(constants.MIN_VERSION_DATA_DESCRIPTOR);
 | 
						|
  }
 | 
						|
 | 
						|
  if (ae.getTime() === -1) {
 | 
						|
    ae.setTime(new Date(), this._archive.forceLocalTime);
 | 
						|
  }
 | 
						|
 | 
						|
  ae._offsets = {
 | 
						|
    file: 0,
 | 
						|
    data: 0,
 | 
						|
    contents: 0,
 | 
						|
  };
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._smartStream = function (ae, callback) {
 | 
						|
  var deflate = ae.getMethod() === constants.METHOD_DEFLATED;
 | 
						|
  var process = deflate ? new DeflateCRC32Stream(this.options.zlib) : new CRC32Stream();
 | 
						|
  var error = null;
 | 
						|
 | 
						|
  function handleStuff() {
 | 
						|
    var digest = process.digest().readUInt32BE(0);
 | 
						|
    ae.setCrc(digest);
 | 
						|
    ae.setSize(process.size());
 | 
						|
    ae.setCompressedSize(process.size(true));
 | 
						|
    this._afterAppend(ae);
 | 
						|
    callback(error, ae);
 | 
						|
  }
 | 
						|
 | 
						|
  process.once('end', handleStuff.bind(this));
 | 
						|
  process.once('error', function (err) {
 | 
						|
    error = err;
 | 
						|
  });
 | 
						|
 | 
						|
  process.pipe(this, { end: false });
 | 
						|
 | 
						|
  return process;
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._writeCentralDirectoryEnd = function () {
 | 
						|
  var records = this._entries.length;
 | 
						|
  var size = this._archive.centralLength;
 | 
						|
  var offset = this._archive.centralOffset;
 | 
						|
 | 
						|
  if (this.isZip64()) {
 | 
						|
    records = constants.ZIP64_MAGIC_SHORT;
 | 
						|
    size = constants.ZIP64_MAGIC;
 | 
						|
    offset = constants.ZIP64_MAGIC;
 | 
						|
  }
 | 
						|
 | 
						|
  // signature
 | 
						|
  this.write(zipUtil.getLongBytes(constants.SIG_EOCD));
 | 
						|
 | 
						|
  // disk numbers
 | 
						|
  this.write(constants.SHORT_ZERO);
 | 
						|
  this.write(constants.SHORT_ZERO);
 | 
						|
 | 
						|
  // number of entries
 | 
						|
  this.write(zipUtil.getShortBytes(records));
 | 
						|
  this.write(zipUtil.getShortBytes(records));
 | 
						|
 | 
						|
  // length and location of CD
 | 
						|
  this.write(zipUtil.getLongBytes(size));
 | 
						|
  this.write(zipUtil.getLongBytes(offset));
 | 
						|
 | 
						|
  // archive comment
 | 
						|
  var comment = this.getComment();
 | 
						|
  var commentLength = Buffer.byteLength(comment);
 | 
						|
  this.write(zipUtil.getShortBytes(commentLength));
 | 
						|
  this.write(comment);
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._writeCentralDirectoryZip64 = function () {
 | 
						|
  // signature
 | 
						|
  this.write(zipUtil.getLongBytes(constants.SIG_ZIP64_EOCD));
 | 
						|
 | 
						|
  // size of the ZIP64 EOCD record
 | 
						|
  this.write(zipUtil.getEightBytes(44));
 | 
						|
 | 
						|
  // version made by
 | 
						|
  this.write(zipUtil.getShortBytes(constants.MIN_VERSION_ZIP64));
 | 
						|
 | 
						|
  // version to extract
 | 
						|
  this.write(zipUtil.getShortBytes(constants.MIN_VERSION_ZIP64));
 | 
						|
 | 
						|
  // disk numbers
 | 
						|
  this.write(constants.LONG_ZERO);
 | 
						|
  this.write(constants.LONG_ZERO);
 | 
						|
 | 
						|
  // number of entries
 | 
						|
  this.write(zipUtil.getEightBytes(this._entries.length));
 | 
						|
  this.write(zipUtil.getEightBytes(this._entries.length));
 | 
						|
 | 
						|
  // length and location of CD
 | 
						|
  this.write(zipUtil.getEightBytes(this._archive.centralLength));
 | 
						|
  this.write(zipUtil.getEightBytes(this._archive.centralOffset));
 | 
						|
 | 
						|
  // extensible data sector
 | 
						|
  // not implemented at this time
 | 
						|
 | 
						|
  // end of central directory locator
 | 
						|
  this.write(zipUtil.getLongBytes(constants.SIG_ZIP64_EOCD_LOC));
 | 
						|
 | 
						|
  // disk number holding the ZIP64 EOCD record
 | 
						|
  this.write(constants.LONG_ZERO);
 | 
						|
 | 
						|
  // relative offset of the ZIP64 EOCD record
 | 
						|
  this.write(zipUtil.getEightBytes(this._archive.centralOffset + this._archive.centralLength));
 | 
						|
 | 
						|
  // total number of disks
 | 
						|
  this.write(zipUtil.getLongBytes(1));
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._writeCentralFileHeader = function (ae) {
 | 
						|
  var gpb = ae.getGeneralPurposeBit();
 | 
						|
  var method = ae.getMethod();
 | 
						|
  var offsets = ae._offsets;
 | 
						|
 | 
						|
  var size = ae.getSize();
 | 
						|
  var compressedSize = ae.getCompressedSize();
 | 
						|
 | 
						|
  if (ae.isZip64() || offsets.file > constants.ZIP64_MAGIC) {
 | 
						|
    size = constants.ZIP64_MAGIC;
 | 
						|
    compressedSize = constants.ZIP64_MAGIC;
 | 
						|
 | 
						|
    ae.setVersionNeededToExtract(constants.MIN_VERSION_ZIP64);
 | 
						|
 | 
						|
    var extraBuf = Buffer.concat([
 | 
						|
      zipUtil.getShortBytes(constants.ZIP64_EXTRA_ID),
 | 
						|
      zipUtil.getShortBytes(24),
 | 
						|
      zipUtil.getEightBytes(ae.getSize()),
 | 
						|
      zipUtil.getEightBytes(ae.getCompressedSize()),
 | 
						|
      zipUtil.getEightBytes(offsets.file)
 | 
						|
    ], 28);
 | 
						|
 | 
						|
    ae.setExtra(extraBuf);
 | 
						|
  }
 | 
						|
 | 
						|
  // signature
 | 
						|
  this.write(zipUtil.getLongBytes(constants.SIG_CFH));
 | 
						|
 | 
						|
  // version made by
 | 
						|
  this.write(zipUtil.getShortBytes((ae.getPlatform() << 8) | constants.VERSION_MADEBY));
 | 
						|
 | 
						|
  // version to extract and general bit flag
 | 
						|
  this.write(zipUtil.getShortBytes(ae.getVersionNeededToExtract()));
 | 
						|
  this.write(gpb.encode());
 | 
						|
 | 
						|
  // compression method
 | 
						|
  this.write(zipUtil.getShortBytes(method));
 | 
						|
 | 
						|
  // datetime
 | 
						|
  this.write(zipUtil.getLongBytes(ae.getTimeDos()));
 | 
						|
 | 
						|
  // crc32 checksum
 | 
						|
  this.write(zipUtil.getLongBytes(ae.getCrc()));
 | 
						|
 | 
						|
  // sizes
 | 
						|
  this.write(zipUtil.getLongBytes(compressedSize));
 | 
						|
  this.write(zipUtil.getLongBytes(size));
 | 
						|
 | 
						|
  var name = ae.getName();
 | 
						|
  var comment = ae.getComment();
 | 
						|
  var extra = ae.getCentralDirectoryExtra();
 | 
						|
 | 
						|
  if (gpb.usesUTF8ForNames()) {
 | 
						|
    name = Buffer.from(name);
 | 
						|
    comment = Buffer.from(comment);
 | 
						|
  }
 | 
						|
 | 
						|
  // name length
 | 
						|
  this.write(zipUtil.getShortBytes(name.length));
 | 
						|
 | 
						|
  // extra length
 | 
						|
  this.write(zipUtil.getShortBytes(extra.length));
 | 
						|
 | 
						|
  // comments length
 | 
						|
  this.write(zipUtil.getShortBytes(comment.length));
 | 
						|
 | 
						|
  // disk number start
 | 
						|
  this.write(constants.SHORT_ZERO);
 | 
						|
 | 
						|
  // internal attributes
 | 
						|
  this.write(zipUtil.getShortBytes(ae.getInternalAttributes()));
 | 
						|
 | 
						|
  // external attributes
 | 
						|
  this.write(zipUtil.getLongBytes(ae.getExternalAttributes()));
 | 
						|
 | 
						|
  // relative offset of LFH
 | 
						|
  if (offsets.file > constants.ZIP64_MAGIC) {
 | 
						|
    this.write(zipUtil.getLongBytes(constants.ZIP64_MAGIC));
 | 
						|
  } else {
 | 
						|
    this.write(zipUtil.getLongBytes(offsets.file));
 | 
						|
  }
 | 
						|
 | 
						|
  // name
 | 
						|
  this.write(name);
 | 
						|
 | 
						|
  // extra
 | 
						|
  this.write(extra);
 | 
						|
 | 
						|
  // comment
 | 
						|
  this.write(comment);
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._writeDataDescriptor = function (ae) {
 | 
						|
  // signature
 | 
						|
  this.write(zipUtil.getLongBytes(constants.SIG_DD));
 | 
						|
 | 
						|
  // crc32 checksum
 | 
						|
  this.write(zipUtil.getLongBytes(ae.getCrc()));
 | 
						|
 | 
						|
  // sizes
 | 
						|
  if (ae.isZip64()) {
 | 
						|
    this.write(zipUtil.getEightBytes(ae.getCompressedSize()));
 | 
						|
    this.write(zipUtil.getEightBytes(ae.getSize()));
 | 
						|
  } else {
 | 
						|
    this.write(zipUtil.getLongBytes(ae.getCompressedSize()));
 | 
						|
    this.write(zipUtil.getLongBytes(ae.getSize()));
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype._writeLocalFileHeader = function (ae) {
 | 
						|
  var gpb = ae.getGeneralPurposeBit();
 | 
						|
  var method = ae.getMethod();
 | 
						|
  var name = ae.getName();
 | 
						|
  var extra = ae.getLocalFileDataExtra();
 | 
						|
 | 
						|
  if (ae.isZip64()) {
 | 
						|
    gpb.useDataDescriptor(true);
 | 
						|
    ae.setVersionNeededToExtract(constants.MIN_VERSION_ZIP64);
 | 
						|
  }
 | 
						|
 | 
						|
  if (gpb.usesUTF8ForNames()) {
 | 
						|
    name = Buffer.from(name);
 | 
						|
  }
 | 
						|
 | 
						|
  ae._offsets.file = this.offset;
 | 
						|
 | 
						|
  // signature
 | 
						|
  this.write(zipUtil.getLongBytes(constants.SIG_LFH));
 | 
						|
 | 
						|
  // version to extract and general bit flag
 | 
						|
  this.write(zipUtil.getShortBytes(ae.getVersionNeededToExtract()));
 | 
						|
  this.write(gpb.encode());
 | 
						|
 | 
						|
  // compression method
 | 
						|
  this.write(zipUtil.getShortBytes(method));
 | 
						|
 | 
						|
  // datetime
 | 
						|
  this.write(zipUtil.getLongBytes(ae.getTimeDos()));
 | 
						|
 | 
						|
  ae._offsets.data = this.offset;
 | 
						|
 | 
						|
  // crc32 checksum and sizes
 | 
						|
  if (gpb.usesDataDescriptor()) {
 | 
						|
    this.write(constants.LONG_ZERO);
 | 
						|
    this.write(constants.LONG_ZERO);
 | 
						|
    this.write(constants.LONG_ZERO);
 | 
						|
  } else {
 | 
						|
    this.write(zipUtil.getLongBytes(ae.getCrc()));
 | 
						|
    this.write(zipUtil.getLongBytes(ae.getCompressedSize()));
 | 
						|
    this.write(zipUtil.getLongBytes(ae.getSize()));
 | 
						|
  }
 | 
						|
 | 
						|
  // name length
 | 
						|
  this.write(zipUtil.getShortBytes(name.length));
 | 
						|
 | 
						|
  // extra length
 | 
						|
  this.write(zipUtil.getShortBytes(extra.length));
 | 
						|
 | 
						|
  // name
 | 
						|
  this.write(name);
 | 
						|
 | 
						|
  // extra
 | 
						|
  this.write(extra);
 | 
						|
 | 
						|
  ae._offsets.contents = this.offset;
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype.getComment = function (comment) {
 | 
						|
  return this._archive.comment !== null ? this._archive.comment : '';
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype.isZip64 = function () {
 | 
						|
  return this._archive.forceZip64 || this._entries.length > constants.ZIP64_MAGIC_SHORT || this._archive.centralLength > constants.ZIP64_MAGIC || this._archive.centralOffset > constants.ZIP64_MAGIC;
 | 
						|
};
 | 
						|
 | 
						|
ZipArchiveOutputStream.prototype.setComment = function (comment) {
 | 
						|
  this._archive.comment = comment;
 | 
						|
};
 |