Files
codeql-action/node_modules/adm-zip/zipFile.js
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

447 lines
15 KiB
JavaScript
Raw Normal View History

2022-08-01 11:24:34 +02:00
const ZipEntry = require("./zipEntry");
const Headers = require("./headers");
const Utils = require("./util");
module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
var entryList = [],
entryTable = {},
_comment = Buffer.alloc(0),
mainHeader = new Headers.MainHeader(),
loadedEntries = false;
2024-03-18 17:58:54 +00:00
var password = null;
2024-08-12 11:04:43 -07:00
const temporary = new Set();
2022-08-01 11:24:34 +02:00
// assign options
2024-08-12 11:04:43 -07:00
const opts = options;
2022-08-01 11:24:34 +02:00
2024-08-12 11:04:43 -07:00
const { noSort, decoder } = opts;
2022-08-01 11:24:34 +02:00
if (inBuffer) {
// is a memory buffer
readMainHeader(opts.readEntries);
} else {
// none. is a new file
loadedEntries = true;
}
2024-08-12 11:04:43 -07:00
function makeTemporaryFolders() {
const foldersList = new Set();
// Make list of all folders in file
for (const elem of Object.keys(entryTable)) {
const elements = elem.split("/");
elements.pop(); // filename
if (!elements.length) continue; // no folders
for (let i = 0; i < elements.length; i++) {
const sub = elements.slice(0, i + 1).join("/") + "/";
foldersList.add(sub);
}
}
2022-08-01 11:24:34 +02:00
2024-08-12 11:04:43 -07:00
// create missing folders as temporary
for (const elem of foldersList) {
if (!(elem in entryTable)) {
const tempfolder = new ZipEntry(opts);
tempfolder.entryName = elem;
tempfolder.attr = 0x10;
tempfolder.temporary = true;
entryList.push(tempfolder);
entryTable[tempfolder.entryName] = tempfolder;
temporary.add(tempfolder);
}
2022-08-01 11:24:34 +02:00
}
}
function readEntries() {
loadedEntries = true;
entryTable = {};
2024-06-03 18:24:11 +00:00
if (mainHeader.diskEntries > (inBuffer.length - mainHeader.offset) / Utils.Constants.CENHDR) {
2024-08-12 11:04:43 -07:00
throw Utils.Errors.DISK_ENTRY_TOO_LARGE();
2024-06-03 18:24:11 +00:00
}
2022-08-01 11:24:34 +02:00
entryList = new Array(mainHeader.diskEntries); // total number of entries
var index = mainHeader.offset; // offset of first CEN header
for (var i = 0; i < entryList.length; i++) {
var tmp = index,
2024-08-12 11:04:43 -07:00
entry = new ZipEntry(opts, inBuffer);
2022-08-01 11:24:34 +02:00
entry.header = inBuffer.slice(tmp, (tmp += Utils.Constants.CENHDR));
entry.entryName = inBuffer.slice(tmp, (tmp += entry.header.fileNameLength));
if (entry.header.extraLength) {
entry.extra = inBuffer.slice(tmp, (tmp += entry.header.extraLength));
}
if (entry.header.commentLength) entry.comment = inBuffer.slice(tmp, tmp + entry.header.commentLength);
2024-06-03 18:24:11 +00:00
index += entry.header.centralHeaderSize;
2022-08-01 11:24:34 +02:00
entryList[i] = entry;
entryTable[entry.entryName] = entry;
}
2024-08-12 11:04:43 -07:00
temporary.clear();
makeTemporaryFolders();
2022-08-01 11:24:34 +02:00
}
function readMainHeader(/*Boolean*/ readNow) {
var i = inBuffer.length - Utils.Constants.ENDHDR, // END header size
max = Math.max(0, i - 0xffff), // 0xFFFF is the max zip file comment length
n = max,
endStart = inBuffer.length,
endOffset = -1, // Start offset of the END header
commentEnd = 0;
2024-08-12 11:04:43 -07:00
// option to search header form entire file
const trailingSpace = typeof opts.trailingSpace === "boolean" ? opts.trailingSpace : false;
if (trailingSpace) max = 0;
2022-08-01 11:24:34 +02:00
for (i; i >= n; i--) {
if (inBuffer[i] !== 0x50) continue; // quick check that the byte is 'P'
if (inBuffer.readUInt32LE(i) === Utils.Constants.ENDSIG) {
// "PK\005\006"
endOffset = i;
commentEnd = i;
endStart = i + Utils.Constants.ENDHDR;
// We already found a regular signature, let's look just a bit further to check if there's any zip64 signature
n = i - Utils.Constants.END64HDR;
continue;
}
if (inBuffer.readUInt32LE(i) === Utils.Constants.END64SIG) {
// Found a zip64 signature, let's continue reading the whole zip64 record
n = max;
continue;
}
if (inBuffer.readUInt32LE(i) === Utils.Constants.ZIP64SIG) {
// Found the zip64 record, let's determine it's size
endOffset = i;
endStart = i + Utils.readBigUInt64LE(inBuffer, i + Utils.Constants.ZIP64SIZE) + Utils.Constants.ZIP64LEAD;
break;
}
}
2024-08-12 11:04:43 -07:00
if (endOffset == -1) throw Utils.Errors.INVALID_FORMAT();
2022-08-01 11:24:34 +02:00
mainHeader.loadFromBinary(inBuffer.slice(endOffset, endStart));
if (mainHeader.commentLength) {
_comment = inBuffer.slice(commentEnd + Utils.Constants.ENDHDR);
}
if (readNow) readEntries();
}
function sortEntries() {
if (entryList.length > 1 && !noSort) {
entryList.sort((a, b) => a.entryName.toLowerCase().localeCompare(b.entryName.toLowerCase()));
}
}
return {
/**
* Returns an array of ZipEntry objects existent in the current opened archive
* @return Array
*/
get entries() {
if (!loadedEntries) {
readEntries();
}
2024-08-12 11:04:43 -07:00
return entryList.filter((e) => !temporary.has(e));
2022-08-01 11:24:34 +02:00
},
/**
* Archive comment
* @return {String}
*/
get comment() {
2024-08-12 11:04:43 -07:00
return decoder.decode(_comment);
2022-08-01 11:24:34 +02:00
},
set comment(val) {
2024-08-12 11:04:43 -07:00
_comment = Utils.toBuffer(val, decoder.encode);
2022-08-01 11:24:34 +02:00
mainHeader.commentLength = _comment.length;
},
getEntryCount: function () {
if (!loadedEntries) {
return mainHeader.diskEntries;
}
return entryList.length;
},
forEach: function (callback) {
2024-08-12 11:04:43 -07:00
this.entries.forEach(callback);
2022-08-01 11:24:34 +02:00
},
/**
* Returns a reference to the entry with the given name or null if entry is inexistent
*
* @param entryName
* @return ZipEntry
*/
getEntry: function (/*String*/ entryName) {
if (!loadedEntries) {
readEntries();
}
return entryTable[entryName] || null;
},
/**
* Adds the given entry to the entry list
*
* @param entry
*/
setEntry: function (/*ZipEntry*/ entry) {
if (!loadedEntries) {
readEntries();
}
entryList.push(entry);
entryTable[entry.entryName] = entry;
mainHeader.totalEntries = entryList.length;
},
/**
2024-08-12 11:04:43 -07:00
* Removes the file with the given name from the entry list.
2022-08-01 11:24:34 +02:00
*
* If the entry is a directory, then all nested files and directories will be removed
* @param entryName
2024-08-12 11:04:43 -07:00
* @returns {void}
*/
deleteFile: function (/*String*/ entryName, withsubfolders = true) {
if (!loadedEntries) {
readEntries();
}
const entry = entryTable[entryName];
const list = this.getEntryChildren(entry, withsubfolders).map((child) => child.entryName);
list.forEach(this.deleteEntry);
},
/**
* Removes the entry with the given name from the entry list.
*
* @param {string} entryName
* @returns {void}
2022-08-01 11:24:34 +02:00
*/
deleteEntry: function (/*String*/ entryName) {
if (!loadedEntries) {
readEntries();
}
2024-08-12 11:04:43 -07:00
const entry = entryTable[entryName];
const index = entryList.indexOf(entry);
if (index >= 0) {
entryList.splice(index, 1);
delete entryTable[entryName];
mainHeader.totalEntries = entryList.length;
2022-08-01 11:24:34 +02:00
}
},
/**
* Iterates and returns all nested files and directories of the given entry
*
* @param entry
* @return Array
*/
2024-08-12 11:04:43 -07:00
getEntryChildren: function (/*ZipEntry*/ entry, subfolders = true) {
2022-08-01 11:24:34 +02:00
if (!loadedEntries) {
readEntries();
}
2024-08-12 11:04:43 -07:00
if (typeof entry === "object") {
if (entry.isDirectory && subfolders) {
const list = [];
const name = entry.entryName;
for (const zipEntry of entryList) {
if (zipEntry.entryName.startsWith(name)) {
list.push(zipEntry);
}
2022-08-01 11:24:34 +02:00
}
2024-08-12 11:04:43 -07:00
return list;
} else {
return [entry];
}
2022-08-01 11:24:34 +02:00
}
return [];
},
2024-08-12 11:04:43 -07:00
/**
* How many child elements entry has
*
* @param {ZipEntry} entry
* @return {integer}
*/
getChildCount: function (entry) {
if (entry && entry.isDirectory) {
const list = this.getEntryChildren(entry);
return list.includes(entry) ? list.length - 1 : list.length;
}
return 0;
},
2022-08-01 11:24:34 +02:00
/**
* Returns the zip file
*
* @return Buffer
*/
compressToBuffer: function () {
if (!loadedEntries) {
readEntries();
}
sortEntries();
const dataBlock = [];
2024-06-03 18:24:11 +00:00
const headerBlocks = [];
2022-08-01 11:24:34 +02:00
let totalSize = 0;
let dindex = 0;
mainHeader.size = 0;
mainHeader.offset = 0;
2024-09-02 17:34:39 +00:00
let totalEntries = 0;
2022-08-01 11:24:34 +02:00
2024-08-12 11:04:43 -07:00
for (const entry of this.entries) {
2022-08-01 11:24:34 +02:00
// compress data and set local and entry header accordingly. Reason why is called first
const compressedData = entry.getCompressedData();
entry.header.offset = dindex;
2024-06-03 18:24:11 +00:00
// 1. construct local header
const localHeader = entry.packLocalHeader();
2022-08-01 11:24:34 +02:00
// 2. offsets
2024-06-03 18:24:11 +00:00
const dataLength = localHeader.length + compressedData.length;
2022-08-01 11:24:34 +02:00
dindex += dataLength;
// 3. store values in sequence
2024-06-03 18:24:11 +00:00
dataBlock.push(localHeader);
2022-08-01 11:24:34 +02:00
dataBlock.push(compressedData);
2024-06-03 18:24:11 +00:00
// 4. construct central header
const centralHeader = entry.packCentralHeader();
headerBlocks.push(centralHeader);
2022-08-01 11:24:34 +02:00
// 5. update main header
2024-06-03 18:24:11 +00:00
mainHeader.size += centralHeader.length;
totalSize += dataLength + centralHeader.length;
2024-08-12 11:04:43 -07:00
totalEntries++;
2022-08-01 11:24:34 +02:00
}
totalSize += mainHeader.mainHeaderSize; // also includes zip file comment length
// point to end of data and beginning of central directory first record
mainHeader.offset = dindex;
2024-08-12 11:04:43 -07:00
mainHeader.totalEntries = totalEntries;
2022-08-01 11:24:34 +02:00
dindex = 0;
const outBuffer = Buffer.alloc(totalSize);
// write data blocks
for (const content of dataBlock) {
content.copy(outBuffer, dindex);
dindex += content.length;
}
// write central directory entries
2024-06-03 18:24:11 +00:00
for (const content of headerBlocks) {
2022-08-01 11:24:34 +02:00
content.copy(outBuffer, dindex);
dindex += content.length;
}
// write main header
const mh = mainHeader.toBinary();
if (_comment) {
_comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment
}
mh.copy(outBuffer, dindex);
2024-08-12 11:04:43 -07:00
// Since we update entry and main header offsets,
// they are no longer valid and we have to reset content
// (Issue 64)
inBuffer = outBuffer;
loadedEntries = false;
2022-08-01 11:24:34 +02:00
return outBuffer;
},
toAsyncBuffer: function (/*Function*/ onSuccess, /*Function*/ onFail, /*Function*/ onItemStart, /*Function*/ onItemEnd) {
try {
if (!loadedEntries) {
readEntries();
}
sortEntries();
const dataBlock = [];
2024-06-03 18:24:11 +00:00
const centralHeaders = [];
2022-08-01 11:24:34 +02:00
let totalSize = 0;
let dindex = 0;
2024-08-12 11:04:43 -07:00
let totalEntries = 0;
2022-08-01 11:24:34 +02:00
mainHeader.size = 0;
mainHeader.offset = 0;
const compress2Buffer = function (entryLists) {
2024-06-03 18:24:11 +00:00
if (entryLists.length > 0) {
const entry = entryLists.shift();
2022-08-01 11:24:34 +02:00
const name = entry.entryName + entry.extra.toString();
if (onItemStart) onItemStart(name);
entry.getCompressedDataAsync(function (compressedData) {
if (onItemEnd) onItemEnd(name);
entry.header.offset = dindex;
2024-06-03 18:24:11 +00:00
// 1. construct local header
const localHeader = entry.packLocalHeader();
// 2. offsets
const dataLength = localHeader.length + compressedData.length;
2022-08-01 11:24:34 +02:00
dindex += dataLength;
2024-06-03 18:24:11 +00:00
// 3. store values in sequence
dataBlock.push(localHeader);
2022-08-01 11:24:34 +02:00
dataBlock.push(compressedData);
2024-06-03 18:24:11 +00:00
// central header
const centalHeader = entry.packCentralHeader();
centralHeaders.push(centalHeader);
mainHeader.size += centalHeader.length;
totalSize += dataLength + centalHeader.length;
2024-08-12 11:04:43 -07:00
totalEntries++;
2022-08-01 11:24:34 +02:00
compress2Buffer(entryLists);
});
} else {
totalSize += mainHeader.mainHeaderSize; // also includes zip file comment length
// point to end of data and beginning of central directory first record
mainHeader.offset = dindex;
2024-08-12 11:04:43 -07:00
mainHeader.totalEntries = totalEntries;
2022-08-01 11:24:34 +02:00
dindex = 0;
const outBuffer = Buffer.alloc(totalSize);
dataBlock.forEach(function (content) {
content.copy(outBuffer, dindex); // write data blocks
dindex += content.length;
});
2024-06-03 18:24:11 +00:00
centralHeaders.forEach(function (content) {
2022-08-01 11:24:34 +02:00
content.copy(outBuffer, dindex); // write central directory entries
dindex += content.length;
});
const mh = mainHeader.toBinary();
if (_comment) {
_comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment
}
mh.copy(outBuffer, dindex); // write main header
2024-08-12 11:04:43 -07:00
// Since we update entry and main header offsets, they are no
// longer valid and we have to reset content using our new buffer
// (Issue 64)
inBuffer = outBuffer;
loadedEntries = false;
2022-08-01 11:24:34 +02:00
onSuccess(outBuffer);
}
};
2024-08-12 11:04:43 -07:00
compress2Buffer(Array.from(this.entries));
2022-08-01 11:24:34 +02:00
} catch (e) {
onFail(e);
}
}
};
};