884 lines
29 KiB
JavaScript
884 lines
29 KiB
JavaScript
|
'use strict';
|
||
|
var support = require('./support');
|
||
|
var utils = require('./utils');
|
||
|
var crc32 = require('./crc32');
|
||
|
var signature = require('./signature');
|
||
|
var defaults = require('./defaults');
|
||
|
var base64 = require('./base64');
|
||
|
var compressions = require('./compressions');
|
||
|
var CompressedObject = require('./compressedObject');
|
||
|
var nodeBuffer = require('./nodeBuffer');
|
||
|
var utf8 = require('./utf8');
|
||
|
var StringWriter = require('./stringWriter');
|
||
|
var Uint8ArrayWriter = require('./uint8ArrayWriter');
|
||
|
|
||
|
/**
|
||
|
* Returns the raw data of a ZipObject, decompress the content if necessary.
|
||
|
* @param {ZipObject} file the file to use.
|
||
|
* @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
|
||
|
*/
|
||
|
var getRawData = function(file) {
|
||
|
if (file._data instanceof CompressedObject) {
|
||
|
file._data = file._data.getContent();
|
||
|
file.options.binary = true;
|
||
|
file.options.base64 = false;
|
||
|
|
||
|
if (utils.getTypeOf(file._data) === "uint8array") {
|
||
|
var copy = file._data;
|
||
|
// when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
|
||
|
// if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
|
||
|
file._data = new Uint8Array(copy.length);
|
||
|
// with an empty Uint8Array, Opera fails with a "Offset larger than array size"
|
||
|
if (copy.length !== 0) {
|
||
|
file._data.set(copy, 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return file._data;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
|
||
|
* @param {ZipObject} file the file to use.
|
||
|
* @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
|
||
|
*/
|
||
|
var getBinaryData = function(file) {
|
||
|
var result = getRawData(file),
|
||
|
type = utils.getTypeOf(result);
|
||
|
if (type === "string") {
|
||
|
if (!file.options.binary) {
|
||
|
// unicode text !
|
||
|
// unicode string => binary string is a painful process, check if we can avoid it.
|
||
|
if (support.nodebuffer) {
|
||
|
return nodeBuffer(result, "utf-8");
|
||
|
}
|
||
|
}
|
||
|
return file.asBinary();
|
||
|
}
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Transform this._data into a string.
|
||
|
* @param {function} filter a function String -> String, applied if not null on the result.
|
||
|
* @return {String} the string representing this._data.
|
||
|
*/
|
||
|
var dataToString = function(asUTF8) {
|
||
|
var result = getRawData(this);
|
||
|
if (result === null || typeof result === "undefined") {
|
||
|
return "";
|
||
|
}
|
||
|
// if the data is a base64 string, we decode it before checking the encoding !
|
||
|
if (this.options.base64) {
|
||
|
result = base64.decode(result);
|
||
|
}
|
||
|
if (asUTF8 && this.options.binary) {
|
||
|
// JSZip.prototype.utf8decode supports arrays as input
|
||
|
// skip to array => string step, utf8decode will do it.
|
||
|
result = out.utf8decode(result);
|
||
|
}
|
||
|
else {
|
||
|
// no utf8 transformation, do the array => string step.
|
||
|
result = utils.transformTo("string", result);
|
||
|
}
|
||
|
|
||
|
if (!asUTF8 && !this.options.binary) {
|
||
|
result = utils.transformTo("string", out.utf8encode(result));
|
||
|
}
|
||
|
return result;
|
||
|
};
|
||
|
/**
|
||
|
* A simple object representing a file in the zip file.
|
||
|
* @constructor
|
||
|
* @param {string} name the name of the file
|
||
|
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
|
||
|
* @param {Object} options the options of the file
|
||
|
*/
|
||
|
var ZipObject = function(name, data, options) {
|
||
|
this.name = name;
|
||
|
this.dir = options.dir;
|
||
|
this.date = options.date;
|
||
|
this.comment = options.comment;
|
||
|
this.unixPermissions = options.unixPermissions;
|
||
|
this.dosPermissions = options.dosPermissions;
|
||
|
|
||
|
this._data = data;
|
||
|
this.options = options;
|
||
|
|
||
|
/*
|
||
|
* This object contains initial values for dir and date.
|
||
|
* With them, we can check if the user changed the deprecated metadata in
|
||
|
* `ZipObject#options` or not.
|
||
|
*/
|
||
|
this._initialMetadata = {
|
||
|
dir : options.dir,
|
||
|
date : options.date
|
||
|
};
|
||
|
};
|
||
|
|
||
|
ZipObject.prototype = {
|
||
|
/**
|
||
|
* Return the content as UTF8 string.
|
||
|
* @return {string} the UTF8 string.
|
||
|
*/
|
||
|
asText: function() {
|
||
|
return dataToString.call(this, true);
|
||
|
},
|
||
|
/**
|
||
|
* Returns the binary content.
|
||
|
* @return {string} the content as binary.
|
||
|
*/
|
||
|
asBinary: function() {
|
||
|
return dataToString.call(this, false);
|
||
|
},
|
||
|
/**
|
||
|
* Returns the content as a nodejs Buffer.
|
||
|
* @return {Buffer} the content as a Buffer.
|
||
|
*/
|
||
|
asNodeBuffer: function() {
|
||
|
var result = getBinaryData(this);
|
||
|
return utils.transformTo("nodebuffer", result);
|
||
|
},
|
||
|
/**
|
||
|
* Returns the content as an Uint8Array.
|
||
|
* @return {Uint8Array} the content as an Uint8Array.
|
||
|
*/
|
||
|
asUint8Array: function() {
|
||
|
var result = getBinaryData(this);
|
||
|
return utils.transformTo("uint8array", result);
|
||
|
},
|
||
|
/**
|
||
|
* Returns the content as an ArrayBuffer.
|
||
|
* @return {ArrayBuffer} the content as an ArrayBufer.
|
||
|
*/
|
||
|
asArrayBuffer: function() {
|
||
|
return this.asUint8Array().buffer;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Transform an integer into a string in hexadecimal.
|
||
|
* @private
|
||
|
* @param {number} dec the number to convert.
|
||
|
* @param {number} bytes the number of bytes to generate.
|
||
|
* @returns {string} the result.
|
||
|
*/
|
||
|
var decToHex = function(dec, bytes) {
|
||
|
var hex = "",
|
||
|
i;
|
||
|
for (i = 0; i < bytes; i++) {
|
||
|
hex += String.fromCharCode(dec & 0xff);
|
||
|
dec = dec >>> 8;
|
||
|
}
|
||
|
return hex;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Merge the objects passed as parameters into a new one.
|
||
|
* @private
|
||
|
* @param {...Object} var_args All objects to merge.
|
||
|
* @return {Object} a new object with the data of the others.
|
||
|
*/
|
||
|
var extend = function() {
|
||
|
var result = {}, i, attr;
|
||
|
for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
|
||
|
for (attr in arguments[i]) {
|
||
|
if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
|
||
|
result[attr] = arguments[i][attr];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Transforms the (incomplete) options from the user into the complete
|
||
|
* set of options to create a file.
|
||
|
* @private
|
||
|
* @param {Object} o the options from the user.
|
||
|
* @return {Object} the complete set of options.
|
||
|
*/
|
||
|
var prepareFileAttrs = function(o) {
|
||
|
o = o || {};
|
||
|
if (o.base64 === true && (o.binary === null || o.binary === undefined)) {
|
||
|
o.binary = true;
|
||
|
}
|
||
|
o = extend(o, defaults);
|
||
|
o.date = o.date || new Date();
|
||
|
if (o.compression !== null) o.compression = o.compression.toUpperCase();
|
||
|
|
||
|
return o;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add a file in the current folder.
|
||
|
* @private
|
||
|
* @param {string} name the name of the file
|
||
|
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
|
||
|
* @param {Object} o the options of the file
|
||
|
* @return {Object} the new file.
|
||
|
*/
|
||
|
var fileAdd = function(name, data, o) {
|
||
|
// be sure sub folders exist
|
||
|
var dataType = utils.getTypeOf(data),
|
||
|
parent;
|
||
|
|
||
|
o = prepareFileAttrs(o);
|
||
|
|
||
|
if (typeof o.unixPermissions === "string") {
|
||
|
o.unixPermissions = parseInt(o.unixPermissions, 8);
|
||
|
}
|
||
|
|
||
|
// UNX_IFDIR 0040000 see zipinfo.c
|
||
|
if (o.unixPermissions && (o.unixPermissions & 0x4000)) {
|
||
|
o.dir = true;
|
||
|
}
|
||
|
// Bit 4 Directory
|
||
|
if (o.dosPermissions && (o.dosPermissions & 0x0010)) {
|
||
|
o.dir = true;
|
||
|
}
|
||
|
|
||
|
if (o.dir) {
|
||
|
name = forceTrailingSlash(name);
|
||
|
}
|
||
|
|
||
|
if (o.createFolders && (parent = parentFolder(name))) {
|
||
|
folderAdd.call(this, parent, true);
|
||
|
}
|
||
|
|
||
|
if (o.dir || data === null || typeof data === "undefined") {
|
||
|
o.base64 = false;
|
||
|
o.binary = false;
|
||
|
data = null;
|
||
|
dataType = null;
|
||
|
}
|
||
|
else if (dataType === "string") {
|
||
|
if (o.binary && !o.base64) {
|
||
|
// optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
|
||
|
if (o.optimizedBinaryString !== true) {
|
||
|
// this is a string, not in a base64 format.
|
||
|
// Be sure that this is a correct "binary string"
|
||
|
data = utils.string2binary(data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else { // arraybuffer, uint8array, ...
|
||
|
o.base64 = false;
|
||
|
o.binary = true;
|
||
|
|
||
|
if (!dataType && !(data instanceof CompressedObject)) {
|
||
|
throw new Error("The data of '" + name + "' is in an unsupported format !");
|
||
|
}
|
||
|
|
||
|
// special case : it's way easier to work with Uint8Array than with ArrayBuffer
|
||
|
if (dataType === "arraybuffer") {
|
||
|
data = utils.transformTo("uint8array", data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var object = new ZipObject(name, data, o);
|
||
|
this.files[name] = object;
|
||
|
return object;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Find the parent folder of the path.
|
||
|
* @private
|
||
|
* @param {string} path the path to use
|
||
|
* @return {string} the parent folder, or ""
|
||
|
*/
|
||
|
var parentFolder = function (path) {
|
||
|
if (path.slice(-1) == '/') {
|
||
|
path = path.substring(0, path.length - 1);
|
||
|
}
|
||
|
var lastSlash = path.lastIndexOf('/');
|
||
|
return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Returns the path with a slash at the end.
|
||
|
* @private
|
||
|
* @param {String} path the path to check.
|
||
|
* @return {String} the path with a trailing slash.
|
||
|
*/
|
||
|
var forceTrailingSlash = function(path) {
|
||
|
// Check the name ends with a /
|
||
|
if (path.slice(-1) != "/") {
|
||
|
path += "/"; // IE doesn't like substr(-1)
|
||
|
}
|
||
|
return path;
|
||
|
};
|
||
|
/**
|
||
|
* Add a (sub) folder in the current folder.
|
||
|
* @private
|
||
|
* @param {string} name the folder's name
|
||
|
* @param {boolean=} [createFolders] If true, automatically create sub
|
||
|
* folders. Defaults to false.
|
||
|
* @return {Object} the new folder.
|
||
|
*/
|
||
|
var folderAdd = function(name, createFolders) {
|
||
|
createFolders = (typeof createFolders !== 'undefined') ? createFolders : false;
|
||
|
|
||
|
name = forceTrailingSlash(name);
|
||
|
|
||
|
// Does this folder already exist?
|
||
|
if (!this.files[name]) {
|
||
|
fileAdd.call(this, name, null, {
|
||
|
dir: true,
|
||
|
createFolders: createFolders
|
||
|
});
|
||
|
}
|
||
|
return this.files[name];
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Generate a JSZip.CompressedObject for a given zipOject.
|
||
|
* @param {ZipObject} file the object to read.
|
||
|
* @param {JSZip.compression} compression the compression to use.
|
||
|
* @param {Object} compressionOptions the options to use when compressing.
|
||
|
* @return {JSZip.CompressedObject} the compressed result.
|
||
|
*/
|
||
|
var generateCompressedObjectFrom = function(file, compression, compressionOptions) {
|
||
|
var result = new CompressedObject(),
|
||
|
content;
|
||
|
|
||
|
// the data has not been decompressed, we might reuse things !
|
||
|
if (file._data instanceof CompressedObject) {
|
||
|
result.uncompressedSize = file._data.uncompressedSize;
|
||
|
result.crc32 = file._data.crc32;
|
||
|
|
||
|
if (result.uncompressedSize === 0 || file.dir) {
|
||
|
compression = compressions['STORE'];
|
||
|
result.compressedContent = "";
|
||
|
result.crc32 = 0;
|
||
|
}
|
||
|
else if (file._data.compressionMethod === compression.magic) {
|
||
|
result.compressedContent = file._data.getCompressedContent();
|
||
|
}
|
||
|
else {
|
||
|
content = file._data.getContent();
|
||
|
// need to decompress / recompress
|
||
|
result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// have uncompressed data
|
||
|
content = getBinaryData(file);
|
||
|
if (!content || content.length === 0 || file.dir) {
|
||
|
compression = compressions['STORE'];
|
||
|
content = "";
|
||
|
}
|
||
|
result.uncompressedSize = content.length;
|
||
|
result.crc32 = crc32(content);
|
||
|
result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions);
|
||
|
}
|
||
|
|
||
|
result.compressedSize = result.compressedContent.length;
|
||
|
result.compressionMethod = compression.magic;
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Generate the UNIX part of the external file attributes.
|
||
|
* @param {Object} unixPermissions the unix permissions or null.
|
||
|
* @param {Boolean} isDir true if the entry is a directory, false otherwise.
|
||
|
* @return {Number} a 32 bit integer.
|
||
|
*
|
||
|
* adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute :
|
||
|
*
|
||
|
* TTTTsstrwxrwxrwx0000000000ADVSHR
|
||
|
* ^^^^____________________________ file type, see zipinfo.c (UNX_*)
|
||
|
* ^^^_________________________ setuid, setgid, sticky
|
||
|
* ^^^^^^^^^________________ permissions
|
||
|
* ^^^^^^^^^^______ not used ?
|
||
|
* ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only
|
||
|
*/
|
||
|
var generateUnixExternalFileAttr = function (unixPermissions, isDir) {
|
||
|
|
||
|
var result = unixPermissions;
|
||
|
if (!unixPermissions) {
|
||
|
// I can't use octal values in strict mode, hence the hexa.
|
||
|
// 040775 => 0x41fd
|
||
|
// 0100664 => 0x81b4
|
||
|
result = isDir ? 0x41fd : 0x81b4;
|
||
|
}
|
||
|
|
||
|
return (result & 0xFFFF) << 16;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Generate the DOS part of the external file attributes.
|
||
|
* @param {Object} dosPermissions the dos permissions or null.
|
||
|
* @param {Boolean} isDir true if the entry is a directory, false otherwise.
|
||
|
* @return {Number} a 32 bit integer.
|
||
|
*
|
||
|
* Bit 0 Read-Only
|
||
|
* Bit 1 Hidden
|
||
|
* Bit 2 System
|
||
|
* Bit 3 Volume Label
|
||
|
* Bit 4 Directory
|
||
|
* Bit 5 Archive
|
||
|
*/
|
||
|
var generateDosExternalFileAttr = function (dosPermissions, isDir) {
|
||
|
|
||
|
// the dir flag is already set for compatibility
|
||
|
|
||
|
return (dosPermissions || 0) & 0x3F;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Generate the various parts used in the construction of the final zip file.
|
||
|
* @param {string} name the file name.
|
||
|
* @param {ZipObject} file the file content.
|
||
|
* @param {JSZip.CompressedObject} compressedObject the compressed object.
|
||
|
* @param {number} offset the current offset from the start of the zip file.
|
||
|
* @param {String} platform let's pretend we are this platform (change platform dependents fields)
|
||
|
* @return {object} the zip parts.
|
||
|
*/
|
||
|
var generateZipParts = function(name, file, compressedObject, offset, platform) {
|
||
|
var data = compressedObject.compressedContent,
|
||
|
utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)),
|
||
|
comment = file.comment || "",
|
||
|
utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)),
|
||
|
useUTF8ForFileName = utfEncodedFileName.length !== file.name.length,
|
||
|
useUTF8ForComment = utfEncodedComment.length !== comment.length,
|
||
|
o = file.options,
|
||
|
dosTime,
|
||
|
dosDate,
|
||
|
extraFields = "",
|
||
|
unicodePathExtraField = "",
|
||
|
unicodeCommentExtraField = "",
|
||
|
dir, date;
|
||
|
|
||
|
|
||
|
// handle the deprecated options.dir
|
||
|
if (file._initialMetadata.dir !== file.dir) {
|
||
|
dir = file.dir;
|
||
|
} else {
|
||
|
dir = o.dir;
|
||
|
}
|
||
|
|
||
|
// handle the deprecated options.date
|
||
|
if(file._initialMetadata.date !== file.date) {
|
||
|
date = file.date;
|
||
|
} else {
|
||
|
date = o.date;
|
||
|
}
|
||
|
|
||
|
var extFileAttr = 0;
|
||
|
var versionMadeBy = 0;
|
||
|
if (dir) {
|
||
|
// dos or unix, we set the dos dir flag
|
||
|
extFileAttr |= 0x00010;
|
||
|
}
|
||
|
if(platform === "UNIX") {
|
||
|
versionMadeBy = 0x031E; // UNIX, version 3.0
|
||
|
extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir);
|
||
|
} else { // DOS or other, fallback to DOS
|
||
|
versionMadeBy = 0x0014; // DOS, version 2.0
|
||
|
extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir);
|
||
|
}
|
||
|
|
||
|
// date
|
||
|
// @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
|
||
|
// @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
|
||
|
// @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
|
||
|
|
||
|
dosTime = date.getHours();
|
||
|
dosTime = dosTime << 6;
|
||
|
dosTime = dosTime | date.getMinutes();
|
||
|
dosTime = dosTime << 5;
|
||
|
dosTime = dosTime | date.getSeconds() / 2;
|
||
|
|
||
|
dosDate = date.getFullYear() - 1980;
|
||
|
dosDate = dosDate << 4;
|
||
|
dosDate = dosDate | (date.getMonth() + 1);
|
||
|
dosDate = dosDate << 5;
|
||
|
dosDate = dosDate | date.getDate();
|
||
|
|
||
|
if (useUTF8ForFileName) {
|
||
|
// set the unicode path extra field. unzip needs at least one extra
|
||
|
// field to correctly handle unicode path, so using the path is as good
|
||
|
// as any other information. This could improve the situation with
|
||
|
// other archive managers too.
|
||
|
// This field is usually used without the utf8 flag, with a non
|
||
|
// unicode path in the header (winrar, winzip). This helps (a bit)
|
||
|
// with the messy Windows' default compressed folders feature but
|
||
|
// breaks on p7zip which doesn't seek the unicode path extra field.
|
||
|
// So for now, UTF-8 everywhere !
|
||
|
unicodePathExtraField =
|
||
|
// Version
|
||
|
decToHex(1, 1) +
|
||
|
// NameCRC32
|
||
|
decToHex(crc32(utfEncodedFileName), 4) +
|
||
|
// UnicodeName
|
||
|
utfEncodedFileName;
|
||
|
|
||
|
extraFields +=
|
||
|
// Info-ZIP Unicode Path Extra Field
|
||
|
"\x75\x70" +
|
||
|
// size
|
||
|
decToHex(unicodePathExtraField.length, 2) +
|
||
|
// content
|
||
|
unicodePathExtraField;
|
||
|
}
|
||
|
|
||
|
if(useUTF8ForComment) {
|
||
|
|
||
|
unicodeCommentExtraField =
|
||
|
// Version
|
||
|
decToHex(1, 1) +
|
||
|
// CommentCRC32
|
||
|
decToHex(this.crc32(utfEncodedComment), 4) +
|
||
|
// UnicodeName
|
||
|
utfEncodedComment;
|
||
|
|
||
|
extraFields +=
|
||
|
// Info-ZIP Unicode Path Extra Field
|
||
|
"\x75\x63" +
|
||
|
// size
|
||
|
decToHex(unicodeCommentExtraField.length, 2) +
|
||
|
// content
|
||
|
unicodeCommentExtraField;
|
||
|
}
|
||
|
|
||
|
var header = "";
|
||
|
|
||
|
// version needed to extract
|
||
|
header += "\x0A\x00";
|
||
|
// general purpose bit flag
|
||
|
// set bit 11 if utf8
|
||
|
header += (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00";
|
||
|
// compression method
|
||
|
header += compressedObject.compressionMethod;
|
||
|
// last mod file time
|
||
|
header += decToHex(dosTime, 2);
|
||
|
// last mod file date
|
||
|
header += decToHex(dosDate, 2);
|
||
|
// crc-32
|
||
|
header += decToHex(compressedObject.crc32, 4);
|
||
|
// compressed size
|
||
|
header += decToHex(compressedObject.compressedSize, 4);
|
||
|
// uncompressed size
|
||
|
header += decToHex(compressedObject.uncompressedSize, 4);
|
||
|
// file name length
|
||
|
header += decToHex(utfEncodedFileName.length, 2);
|
||
|
// extra field length
|
||
|
header += decToHex(extraFields.length, 2);
|
||
|
|
||
|
|
||
|
var fileRecord = signature.LOCAL_FILE_HEADER + header + utfEncodedFileName + extraFields;
|
||
|
|
||
|
var dirRecord = signature.CENTRAL_FILE_HEADER +
|
||
|
// version made by (00: DOS)
|
||
|
decToHex(versionMadeBy, 2) +
|
||
|
// file header (common to file and central directory)
|
||
|
header +
|
||
|
// file comment length
|
||
|
decToHex(utfEncodedComment.length, 2) +
|
||
|
// disk number start
|
||
|
"\x00\x00" +
|
||
|
// internal file attributes TODO
|
||
|
"\x00\x00" +
|
||
|
// external file attributes
|
||
|
decToHex(extFileAttr, 4) +
|
||
|
// relative offset of local header
|
||
|
decToHex(offset, 4) +
|
||
|
// file name
|
||
|
utfEncodedFileName +
|
||
|
// extra field
|
||
|
extraFields +
|
||
|
// file comment
|
||
|
utfEncodedComment;
|
||
|
|
||
|
return {
|
||
|
fileRecord: fileRecord,
|
||
|
dirRecord: dirRecord,
|
||
|
compressedObject: compressedObject
|
||
|
};
|
||
|
};
|
||
|
|
||
|
|
||
|
// return the actual prototype of JSZip
|
||
|
var out = {
|
||
|
/**
|
||
|
* Read an existing zip and merge the data in the current JSZip object.
|
||
|
* The implementation is in jszip-load.js, don't forget to include it.
|
||
|
* @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load
|
||
|
* @param {Object} options Options for loading the stream.
|
||
|
* options.base64 : is the stream in base64 ? default : false
|
||
|
* @return {JSZip} the current JSZip object
|
||
|
*/
|
||
|
load: function(stream, options) {
|
||
|
throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Filter nested files/folders with the specified function.
|
||
|
* @param {Function} search the predicate to use :
|
||
|
* function (relativePath, file) {...}
|
||
|
* It takes 2 arguments : the relative path and the file.
|
||
|
* @return {Array} An array of matching elements.
|
||
|
*/
|
||
|
filter: function(search) {
|
||
|
var result = [],
|
||
|
filename, relativePath, file, fileClone;
|
||
|
for (filename in this.files) {
|
||
|
if (!this.files.hasOwnProperty(filename)) {
|
||
|
continue;
|
||
|
}
|
||
|
file = this.files[filename];
|
||
|
// return a new object, don't let the user mess with our internal objects :)
|
||
|
fileClone = new ZipObject(file.name, file._data, extend(file.options));
|
||
|
relativePath = filename.slice(this.root.length, filename.length);
|
||
|
if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
|
||
|
search(relativePath, fileClone)) { // and the file matches the function
|
||
|
result.push(fileClone);
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Add a file to the zip file, or search a file.
|
||
|
* @param {string|RegExp} name The name of the file to add (if data is defined),
|
||
|
* the name of the file to find (if no data) or a regex to match files.
|
||
|
* @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
|
||
|
* @param {Object} o File options
|
||
|
* @return {JSZip|Object|Array} this JSZip object (when adding a file),
|
||
|
* a file (when searching by string) or an array of files (when searching by regex).
|
||
|
*/
|
||
|
file: function(name, data, o) {
|
||
|
if (arguments.length === 1) {
|
||
|
if (utils.isRegExp(name)) {
|
||
|
var regexp = name;
|
||
|
return this.filter(function(relativePath, file) {
|
||
|
return !file.dir && regexp.test(relativePath);
|
||
|
});
|
||
|
}
|
||
|
else { // text
|
||
|
return this.filter(function(relativePath, file) {
|
||
|
return !file.dir && relativePath === name;
|
||
|
})[0] || null;
|
||
|
}
|
||
|
}
|
||
|
else { // more than one argument : we have data !
|
||
|
name = this.root + name;
|
||
|
fileAdd.call(this, name, data, o);
|
||
|
}
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Add a directory to the zip file, or search.
|
||
|
* @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
|
||
|
* @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
|
||
|
*/
|
||
|
folder: function(arg) {
|
||
|
if (!arg) {
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
if (utils.isRegExp(arg)) {
|
||
|
return this.filter(function(relativePath, file) {
|
||
|
return file.dir && arg.test(relativePath);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// else, name is a new folder
|
||
|
var name = this.root + arg;
|
||
|
var newFolder = folderAdd.call(this, name);
|
||
|
|
||
|
// Allow chaining by returning a new object with this folder as the root
|
||
|
var ret = this.clone();
|
||
|
ret.root = newFolder.name;
|
||
|
return ret;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Delete a file, or a directory and all sub-files, from the zip
|
||
|
* @param {string} name the name of the file to delete
|
||
|
* @return {JSZip} this JSZip object
|
||
|
*/
|
||
|
remove: function(name) {
|
||
|
name = this.root + name;
|
||
|
var file = this.files[name];
|
||
|
if (!file) {
|
||
|
// Look for any folders
|
||
|
if (name.slice(-1) != "/") {
|
||
|
name += "/";
|
||
|
}
|
||
|
file = this.files[name];
|
||
|
}
|
||
|
|
||
|
if (file && !file.dir) {
|
||
|
// file
|
||
|
delete this.files[name];
|
||
|
} else {
|
||
|
// maybe a folder, delete recursively
|
||
|
var kids = this.filter(function(relativePath, file) {
|
||
|
return file.name.slice(0, name.length) === name;
|
||
|
});
|
||
|
for (var i = 0; i < kids.length; i++) {
|
||
|
delete this.files[kids[i].name];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Generate the complete zip file
|
||
|
* @param {Object} options the options to generate the zip file :
|
||
|
* - base64, (deprecated, use type instead) true to generate base64.
|
||
|
* - compression, "STORE" by default.
|
||
|
* - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
|
||
|
* @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
|
||
|
*/
|
||
|
generate: function(options) {
|
||
|
options = extend(options || {}, {
|
||
|
base64: true,
|
||
|
compression: "STORE",
|
||
|
compressionOptions : null,
|
||
|
type: "base64",
|
||
|
platform: "DOS",
|
||
|
comment: null,
|
||
|
mimeType: 'application/zip'
|
||
|
});
|
||
|
|
||
|
utils.checkSupport(options.type);
|
||
|
|
||
|
// accept nodejs `process.platform`
|
||
|
if(
|
||
|
options.platform === 'darwin' ||
|
||
|
options.platform === 'freebsd' ||
|
||
|
options.platform === 'linux' ||
|
||
|
options.platform === 'sunos'
|
||
|
) {
|
||
|
options.platform = "UNIX";
|
||
|
}
|
||
|
if (options.platform === 'win32') {
|
||
|
options.platform = "DOS";
|
||
|
}
|
||
|
|
||
|
var zipData = [],
|
||
|
localDirLength = 0,
|
||
|
centralDirLength = 0,
|
||
|
writer, i,
|
||
|
utfEncodedComment = utils.transformTo("string", this.utf8encode(options.comment || this.comment || ""));
|
||
|
|
||
|
// first, generate all the zip parts.
|
||
|
for (var name in this.files) {
|
||
|
if (!this.files.hasOwnProperty(name)) {
|
||
|
continue;
|
||
|
}
|
||
|
var file = this.files[name];
|
||
|
|
||
|
var compressionName = file.options.compression || options.compression.toUpperCase();
|
||
|
var compression = compressions[compressionName];
|
||
|
if (!compression) {
|
||
|
throw new Error(compressionName + " is not a valid compression method !");
|
||
|
}
|
||
|
var compressionOptions = file.options.compressionOptions || options.compressionOptions || {};
|
||
|
|
||
|
var compressedObject = generateCompressedObjectFrom.call(this, file, compression, compressionOptions);
|
||
|
|
||
|
var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength, options.platform);
|
||
|
localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
|
||
|
centralDirLength += zipPart.dirRecord.length;
|
||
|
zipData.push(zipPart);
|
||
|
}
|
||
|
|
||
|
var dirEnd = "";
|
||
|
|
||
|
// end of central dir signature
|
||
|
dirEnd = signature.CENTRAL_DIRECTORY_END +
|
||
|
// number of this disk
|
||
|
"\x00\x00" +
|
||
|
// number of the disk with the start of the central directory
|
||
|
"\x00\x00" +
|
||
|
// total number of entries in the central directory on this disk
|
||
|
decToHex(zipData.length, 2) +
|
||
|
// total number of entries in the central directory
|
||
|
decToHex(zipData.length, 2) +
|
||
|
// size of the central directory 4 bytes
|
||
|
decToHex(centralDirLength, 4) +
|
||
|
// offset of start of central directory with respect to the starting disk number
|
||
|
decToHex(localDirLength, 4) +
|
||
|
// .ZIP file comment length
|
||
|
decToHex(utfEncodedComment.length, 2) +
|
||
|
// .ZIP file comment
|
||
|
utfEncodedComment;
|
||
|
|
||
|
|
||
|
// we have all the parts (and the total length)
|
||
|
// time to create a writer !
|
||
|
var typeName = options.type.toLowerCase();
|
||
|
if(typeName==="uint8array"||typeName==="arraybuffer"||typeName==="blob"||typeName==="nodebuffer") {
|
||
|
writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
|
||
|
}else{
|
||
|
writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < zipData.length; i++) {
|
||
|
writer.append(zipData[i].fileRecord);
|
||
|
writer.append(zipData[i].compressedObject.compressedContent);
|
||
|
}
|
||
|
for (i = 0; i < zipData.length; i++) {
|
||
|
writer.append(zipData[i].dirRecord);
|
||
|
}
|
||
|
|
||
|
writer.append(dirEnd);
|
||
|
|
||
|
var zip = writer.finalize();
|
||
|
|
||
|
|
||
|
|
||
|
switch(options.type.toLowerCase()) {
|
||
|
// case "zip is an Uint8Array"
|
||
|
case "uint8array" :
|
||
|
case "arraybuffer" :
|
||
|
case "nodebuffer" :
|
||
|
return utils.transformTo(options.type.toLowerCase(), zip);
|
||
|
case "blob" :
|
||
|
return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip), options.mimeType);
|
||
|
// case "zip is a string"
|
||
|
case "base64" :
|
||
|
return (options.base64) ? base64.encode(zip) : zip;
|
||
|
default : // case "string" :
|
||
|
return zip;
|
||
|
}
|
||
|
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @deprecated
|
||
|
* This method will be removed in a future version without replacement.
|
||
|
*/
|
||
|
crc32: function (input, crc) {
|
||
|
return crc32(input, crc);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @deprecated
|
||
|
* This method will be removed in a future version without replacement.
|
||
|
*/
|
||
|
utf8encode: function (string) {
|
||
|
return utils.transformTo("string", utf8.utf8encode(string));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @deprecated
|
||
|
* This method will be removed in a future version without replacement.
|
||
|
*/
|
||
|
utf8decode: function (input) {
|
||
|
return utf8.utf8decode(input);
|
||
|
}
|
||
|
};
|
||
|
module.exports = out;
|