@zip.js/zip.js 2.8.49 → 2.8.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist/zip-core-external.js +333 -203
- package/dist/zip-core-external.min.js +1 -1
- package/dist/zip-core.js +339 -203
- package/dist/zip-core.min.js +1 -1
- package/dist/zip-fs-core-external.js +567 -332
- package/dist/zip-fs-core-external.min.js +1 -1
- package/dist/zip-fs-core.js +339 -547
- package/dist/zip-fs-core.min.js +1 -1
- package/dist/zip-fs-external.js +567 -332
- package/dist/zip-fs-external.min.js +1 -1
- package/dist/zip-fs-native.js +574 -332
- package/dist/zip-fs-native.min.js +1 -1
- package/dist/zip-fs.js +574 -332
- package/dist/zip-fs.min.js +1 -1
- package/dist/zip-legacy.js +339 -203
- package/dist/zip-legacy.min.js +1 -1
- package/dist/zip-native.js +339 -203
- package/dist/zip-native.min.js +1 -1
- package/dist/zip.js +339 -203
- package/dist/zip.min.js +1 -1
- package/index-native.cjs +573 -331
- package/index-native.min.js +1 -1
- package/index.cjs +573 -331
- package/index.d.cts +167 -2
- package/index.d.ts +167 -2
- package/index.min.js +1 -1
- package/lib/core/codec-registry.js +2 -2
- package/lib/core/codec-worker-web.js +436 -0
- package/lib/core/codec-worker.js +23 -404
- package/lib/core/constants.js +5 -1
- package/lib/core/io.js +2 -1
- package/lib/core/options.js +8 -0
- package/lib/core/zip-fs.js +215 -110
- package/lib/core/zip-reader.js +90 -16
- package/lib/core/zip-writer.js +36 -13
- package/lib/zip-core-reader.js +6 -0
- package/lib/zip-core-writer.js +4 -0
- package/package.json +1 -1
package/lib/core/zip-fs.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
/* global WritableStream */
|
|
29
|
+
/* global WritableStream, AbortController, DOMException */
|
|
30
30
|
// deno-lint-ignore-file no-this-alias
|
|
31
31
|
|
|
32
32
|
import {
|
|
@@ -71,6 +71,9 @@ import { toCompatibleReadable, toCompatibleWritable } from "./util/compatible-st
|
|
|
71
71
|
|
|
72
72
|
const ERR_ENTRY_EXISTS = "Entry filename already exists";
|
|
73
73
|
const ERR_READABLE_CONSUMED = "Readable stream already consumed";
|
|
74
|
+
const ERR_ABORT_EXPORT = "zipjs-abort-export";
|
|
75
|
+
const ERR_ABORTED = "The operation was aborted";
|
|
76
|
+
const ABORT_ERROR_NAME = "AbortError";
|
|
74
77
|
const INFOZIP_EXTRA_FIELD_TYPE = "infozip";
|
|
75
78
|
const INTERPRETED_EXTRA_FIELD_TYPES = new Set([
|
|
76
79
|
EXTRAFIELD_TYPE_ZIP64,
|
|
@@ -179,8 +182,9 @@ class ZipFileEntry extends ZipEntry {
|
|
|
179
182
|
const uncompressedSize = zipEntry.data ? zipEntry.data.uncompressedSize : reader.size;
|
|
180
183
|
await Promise.all([initStream(reader), initStream(writer, uncompressedSize)]);
|
|
181
184
|
const { readable } = reader;
|
|
185
|
+
const { signal } = options;
|
|
182
186
|
zipEntry.uncompressedSize = reader.size;
|
|
183
|
-
await toCompatibleReadable(readable).pipeTo(toCompatibleWritable(writer.writable));
|
|
187
|
+
await toCompatibleReadable(readable).pipeTo(toCompatibleWritable(writer.writable), { signal });
|
|
184
188
|
return writer.getData ? writer.getData() : writer.writable;
|
|
185
189
|
}
|
|
186
190
|
}
|
|
@@ -447,7 +451,7 @@ class ZipDirectoryEntry extends ZipEntry {
|
|
|
447
451
|
for (const entry of entries) {
|
|
448
452
|
let parent = this;
|
|
449
453
|
try {
|
|
450
|
-
const path = entry.filename.split("/");
|
|
454
|
+
const path = entry.filename.split("/").filter(pathPart => pathPart != "" && pathPart != ".");
|
|
451
455
|
const name = path.pop();
|
|
452
456
|
path.forEach(pathPart => {
|
|
453
457
|
const previousParent = parent;
|
|
@@ -522,6 +526,10 @@ class ZipDirectoryEntry extends ZipEntry {
|
|
|
522
526
|
}
|
|
523
527
|
}
|
|
524
528
|
|
|
529
|
+
getChildren(options = {}) {
|
|
530
|
+
return collectChildren(this, options.recursive);
|
|
531
|
+
}
|
|
532
|
+
|
|
525
533
|
isPasswordProtected() {
|
|
526
534
|
const children = this.children;
|
|
527
535
|
for (let childIndex = 0; childIndex < children.length; childIndex++) {
|
|
@@ -613,6 +621,10 @@ class FS {
|
|
|
613
621
|
return this.root.getChildByName(name);
|
|
614
622
|
}
|
|
615
623
|
|
|
624
|
+
getChildren(options) {
|
|
625
|
+
return this.root.getChildren(options);
|
|
626
|
+
}
|
|
627
|
+
|
|
616
628
|
addDirectory(name, options) {
|
|
617
629
|
return this.root.addDirectory(name, options);
|
|
618
630
|
}
|
|
@@ -721,7 +733,7 @@ class FS {
|
|
|
721
733
|
}
|
|
722
734
|
|
|
723
735
|
const fs = { FS, ZipDirectoryEntry, ZipFileEntry };
|
|
724
|
-
export { fs, ERR_ENTRY_EXISTS, ERR_READABLE_CONSUMED };
|
|
736
|
+
export { fs, ERR_ENTRY_EXISTS, ERR_READABLE_CONSUMED, ERR_ABORTED };
|
|
725
737
|
|
|
726
738
|
function getTotalSize(entries, propertyName) {
|
|
727
739
|
let size = 0;
|
|
@@ -823,6 +835,39 @@ function detach(entry) {
|
|
|
823
835
|
}
|
|
824
836
|
}
|
|
825
837
|
|
|
838
|
+
function forwardAbort(signal, abortController) {
|
|
839
|
+
if (!signal) {
|
|
840
|
+
return () => { };
|
|
841
|
+
}
|
|
842
|
+
if (signal.aborted) {
|
|
843
|
+
abortController.abort(signal.reason);
|
|
844
|
+
return () => { };
|
|
845
|
+
}
|
|
846
|
+
const abort = () => abortController.abort(signal.reason);
|
|
847
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
848
|
+
return () => signal.removeEventListener("abort", abort);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function isExportAborted(error) {
|
|
852
|
+
return Boolean(error) && error.message == ERR_ABORT_EXPORT;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function aggregateEntryErrors(errors) {
|
|
856
|
+
const [error] = errors;
|
|
857
|
+
const otherErrors = errors
|
|
858
|
+
.slice(1)
|
|
859
|
+
.flatMap(otherError => [otherError, ...(otherError && otherError.entryErrors || [])])
|
|
860
|
+
.filter(otherError => otherError !== error);
|
|
861
|
+
if (otherErrors.length) {
|
|
862
|
+
try {
|
|
863
|
+
error.entryErrors = [...(error.entryErrors || []), ...otherErrors];
|
|
864
|
+
} catch {
|
|
865
|
+
// ignored
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return error;
|
|
869
|
+
}
|
|
870
|
+
|
|
826
871
|
function getUserExtraField(extraField) {
|
|
827
872
|
if (extraField) {
|
|
828
873
|
const userExtraField = new Map();
|
|
@@ -840,111 +885,110 @@ function getUserExtraField(extraField) {
|
|
|
840
885
|
async function exportZip(zipWriter, entry, totalSize, options, readers) {
|
|
841
886
|
const selectedEntry = entry;
|
|
842
887
|
const entryOffsets = new Map();
|
|
843
|
-
|
|
888
|
+
if (options.bufferedWrite) {
|
|
889
|
+
await processChildren(entry);
|
|
890
|
+
} else {
|
|
891
|
+
for (const child of entry.getChildren({ recursive: true })) {
|
|
892
|
+
await addChild(child);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
844
895
|
|
|
845
|
-
async function
|
|
846
|
-
await
|
|
896
|
+
async function processChildren(entry) {
|
|
897
|
+
const results = await Promise.allSettled(entry.children.map(async child => {
|
|
898
|
+
await addChild(child);
|
|
899
|
+
await processChildren(child);
|
|
900
|
+
}));
|
|
901
|
+
const errorResult = results.find(result => result.status == "rejected");
|
|
902
|
+
if (errorResult) {
|
|
903
|
+
throw errorResult.reason;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
847
906
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
907
|
+
async function addChild(child) {
|
|
908
|
+
const name = options.relativePath ? child.getRelativeName(selectedEntry) : child.getFullname();
|
|
909
|
+
const childOptions = child.options || {};
|
|
910
|
+
let zipEntryOptions = {};
|
|
911
|
+
if (child.data instanceof Entry) {
|
|
912
|
+
const {
|
|
913
|
+
externalFileAttributes,
|
|
914
|
+
versionMadeBy,
|
|
915
|
+
comment,
|
|
916
|
+
lastModDate,
|
|
917
|
+
creationDate,
|
|
918
|
+
lastAccessDate,
|
|
919
|
+
uncompressedSize,
|
|
920
|
+
encrypted,
|
|
921
|
+
zipCrypto,
|
|
922
|
+
crc32,
|
|
923
|
+
compressionMethod,
|
|
924
|
+
extraFieldAES,
|
|
925
|
+
internalFileAttributes,
|
|
926
|
+
extraField,
|
|
927
|
+
uid,
|
|
928
|
+
gid
|
|
929
|
+
} = child.data;
|
|
930
|
+
zipEntryOptions = {
|
|
931
|
+
externalFileAttributes,
|
|
932
|
+
versionMadeBy,
|
|
933
|
+
comment,
|
|
934
|
+
lastModDate,
|
|
935
|
+
creationDate,
|
|
936
|
+
lastAccessDate,
|
|
937
|
+
internalFileAttributes
|
|
938
|
+
};
|
|
939
|
+
const userExtraField = getUserExtraField(extraField);
|
|
940
|
+
if (userExtraField) {
|
|
941
|
+
zipEntryOptions.extraField = userExtraField;
|
|
942
|
+
}
|
|
943
|
+
if (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE) {
|
|
944
|
+
Object.assign(zipEntryOptions, {
|
|
945
|
+
uid,
|
|
946
|
+
gid,
|
|
947
|
+
unixExtraFieldType: options.unixExtraFieldType || INFOZIP_EXTRA_FIELD_TYPE
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
if (child.passThrough) {
|
|
951
|
+
let level, encryptionStrength;
|
|
952
|
+
if (compressionMethod === 0) {
|
|
953
|
+
level = 0;
|
|
854
954
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
await processChild(child);
|
|
955
|
+
if (extraFieldAES) {
|
|
956
|
+
encryptionStrength = extraFieldAES.strength;
|
|
858
957
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
async function processChild(child) {
|
|
863
|
-
const name = options.relativePath ? child.getRelativeName(selectedEntry) : child.getFullname();
|
|
864
|
-
const childOptions = child.options || {};
|
|
865
|
-
let zipEntryOptions = {};
|
|
866
|
-
if (child.data instanceof Entry) {
|
|
867
|
-
const {
|
|
868
|
-
externalFileAttributes,
|
|
869
|
-
versionMadeBy,
|
|
870
|
-
comment,
|
|
871
|
-
lastModDate,
|
|
872
|
-
creationDate,
|
|
873
|
-
lastAccessDate,
|
|
874
|
-
uncompressedSize,
|
|
958
|
+
zipEntryOptions = Object.assign(zipEntryOptions, {
|
|
959
|
+
passThrough: true,
|
|
875
960
|
encrypted,
|
|
876
961
|
zipCrypto,
|
|
877
962
|
crc32,
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
gid
|
|
884
|
-
} = child.data;
|
|
885
|
-
zipEntryOptions = {
|
|
886
|
-
externalFileAttributes,
|
|
887
|
-
versionMadeBy,
|
|
888
|
-
comment,
|
|
889
|
-
lastModDate,
|
|
890
|
-
creationDate,
|
|
891
|
-
lastAccessDate,
|
|
892
|
-
internalFileAttributes
|
|
893
|
-
};
|
|
894
|
-
const userExtraField = getUserExtraField(extraField);
|
|
895
|
-
if (userExtraField) {
|
|
896
|
-
zipEntryOptions.extraField = userExtraField;
|
|
897
|
-
}
|
|
898
|
-
if (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE) {
|
|
899
|
-
Object.assign(zipEntryOptions, {
|
|
900
|
-
uid,
|
|
901
|
-
gid,
|
|
902
|
-
unixExtraFieldType: options.unixExtraFieldType || INFOZIP_EXTRA_FIELD_TYPE
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
if (child.passThrough) {
|
|
906
|
-
let level, encryptionStrength;
|
|
907
|
-
if (compressionMethod === 0) {
|
|
908
|
-
level = 0;
|
|
909
|
-
}
|
|
910
|
-
if (extraFieldAES) {
|
|
911
|
-
encryptionStrength = extraFieldAES.strength;
|
|
912
|
-
}
|
|
913
|
-
zipEntryOptions = Object.assign(zipEntryOptions, {
|
|
914
|
-
passThrough: true,
|
|
915
|
-
encrypted,
|
|
916
|
-
zipCrypto,
|
|
917
|
-
crc32,
|
|
918
|
-
uncompressedSize,
|
|
919
|
-
level,
|
|
920
|
-
encryptionStrength,
|
|
921
|
-
compressionMethod
|
|
922
|
-
});
|
|
923
|
-
}
|
|
963
|
+
uncompressedSize,
|
|
964
|
+
level,
|
|
965
|
+
encryptionStrength,
|
|
966
|
+
compressionMethod
|
|
967
|
+
});
|
|
924
968
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
969
|
+
}
|
|
970
|
+
await zipWriter.add(name, readers.get(child), Object.assign({}, options, zipEntryOptions, childOptions, {
|
|
971
|
+
directory: child.directory,
|
|
972
|
+
onprogress: async indexProgress => {
|
|
973
|
+
if (options.onprogress) {
|
|
974
|
+
entryOffsets.set(name, indexProgress);
|
|
975
|
+
try {
|
|
976
|
+
await options.onprogress(Array.from(entryOffsets.values()).reduce((previousValue, currentValue) => previousValue + currentValue), totalSize);
|
|
977
|
+
} catch {
|
|
978
|
+
// ignored
|
|
935
979
|
}
|
|
936
980
|
}
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
}
|
|
981
|
+
}
|
|
982
|
+
}));
|
|
940
983
|
}
|
|
941
984
|
}
|
|
942
985
|
|
|
943
986
|
function addFileSystemHandle(zipEntry, handle, options) {
|
|
944
987
|
return addFile(zipEntry, handle, []);
|
|
945
988
|
|
|
946
|
-
async function addFile(parentEntry, handle, addedEntries) {
|
|
989
|
+
async function addFile(parentEntry, handle, addedEntries, parentName = "") {
|
|
947
990
|
if (handle) {
|
|
991
|
+
const entryName = parentName ? parentName + "/" + handle.name : handle.name;
|
|
948
992
|
try {
|
|
949
993
|
if (handle.isFile || handle.isDirectory) {
|
|
950
994
|
handle = await transformToFileSystemhandle(handle);
|
|
@@ -966,12 +1010,18 @@ function addFileSystemHandle(zipEntry, handle, options) {
|
|
|
966
1010
|
const directoryEntry = parentEntry.addDirectory(handle.name);
|
|
967
1011
|
addedEntries.push(directoryEntry);
|
|
968
1012
|
for await (const childHandle of handle.values()) {
|
|
969
|
-
await addFile(directoryEntry, childHandle, addedEntries);
|
|
1013
|
+
await addFile(directoryEntry, childHandle, addedEntries, entryName);
|
|
970
1014
|
}
|
|
971
1015
|
}
|
|
972
1016
|
} catch (error) {
|
|
973
|
-
|
|
974
|
-
|
|
1017
|
+
try {
|
|
1018
|
+
if (error.entryName === UNDEFINED_VALUE) {
|
|
1019
|
+
error.entryName = entryName;
|
|
1020
|
+
}
|
|
1021
|
+
} catch {
|
|
1022
|
+
// ignored
|
|
1023
|
+
}
|
|
1024
|
+
throw error;
|
|
975
1025
|
}
|
|
976
1026
|
}
|
|
977
1027
|
return addedEntries;
|
|
@@ -981,15 +1031,33 @@ function addFileSystemHandle(zipEntry, handle, options) {
|
|
|
981
1031
|
async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
|
|
982
1032
|
const totalSize = getTotalSize([zipEntry], "uncompressedSize");
|
|
983
1033
|
const writtenSizes = new Map();
|
|
984
|
-
|
|
1034
|
+
const abortController = new AbortController();
|
|
1035
|
+
const { signal } = abortController;
|
|
1036
|
+
const releaseSignal = forwardAbort(options.signal, abortController);
|
|
1037
|
+
const exportedEntryNames = [];
|
|
1038
|
+
let exportAborted = false;
|
|
1039
|
+
try {
|
|
1040
|
+
await exportChildren(zipEntry, directoryHandle);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
try {
|
|
1043
|
+
error.exportedEntryNames = exportedEntryNames;
|
|
1044
|
+
} catch {
|
|
1045
|
+
// ignored
|
|
1046
|
+
}
|
|
1047
|
+
throw error;
|
|
1048
|
+
} finally {
|
|
1049
|
+
releaseSignal();
|
|
1050
|
+
}
|
|
985
1051
|
return directoryHandle;
|
|
986
1052
|
|
|
987
1053
|
async function exportChildren(entry, parentHandle) {
|
|
988
1054
|
if (options.concurrent) {
|
|
989
1055
|
const results = await Promise.allSettled(entry.children.map(child => exportChild(child, parentHandle)));
|
|
990
|
-
const
|
|
991
|
-
if (
|
|
992
|
-
|
|
1056
|
+
const rejectedResults = results.filter(result => result.status == "rejected");
|
|
1057
|
+
if (rejectedResults.length) {
|
|
1058
|
+
const failedResults = rejectedResults.filter(result => !isExportAborted(result.reason));
|
|
1059
|
+
const reportedResults = failedResults.length ? failedResults : rejectedResults;
|
|
1060
|
+
throw aggregateEntryErrors(reportedResults.map(result => result.reason));
|
|
993
1061
|
}
|
|
994
1062
|
} else {
|
|
995
1063
|
for (const child of entry.children) {
|
|
@@ -999,6 +1067,12 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
|
|
|
999
1067
|
}
|
|
1000
1068
|
|
|
1001
1069
|
async function exportChild(child, parentHandle) {
|
|
1070
|
+
if (signal.aborted) {
|
|
1071
|
+
if (exportAborted || isExportAborted(signal.reason)) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
throw signal.reason === UNDEFINED_VALUE ? new DOMException(ERR_ABORTED, ABORT_ERROR_NAME) : signal.reason;
|
|
1075
|
+
}
|
|
1002
1076
|
try {
|
|
1003
1077
|
if (child.directory) {
|
|
1004
1078
|
const childDirectoryHandle = await parentHandle.getDirectoryHandle(child.name, { create: true });
|
|
@@ -1006,21 +1080,37 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
|
|
|
1006
1080
|
} else {
|
|
1007
1081
|
const fileHandle = await parentHandle.getFileHandle(child.name, { create: true });
|
|
1008
1082
|
const writable = await fileHandle.createWritable();
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1083
|
+
try {
|
|
1084
|
+
await child.getData({ writable }, Object.assign({}, options, {
|
|
1085
|
+
signal,
|
|
1086
|
+
onprogress: async progress => {
|
|
1087
|
+
if (options.onprogress) {
|
|
1088
|
+
writtenSizes.set(child.id, progress);
|
|
1089
|
+
try {
|
|
1090
|
+
await options.onprogress(Array.from(writtenSizes.values()).reduce((previousValue, currentValue) => previousValue + currentValue, 0), totalSize);
|
|
1091
|
+
} catch {
|
|
1092
|
+
// ignored
|
|
1093
|
+
}
|
|
1017
1094
|
}
|
|
1018
1095
|
}
|
|
1019
|
-
}
|
|
1020
|
-
})
|
|
1096
|
+
}));
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
throw exportAborted ? new Error(ERR_ABORT_EXPORT) : error;
|
|
1099
|
+
}
|
|
1100
|
+
exportedEntryNames.push(child.getRelativeName(zipEntry));
|
|
1021
1101
|
}
|
|
1022
1102
|
} catch (error) {
|
|
1023
|
-
|
|
1103
|
+
exportAborted = true;
|
|
1104
|
+
abortController.abort(new Error(ERR_ABORT_EXPORT));
|
|
1105
|
+
try {
|
|
1106
|
+
if (error.entryName === UNDEFINED_VALUE) {
|
|
1107
|
+
error.entryName = child.getRelativeName(zipEntry);
|
|
1108
|
+
error.entryId = child.id;
|
|
1109
|
+
}
|
|
1110
|
+
} catch {
|
|
1111
|
+
// ignored
|
|
1112
|
+
}
|
|
1113
|
+
throw error;
|
|
1024
1114
|
}
|
|
1025
1115
|
}
|
|
1026
1116
|
}
|
|
@@ -1082,6 +1172,21 @@ function resetFS(fs) {
|
|
|
1082
1172
|
fs.root = new ZipDirectoryEntry(fs);
|
|
1083
1173
|
}
|
|
1084
1174
|
|
|
1175
|
+
function collectChildren(directory, recursive) {
|
|
1176
|
+
const children = [];
|
|
1177
|
+
const pendingDirectories = [directory];
|
|
1178
|
+
let directoryIndex = 0;
|
|
1179
|
+
while (directoryIndex < pendingDirectories.length) {
|
|
1180
|
+
for (const child of pendingDirectories[directoryIndex++].children) {
|
|
1181
|
+
children.push(child);
|
|
1182
|
+
if (recursive) {
|
|
1183
|
+
pendingDirectories.push(child);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return children;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1085
1190
|
function registerEntries(fs, entry) {
|
|
1086
1191
|
const pendingEntries = [entry];
|
|
1087
1192
|
while (pendingEntries.length) {
|
package/lib/core/zip-reader.js
CHANGED
|
@@ -88,6 +88,8 @@ import {
|
|
|
88
88
|
FILE_ATTR_UNIX_SETUID_MASK,
|
|
89
89
|
FILE_ATTR_UNIX_SETGID_MASK,
|
|
90
90
|
FILE_ATTR_UNIX_STICKY_MASK,
|
|
91
|
+
NUMBER_TYPE,
|
|
92
|
+
STRING_TYPE,
|
|
91
93
|
EMPTY_UINT8_ARRAY
|
|
92
94
|
} from "./constants.js";
|
|
93
95
|
import { getConfiguration } from "./configuration.js";
|
|
@@ -150,8 +152,12 @@ import {
|
|
|
150
152
|
OPTION_CHECK_CRC32,
|
|
151
153
|
OPTION_CHECK_AUTHENTICATION_CODE,
|
|
152
154
|
OPTION_STRICTNESS,
|
|
155
|
+
OPTION_FILENAME_VALIDATION,
|
|
156
|
+
OPTION_NORMALIZE_FILENAME,
|
|
153
157
|
OPTION_MAX_APPENDED_DATA_SIZE,
|
|
154
158
|
OPTION_DECRYPT_CENTRAL_DIRECTORY,
|
|
159
|
+
TEXT_TYPE_FILENAME,
|
|
160
|
+
TEXT_TYPE_COMMENT,
|
|
155
161
|
STRICTNESS_STRICT,
|
|
156
162
|
STRICTNESS_BALANCED,
|
|
157
163
|
STRICTNESS_TOLERANT,
|
|
@@ -174,6 +180,11 @@ const ERR_SPLIT_ZIP_FILE = "Split zip file";
|
|
|
174
180
|
const ERR_OVERLAPPING_ENTRY = "Overlapping entry found";
|
|
175
181
|
const ERR_AMBIGUOUS_ARCHIVE = "Ambiguous archive";
|
|
176
182
|
const ERR_ENCRYPTED_CENTRAL_DIRECTORY = "Encrypted central directory is not supported";
|
|
183
|
+
const ERR_UNSAFE_FILENAME = "Unsafe filename";
|
|
184
|
+
const ERR_INVALID_STRICTNESS = "Invalid strictness (must be 'strict', 'balanced' or 'tolerant')";
|
|
185
|
+
const ERR_INVALID_FILENAME_VALIDATION = "Invalid filenameValidation (must be 'strict', 'balanced' or 'tolerant')";
|
|
186
|
+
const ERR_INVALID_MAX_APPENDED_DATA_SIZE = "Invalid maxAppendedDataSize (must be a number greater than or equal to 0)";
|
|
187
|
+
const DRIVE_LETTER_REGEXP = /^[a-zA-Z]:/;
|
|
177
188
|
const CHARSET_UTF8 = "utf-8";
|
|
178
189
|
const PROPERTY_NAME_UTF8_SUFFIX = "UTF8";
|
|
179
190
|
const CHARSET_CP437 = "cp437";
|
|
@@ -195,6 +206,10 @@ const ZIP64_EXTRACTION = {
|
|
|
195
206
|
bytes: 8
|
|
196
207
|
}
|
|
197
208
|
};
|
|
209
|
+
const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
|
|
210
|
+
const CENTRAL_DIRECTORY_UNREACHABLE = 0;
|
|
211
|
+
const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
|
|
212
|
+
const CENTRAL_DIRECTORY_REACHABLE = 2;
|
|
198
213
|
|
|
199
214
|
class ZipReader {
|
|
200
215
|
|
|
@@ -223,6 +238,8 @@ class ZipReader {
|
|
|
223
238
|
const checkAmbiguity = strictness == STRICTNESS_STRICT;
|
|
224
239
|
const rejectAmbiguousEndOfDirectory = strictness != STRICTNESS_TOLERANT;
|
|
225
240
|
const maxAppendedDataSize = getMaxAppendedDataSize(getOptionValue(zipReader, options, OPTION_MAX_APPENDED_DATA_SIZE), strictness);
|
|
241
|
+
const filenameValidation = getFilenameValidation(getOptionValue(zipReader, options, OPTION_FILENAME_VALIDATION), strictness);
|
|
242
|
+
const normalizeFilename = getOptionValue(zipReader, options, OPTION_NORMALIZE_FILENAME);
|
|
226
243
|
const { endOfDirectoryInfo, endOfDirectoryReachingEndCount } = await findEndOfCentralDirectory(reader, rejectAmbiguousEndOfDirectory, maxAppendedDataSize);
|
|
227
244
|
if (!endOfDirectoryInfo) {
|
|
228
245
|
const signatureArray = await readUint8Array(reader, 0, 4);
|
|
@@ -416,11 +433,22 @@ class ZipReader {
|
|
|
416
433
|
const decode = getOptionValue(zipReader, options, OPTION_DECODE_TEXT) || decodeText;
|
|
417
434
|
const rawFilenameEncoding = filenameUTF8 ? CHARSET_UTF8 : filenameEncoding || CHARSET_CP437;
|
|
418
435
|
const rawCommentEncoding = commentUTF8 ? CHARSET_UTF8 : commentEncoding || CHARSET_CP437;
|
|
419
|
-
let filename = decode(rawFilename, rawFilenameEncoding);
|
|
436
|
+
let filename = decode(rawFilename, rawFilenameEncoding, TEXT_TYPE_FILENAME);
|
|
420
437
|
if (filename === UNDEFINED_VALUE) {
|
|
421
438
|
filename = decodeText(rawFilename, rawFilenameEncoding);
|
|
422
439
|
}
|
|
423
|
-
|
|
440
|
+
if (normalizeFilename) {
|
|
441
|
+
const normalizedFilename = normalizeFilename(filename);
|
|
442
|
+
if (normalizedFilename !== UNDEFINED_VALUE) {
|
|
443
|
+
filename = normalizedFilename;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (isUnsafeFilename(filename, filenameValidation)) {
|
|
447
|
+
const error = new Error(ERR_UNSAFE_FILENAME);
|
|
448
|
+
error.filename = filename;
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
let comment = decode(rawComment, rawCommentEncoding, TEXT_TYPE_COMMENT);
|
|
424
452
|
if (comment === UNDEFINED_VALUE) {
|
|
425
453
|
comment = decodeText(rawComment, rawCommentEncoding);
|
|
426
454
|
}
|
|
@@ -481,10 +509,10 @@ class ZipReader {
|
|
|
481
509
|
entry.getData = (writer, options) => fileEntry.getData(writer, entry, zipReader.readRanges, options);
|
|
482
510
|
entry.arrayBuffer = async options => {
|
|
483
511
|
const writer = new TransformStream();
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return
|
|
512
|
+
const arrayBufferPromise = streamToBlob(writer.readable).then(blob => blob.arrayBuffer());
|
|
513
|
+
arrayBufferPromise.catch(() => { });
|
|
514
|
+
await fileEntry.getData(writer, entry, zipReader.readRanges, options);
|
|
515
|
+
return arrayBufferPromise;
|
|
488
516
|
};
|
|
489
517
|
offset = endOffset;
|
|
490
518
|
const { onprogress } = options;
|
|
@@ -600,6 +628,10 @@ export {
|
|
|
600
628
|
ERR_OVERLAPPING_ENTRY,
|
|
601
629
|
ERR_AMBIGUOUS_ARCHIVE,
|
|
602
630
|
ERR_ENCRYPTED_CENTRAL_DIRECTORY,
|
|
631
|
+
ERR_UNSAFE_FILENAME,
|
|
632
|
+
ERR_INVALID_STRICTNESS,
|
|
633
|
+
ERR_INVALID_FILENAME_VALIDATION,
|
|
634
|
+
ERR_INVALID_MAX_APPENDED_DATA_SIZE,
|
|
603
635
|
ERR_WORKER_STARTUP_TIMEOUT
|
|
604
636
|
};
|
|
605
637
|
|
|
@@ -760,7 +792,7 @@ class ZipEntry {
|
|
|
760
792
|
readRanges
|
|
761
793
|
});
|
|
762
794
|
}
|
|
763
|
-
let writable;
|
|
795
|
+
let writable, abortError;
|
|
764
796
|
try {
|
|
765
797
|
if (!checkOverlappingEntryOnly) {
|
|
766
798
|
if (checkPasswordOnly) {
|
|
@@ -780,12 +812,22 @@ class ZipEntry {
|
|
|
780
812
|
writer.size += error.outputSize;
|
|
781
813
|
}
|
|
782
814
|
if (!checkPasswordOnly || error.message != ERR_ABORT_CHECK_PASSWORD) {
|
|
815
|
+
abortError = error;
|
|
783
816
|
throw error;
|
|
784
817
|
}
|
|
785
818
|
} finally {
|
|
786
819
|
const preventClose = getOptionValue(zipEntry, options, OPTION_PREVENT_CLOSE);
|
|
787
820
|
if (!preventClose && writable && !writable.locked) {
|
|
788
|
-
|
|
821
|
+
const writableWriter = writable.getWriter();
|
|
822
|
+
if (abortError) {
|
|
823
|
+
try {
|
|
824
|
+
await writableWriter.abort(abortError);
|
|
825
|
+
} catch {
|
|
826
|
+
// the error being propagated is more relevant; ignored
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
await writableWriter.close();
|
|
830
|
+
}
|
|
789
831
|
}
|
|
790
832
|
}
|
|
791
833
|
return checkPasswordOnly || checkOverlappingEntryOnly ? UNDEFINED_VALUE : writer.getData ? writer.getData() : writable;
|
|
@@ -1165,16 +1207,51 @@ function getDiskOffset(reader, diskNumber) {
|
|
|
1165
1207
|
return reader.getDiskOffset ? reader.getDiskOffset(diskNumber) : 0;
|
|
1166
1208
|
}
|
|
1167
1209
|
|
|
1210
|
+
function isStrictnessValue(value) {
|
|
1211
|
+
return value === STRICTNESS_STRICT || value === STRICTNESS_BALANCED || value === STRICTNESS_TOLERANT;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1168
1214
|
function getStrictness(strictness, checkAmbiguity) {
|
|
1169
1215
|
if (strictness === UNDEFINED_VALUE) {
|
|
1170
1216
|
return checkAmbiguity ? STRICTNESS_STRICT : STRICTNESS_BALANCED;
|
|
1171
1217
|
}
|
|
1218
|
+
if (!isStrictnessValue(strictness)) {
|
|
1219
|
+
throw new Error(ERR_INVALID_STRICTNESS);
|
|
1220
|
+
}
|
|
1172
1221
|
return strictness;
|
|
1173
1222
|
}
|
|
1174
1223
|
|
|
1224
|
+
function getFilenameValidation(filenameValidation, strictness) {
|
|
1225
|
+
if (filenameValidation === UNDEFINED_VALUE) {
|
|
1226
|
+
return strictness;
|
|
1227
|
+
}
|
|
1228
|
+
if (!isStrictnessValue(filenameValidation)) {
|
|
1229
|
+
throw new Error(ERR_INVALID_FILENAME_VALIDATION);
|
|
1230
|
+
}
|
|
1231
|
+
return filenameValidation;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function isUnsafeFilename(filename, filenameValidation) {
|
|
1235
|
+
if (filenameValidation == STRICTNESS_TOLERANT) {
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1238
|
+
const pathParts = filename.split("/");
|
|
1239
|
+
if (pathParts.length > 1 && pathParts[pathParts.length - 1] === "") {
|
|
1240
|
+
pathParts.pop();
|
|
1241
|
+
}
|
|
1242
|
+
if (pathParts.includes("..") || filename.startsWith("/") || filename.startsWith("\\\\") || DRIVE_LETTER_REGEXP.test(filename)) {
|
|
1243
|
+
return true;
|
|
1244
|
+
}
|
|
1245
|
+
return filenameValidation == STRICTNESS_STRICT && (pathParts.includes(".") || pathParts.includes(""));
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1175
1248
|
function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
|
|
1176
1249
|
if (maxAppendedDataSize !== UNDEFINED_VALUE) {
|
|
1177
|
-
|
|
1250
|
+
const size = toNumber(maxAppendedDataSize);
|
|
1251
|
+
if (typeof size != NUMBER_TYPE || Number.isNaN(size) || size < 0) {
|
|
1252
|
+
throw new Error(ERR_INVALID_MAX_APPENDED_DATA_SIZE);
|
|
1253
|
+
}
|
|
1254
|
+
return size;
|
|
1178
1255
|
}
|
|
1179
1256
|
if (strictness == STRICTNESS_STRICT) {
|
|
1180
1257
|
return 0;
|
|
@@ -1185,12 +1262,6 @@ function getMaxAppendedDataSize(maxAppendedDataSize, strictness) {
|
|
|
1185
1262
|
return MAX_16_BITS;
|
|
1186
1263
|
}
|
|
1187
1264
|
|
|
1188
|
-
const MAX_END_OF_CENTRAL_DIR_PROBES = 64;
|
|
1189
|
-
|
|
1190
|
-
const CENTRAL_DIRECTORY_UNREACHABLE = 0;
|
|
1191
|
-
const CENTRAL_DIRECTORY_PLAUSIBLE = 1;
|
|
1192
|
-
const CENTRAL_DIRECTORY_REACHABLE = 2;
|
|
1193
|
-
|
|
1194
1265
|
async function findEndOfCentralDirectory(reader, rejectAmbiguous, maxAppendedDataSize) {
|
|
1195
1266
|
const { size } = reader;
|
|
1196
1267
|
const anchoredLength = Math.min(size, END_OF_CENTRAL_DIR_LENGTH + MAX_16_BITS);
|
|
@@ -1326,6 +1397,10 @@ function getOptionValue(zipReader, options, name) {
|
|
|
1326
1397
|
return options[name] === UNDEFINED_VALUE ? zipReader.options[name] : options[name];
|
|
1327
1398
|
}
|
|
1328
1399
|
|
|
1400
|
+
function toNumber(value) {
|
|
1401
|
+
return typeof value == STRING_TYPE && value.trim() ? Number(value) : value;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1329
1404
|
function getDate(timeRaw) {
|
|
1330
1405
|
const date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & MAX_16_BITS;
|
|
1331
1406
|
return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2, 0);
|
|
@@ -1350,4 +1425,3 @@ function getUint32(view, offset) {
|
|
|
1350
1425
|
function getBigUint64(view, offset) {
|
|
1351
1426
|
return Number(view.getBigUint64(offset, true));
|
|
1352
1427
|
}
|
|
1353
|
-
|