@openclaw/fs-safe 0.5.5 → 0.6.0
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/CHANGELOG.md +49 -0
- package/README.md +16 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts +2 -0
- package/dist/archive-crc32.d.ts.map +1 -0
- package/dist/archive-crc32.js +14 -0
- package/dist/archive-kind.js +2 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +24 -5
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +53 -6
- package/dist/archive-tar-pax.d.ts +8 -0
- package/dist/archive-tar-pax.d.ts.map +1 -0
- package/dist/archive-tar-pax.js +100 -0
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-zip-admission.d.ts +7 -0
- package/dist/archive-zip-admission.d.ts.map +1 -0
- package/dist/archive-zip-admission.js +60 -0
- package/dist/archive-zip-count.d.ts +2 -0
- package/dist/archive-zip-count.d.ts.map +1 -0
- package/dist/archive-zip-count.js +140 -0
- package/dist/archive-zip-directory.d.ts +8 -0
- package/dist/archive-zip-directory.d.ts.map +1 -0
- package/dist/archive-zip-directory.js +224 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +1 -14
- package/dist/archive-zip-names.d.ts +12 -0
- package/dist/archive-zip-names.d.ts.map +1 -0
- package/dist/archive-zip-names.js +99 -0
- package/dist/archive-zip-preflight.d.ts +1 -1
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -145
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +32 -23
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-identity.d.ts +1 -0
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +10 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +19 -7
- package/dist/move-path.js +2 -2
- package/dist/native-binding.d.ts +3 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +4 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +24 -8
- package/dist/native-pinned-write-windows.d.ts +8 -0
- package/dist/native-pinned-write-windows.d.ts.map +1 -0
- package/dist/native-pinned-write-windows.js +92 -0
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +136 -127
- package/dist/native-staged-file.d.ts +24 -0
- package/dist/native-staged-file.d.ts.map +1 -0
- package/dist/native-staged-file.js +337 -0
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +4 -4
- package/dist/opened-realpath.d.ts +2 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +12 -7
- package/dist/output-sibling.js +3 -3
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -1
- package/dist/permission-exec.d.ts +19 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +57 -11
- package/dist/permissions-public.d.ts +1 -1
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-windows.d.ts +3 -0
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +16 -5
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +19 -29
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +23 -15
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/replace-file-descriptor.d.ts +3 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +2 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +84 -75
- package/dist/root-paths.d.ts +11 -14
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +36 -27
- package/dist/root-write-verification.d.ts +11 -0
- package/dist/root-write-verification.d.ts.map +1 -0
- package/dist/root-write-verification.js +91 -0
- package/dist/secret-file.d.ts +1 -6
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +43 -117
- package/dist/secret-read-async.d.ts +1 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +45 -67
- package/dist/secret-read-policy.d.ts +13 -0
- package/dist/secret-read-policy.d.ts.map +1 -0
- package/dist/secret-read-policy.js +28 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +25 -16
- package/dist/sibling-temp.js +1 -1
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -1
- package/dist/staged-directory.d.ts +16 -0
- package/dist/staged-directory.d.ts.map +1 -0
- package/dist/staged-directory.js +60 -0
- package/dist/staged-file-types.d.ts +56 -0
- package/dist/staged-file-types.d.ts.map +1 -0
- package/dist/staged-file-types.js +1 -0
- package/dist/staged-file.d.ts +10 -0
- package/dist/staged-file.d.ts.map +1 -0
- package/dist/staged-file.js +15 -0
- package/dist/strict-file-identity.d.ts +6 -0
- package/dist/strict-file-identity.d.ts.map +1 -0
- package/dist/strict-file-identity.js +48 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -3
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +9 -7
- package/dist/windows-owner.d.ts +3 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +10 -2
- package/docs/advanced.md +11 -1
- package/docs/archive.md +62 -15
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +17 -4
- package/docs/errors.md +16 -1
- package/docs/index.md +3 -1
- package/docs/install.md +15 -7
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +15 -5
- package/docs/native.md +40 -11
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/root.md +1 -1
- package/docs/secret-file.md +8 -0
- package/docs/secure-file.md +17 -1
- package/docs/security-model.md +15 -1
- package/docs/sidecar-lock.md +13 -3
- package/docs/staged-file.md +178 -0
- package/docs/testing.md +1 -1
- package/docs/writing.md +19 -0
- package/package.json +16 -9
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-zip-count.d.ts","sourceRoot":"","sources":["../src/archive-zip-count.ts"],"names":[],"mappings":"AA4KA,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAU5F"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Conservative public count hint; strict admission must not rely on its fallback.
|
|
2
|
+
const ZIP_EOCD_SIGNATURE = 0x06054b50;
|
|
3
|
+
const ZIP64_EOCD_SIGNATURE = 0x06064b50;
|
|
4
|
+
const ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50;
|
|
5
|
+
const ZIP_EOCD_MIN_BYTES = 22;
|
|
6
|
+
const ZIP_EOCD_MAX_COMMENT_BYTES = 0xffff;
|
|
7
|
+
const ZIP64_ENTRY_COUNT_SENTINEL = 0xffff;
|
|
8
|
+
const ZIP64_UINT32_SENTINEL = 0xffffffff;
|
|
9
|
+
const ZIP_CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
|
|
10
|
+
const ZIP_CENTRAL_FILE_HEADER_MIN_BYTES = 46;
|
|
11
|
+
const ZIP_CENTRAL_FILE_HEADER_NAME_LENGTH_OFFSET = 28;
|
|
12
|
+
const ZIP_CENTRAL_FILE_HEADER_EXTRA_LENGTH_OFFSET = 30;
|
|
13
|
+
const ZIP_CENTRAL_FILE_HEADER_COMMENT_LENGTH_OFFSET = 32;
|
|
14
|
+
const ZIP_EOCD_TOTAL_ENTRIES_OFFSET = 10;
|
|
15
|
+
const ZIP_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET = 12;
|
|
16
|
+
const ZIP_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET = 16;
|
|
17
|
+
const ZIP_EOCD_COMMENT_LENGTH_OFFSET = 20;
|
|
18
|
+
const ZIP64_EOCD_LOCATOR_BYTES = 20;
|
|
19
|
+
const ZIP64_EOCD_OFFSET_OFFSET = 8;
|
|
20
|
+
const ZIP64_EOCD_TOTAL_ENTRIES_OFFSET = 32;
|
|
21
|
+
const ZIP64_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET = 40;
|
|
22
|
+
const ZIP64_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET = 48;
|
|
23
|
+
function asBufferView(buffer) {
|
|
24
|
+
if (Buffer.isBuffer(buffer)) {
|
|
25
|
+
return buffer;
|
|
26
|
+
}
|
|
27
|
+
return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
28
|
+
}
|
|
29
|
+
function readSafeUInt64LE(buffer, offset) {
|
|
30
|
+
const value = buffer.readBigUInt64LE(offset);
|
|
31
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
32
|
+
return Number.MAX_SAFE_INTEGER;
|
|
33
|
+
}
|
|
34
|
+
return Number(value);
|
|
35
|
+
}
|
|
36
|
+
function findZipEndOfCentralDirectory(buffer) {
|
|
37
|
+
if (buffer.byteLength < ZIP_EOCD_MIN_BYTES) {
|
|
38
|
+
return -1;
|
|
39
|
+
}
|
|
40
|
+
const minOffset = Math.max(0, buffer.byteLength - ZIP_EOCD_MIN_BYTES - ZIP_EOCD_MAX_COMMENT_BYTES);
|
|
41
|
+
for (let offset = buffer.byteLength - ZIP_EOCD_MIN_BYTES; offset >= minOffset; offset -= 1) {
|
|
42
|
+
if (buffer.readUInt32LE(offset) !== ZIP_EOCD_SIGNATURE) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const commentLength = buffer.readUInt16LE(offset + ZIP_EOCD_COMMENT_LENGTH_OFFSET);
|
|
46
|
+
if (offset + ZIP_EOCD_MIN_BYTES + commentLength === buffer.byteLength) {
|
|
47
|
+
return offset;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return -1;
|
|
51
|
+
}
|
|
52
|
+
function readZip64CentralDirectoryInfo(buffer, eocdOffset) {
|
|
53
|
+
const locatorOffset = eocdOffset - ZIP64_EOCD_LOCATOR_BYTES;
|
|
54
|
+
if (locatorOffset < 0 || buffer.readUInt32LE(locatorOffset) !== ZIP64_EOCD_LOCATOR_SIGNATURE) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const zip64EocdOffset = readSafeUInt64LE(buffer, locatorOffset + ZIP64_EOCD_OFFSET_OFFSET);
|
|
58
|
+
if (zip64EocdOffset < 0 ||
|
|
59
|
+
zip64EocdOffset + ZIP64_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET + 8 > buffer.byteLength ||
|
|
60
|
+
buffer.readUInt32LE(zip64EocdOffset) !== ZIP64_EOCD_SIGNATURE) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
declaredEntryCount: readSafeUInt64LE(buffer, zip64EocdOffset + ZIP64_EOCD_TOTAL_ENTRIES_OFFSET),
|
|
65
|
+
centralDirectorySize: readSafeUInt64LE(buffer, zip64EocdOffset + ZIP64_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET),
|
|
66
|
+
centralDirectoryOffset: readSafeUInt64LE(buffer, zip64EocdOffset + ZIP64_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET),
|
|
67
|
+
endOfCentralDirectoryOffset: eocdOffset,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function readZipCentralDirectoryInfo(buffer) {
|
|
71
|
+
const eocdOffset = findZipEndOfCentralDirectory(buffer);
|
|
72
|
+
if (eocdOffset < 0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const declaredEntryCount = buffer.readUInt16LE(eocdOffset + ZIP_EOCD_TOTAL_ENTRIES_OFFSET);
|
|
76
|
+
const centralDirectorySize = buffer.readUInt32LE(eocdOffset + ZIP_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET);
|
|
77
|
+
const centralDirectoryOffset = buffer.readUInt32LE(eocdOffset + ZIP_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET);
|
|
78
|
+
const usesZip64 = declaredEntryCount === ZIP64_ENTRY_COUNT_SENTINEL ||
|
|
79
|
+
centralDirectorySize === ZIP64_UINT32_SENTINEL ||
|
|
80
|
+
centralDirectoryOffset === ZIP64_UINT32_SENTINEL;
|
|
81
|
+
if (usesZip64) {
|
|
82
|
+
return (readZip64CentralDirectoryInfo(buffer, eocdOffset) ?? {
|
|
83
|
+
declaredEntryCount,
|
|
84
|
+
centralDirectoryOffset,
|
|
85
|
+
centralDirectorySize,
|
|
86
|
+
endOfCentralDirectoryOffset: eocdOffset,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
declaredEntryCount,
|
|
91
|
+
centralDirectoryOffset,
|
|
92
|
+
centralDirectorySize,
|
|
93
|
+
endOfCentralDirectoryOffset: eocdOffset,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function countZipCentralDirectoryHeaders(buffer, info) {
|
|
97
|
+
const start = info.centralDirectoryOffset;
|
|
98
|
+
const declaredEnd = start + info.centralDirectorySize;
|
|
99
|
+
const scanEnd = info.endOfCentralDirectoryOffset;
|
|
100
|
+
if (!Number.isSafeInteger(start) ||
|
|
101
|
+
!Number.isSafeInteger(declaredEnd) ||
|
|
102
|
+
!Number.isSafeInteger(scanEnd) ||
|
|
103
|
+
start < 0 ||
|
|
104
|
+
declaredEnd < start ||
|
|
105
|
+
scanEnd < start ||
|
|
106
|
+
scanEnd > buffer.byteLength) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
let offset = start;
|
|
110
|
+
let count = 0;
|
|
111
|
+
while (offset < scanEnd) {
|
|
112
|
+
if (scanEnd - offset < ZIP_CENTRAL_FILE_HEADER_MIN_BYTES) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (buffer.readUInt32LE(offset) !== ZIP_CENTRAL_FILE_HEADER_SIGNATURE) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
const nameLength = buffer.readUInt16LE(offset + ZIP_CENTRAL_FILE_HEADER_NAME_LENGTH_OFFSET);
|
|
119
|
+
const extraLength = buffer.readUInt16LE(offset + ZIP_CENTRAL_FILE_HEADER_EXTRA_LENGTH_OFFSET);
|
|
120
|
+
const commentLength = buffer.readUInt16LE(offset + ZIP_CENTRAL_FILE_HEADER_COMMENT_LENGTH_OFFSET);
|
|
121
|
+
const nextOffset = offset + ZIP_CENTRAL_FILE_HEADER_MIN_BYTES + nameLength + extraLength + commentLength;
|
|
122
|
+
if (nextOffset <= offset || nextOffset > scanEnd) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
count += 1;
|
|
126
|
+
offset = nextOffset;
|
|
127
|
+
}
|
|
128
|
+
return count > 0 || info.declaredEntryCount === 0 ? count : null;
|
|
129
|
+
}
|
|
130
|
+
export function readZipCentralDirectoryEntryCount(buffer) {
|
|
131
|
+
const view = asBufferView(buffer);
|
|
132
|
+
const info = readZipCentralDirectoryInfo(view);
|
|
133
|
+
if (!info) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const countedEntryCount = countZipCentralDirectoryHeaders(view, info);
|
|
137
|
+
return countedEntryCount === null
|
|
138
|
+
? info.declaredEntryCount
|
|
139
|
+
: Math.max(info.declaredEntryCount, countedEntryCount);
|
|
140
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ResolvedArchiveExtractLimits } from "./archive-limits.js";
|
|
2
|
+
export type ZipRead = {
|
|
3
|
+
offset: number;
|
|
4
|
+
length: number;
|
|
5
|
+
};
|
|
6
|
+
export type ZipScan = Generator<ZipRead, number, Buffer>;
|
|
7
|
+
export declare function scanZipDirectory(size: number, limits: ResolvedArchiveExtractLimits): ZipScan;
|
|
8
|
+
//# sourceMappingURL=archive-zip-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAmIzD,wBAAiB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,GAAG,OAAO,CA8D7F"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { assertArchiveEntryCountWithinLimit, } from "./archive-limits.js";
|
|
2
|
+
import { admitZipNames, zipExtraFields, zipFormat, zipUInt64 } from "./archive-zip-names.js";
|
|
3
|
+
function* read(offset, length, bound) {
|
|
4
|
+
if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(length) || offset < 0 || length < 0 || length > bound - offset) {
|
|
5
|
+
zipFormat("record outside archive bounds");
|
|
6
|
+
}
|
|
7
|
+
const bytes = yield { offset, length };
|
|
8
|
+
if (bytes.length !== length)
|
|
9
|
+
zipFormat("truncated record");
|
|
10
|
+
return bytes;
|
|
11
|
+
}
|
|
12
|
+
function endOffset(tail) {
|
|
13
|
+
let found = -1;
|
|
14
|
+
for (let offset = tail.length - 22; offset >= 0; offset--) {
|
|
15
|
+
if (tail.readUInt32LE(offset) === 0x06054b50 && offset + 22 + tail.readUInt16LE(offset + 20) === tail.length) {
|
|
16
|
+
if (found !== -1)
|
|
17
|
+
zipFormat("ambiguous end records");
|
|
18
|
+
found = offset;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (found < 0)
|
|
22
|
+
zipFormat("end record missing");
|
|
23
|
+
return found;
|
|
24
|
+
}
|
|
25
|
+
function* zip64End(locator, locatorAt, size) {
|
|
26
|
+
const advertised = zipUInt64(locator, 8);
|
|
27
|
+
// Seek the advertised record, or the fixed ordinary record for a prefixed
|
|
28
|
+
// archive. Never hunt through payload bytes for a ZIP64 signature.
|
|
29
|
+
const candidates = new Set([advertised, locatorAt - 56]);
|
|
30
|
+
let found;
|
|
31
|
+
for (const at of candidates) {
|
|
32
|
+
if (at < 0 || at > locatorAt - 56)
|
|
33
|
+
continue;
|
|
34
|
+
const record = yield* read(at, 56, size);
|
|
35
|
+
if (record.readUInt32LE(0) !== 0x06064b50)
|
|
36
|
+
continue;
|
|
37
|
+
const bodySize = zipUInt64(record, 4);
|
|
38
|
+
if (bodySize < 44 || bodySize !== locatorAt - at - 12)
|
|
39
|
+
continue;
|
|
40
|
+
if (found)
|
|
41
|
+
zipFormat("ambiguous ZIP64 end records");
|
|
42
|
+
found = { at, record };
|
|
43
|
+
}
|
|
44
|
+
if (!found)
|
|
45
|
+
zipFormat("unsupported ZIP64 end record");
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
function* layout(size) {
|
|
49
|
+
const tailStart = Math.max(0, size - 65_557);
|
|
50
|
+
const tail = yield* read(tailStart, size - tailStart, size);
|
|
51
|
+
const at = endOffset(tail);
|
|
52
|
+
const end = tail.subarray(at, at + 22);
|
|
53
|
+
const physicalEnd = tailStart + at;
|
|
54
|
+
if (end.readUInt16LE(4) || end.readUInt16LE(6))
|
|
55
|
+
zipFormat("multi-disk archive");
|
|
56
|
+
let count = end.readUInt16LE(10);
|
|
57
|
+
let directorySize = end.readUInt32LE(12);
|
|
58
|
+
let offset = end.readUInt32LE(16);
|
|
59
|
+
let directoryEnd = physicalEnd;
|
|
60
|
+
const wide = count === 0xffff || end.readUInt16LE(8) === 0xffff || directorySize === 0xffffffff || offset === 0xffffffff;
|
|
61
|
+
if (wide) {
|
|
62
|
+
const locator = yield* read(physicalEnd - 20, 20, size);
|
|
63
|
+
if (locator.readUInt32LE(0) !== 0x07064b50 || locator.readUInt32LE(4) || locator.readUInt32LE(16) !== 1) {
|
|
64
|
+
zipFormat("invalid ZIP64 locator");
|
|
65
|
+
}
|
|
66
|
+
const wideEnd = yield* zip64End(locator, physicalEnd - 20, size);
|
|
67
|
+
directoryEnd = wideEnd.at;
|
|
68
|
+
const record = wideEnd.record;
|
|
69
|
+
if (record.readUInt32LE(16) || record.readUInt32LE(20))
|
|
70
|
+
zipFormat("multi-disk ZIP64 archive");
|
|
71
|
+
count = zipUInt64(record, 32);
|
|
72
|
+
directorySize = zipUInt64(record, 40);
|
|
73
|
+
offset = zipUInt64(record, 48);
|
|
74
|
+
if (zipUInt64(record, 24) !== count)
|
|
75
|
+
zipFormat("ZIP64 disk count mismatch");
|
|
76
|
+
for (const [ordinary, sentinel, value] of [
|
|
77
|
+
[end.readUInt16LE(8), 0xffff, count], [end.readUInt16LE(10), 0xffff, count],
|
|
78
|
+
[end.readUInt32LE(12), 0xffffffff, directorySize], [end.readUInt32LE(16), 0xffffffff, offset],
|
|
79
|
+
]) {
|
|
80
|
+
if (ordinary !== sentinel && ordinary !== value)
|
|
81
|
+
zipFormat("conflicting ZIP64 end values");
|
|
82
|
+
}
|
|
83
|
+
const base = directoryEnd - directorySize - offset;
|
|
84
|
+
if (zipUInt64(locator, 8) !== directoryEnd - base)
|
|
85
|
+
zipFormat("ZIP64 locator offset mismatch");
|
|
86
|
+
}
|
|
87
|
+
else if (end.readUInt16LE(8) !== count)
|
|
88
|
+
zipFormat("disk count mismatch");
|
|
89
|
+
const start = directoryEnd - directorySize;
|
|
90
|
+
const base = start - offset;
|
|
91
|
+
if (base < 0 || start < 0 || !Number.isSafeInteger(base))
|
|
92
|
+
zipFormat("invalid central directory offset");
|
|
93
|
+
return { count, start, end: directoryEnd, base };
|
|
94
|
+
}
|
|
95
|
+
function wideValues(header, extra, central) {
|
|
96
|
+
let compressed = header.readUInt32LE(central ? 20 : 18);
|
|
97
|
+
let uncompressed = header.readUInt32LE(central ? 24 : 22);
|
|
98
|
+
let offset = central ? header.readUInt32LE(42) : 0;
|
|
99
|
+
let disk = central ? header.readUInt16LE(34) : 0;
|
|
100
|
+
const field = extra.get(1);
|
|
101
|
+
if (field?.length === 0)
|
|
102
|
+
zipFormat("empty ZIP64 extra field");
|
|
103
|
+
let cursor = 0;
|
|
104
|
+
const value = () => {
|
|
105
|
+
if (!field)
|
|
106
|
+
return zipFormat("ZIP64 extra field missing");
|
|
107
|
+
const result = zipUInt64(field, cursor);
|
|
108
|
+
cursor += 8;
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
const wide = compressed === 0xffffffff || uncompressed === 0xffffffff;
|
|
112
|
+
if (uncompressed === 0xffffffff)
|
|
113
|
+
uncompressed = value();
|
|
114
|
+
if (compressed === 0xffffffff)
|
|
115
|
+
compressed = value();
|
|
116
|
+
if (offset === 0xffffffff)
|
|
117
|
+
offset = value();
|
|
118
|
+
if (disk === 0xffff) {
|
|
119
|
+
if (!field || cursor + 4 > field.length)
|
|
120
|
+
zipFormat("ZIP64 disk field missing");
|
|
121
|
+
disk = field.readUInt32LE(cursor);
|
|
122
|
+
cursor += 4;
|
|
123
|
+
}
|
|
124
|
+
if (field && cursor !== field.length) {
|
|
125
|
+
// Some writers repeat both local sizes even when the 32-bit fields fit.
|
|
126
|
+
if (central || cursor !== 0 || field.length !== 16 ||
|
|
127
|
+
zipUInt64(field, 0) !== uncompressed || zipUInt64(field, 8) !== compressed) {
|
|
128
|
+
zipFormat("inconsistent ZIP64 extra field");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (disk)
|
|
132
|
+
zipFormat("multi-disk entry");
|
|
133
|
+
return { compressed, uncompressed, offset, wide };
|
|
134
|
+
}
|
|
135
|
+
function* descriptorEnd(at, bound, crc, compressed, uncompressed, wide) {
|
|
136
|
+
const length = wide ? 20 : 12;
|
|
137
|
+
const bytes = yield* read(at, Math.min(length + 4, bound - at), bound);
|
|
138
|
+
const matches = [];
|
|
139
|
+
for (const skip of [0, 4]) {
|
|
140
|
+
if (skip && (bytes.length < 4 || bytes.readUInt32LE(0) !== 0x08074b50))
|
|
141
|
+
continue;
|
|
142
|
+
if (bytes.length < length + skip || bytes.readUInt32LE(skip) !== crc)
|
|
143
|
+
continue;
|
|
144
|
+
const c = wide ? zipUInt64(bytes, skip + 4) : bytes.readUInt32LE(skip + 4);
|
|
145
|
+
const u = wide ? zipUInt64(bytes, skip + 12) : bytes.readUInt32LE(skip + 8);
|
|
146
|
+
if (c === compressed && u === uncompressed)
|
|
147
|
+
matches.push(at + length + skip);
|
|
148
|
+
}
|
|
149
|
+
if (matches.length !== 1)
|
|
150
|
+
zipFormat("invalid or ambiguous data descriptor");
|
|
151
|
+
return matches[0];
|
|
152
|
+
}
|
|
153
|
+
export function* scanZipDirectory(size, limits) {
|
|
154
|
+
const directory = yield* layout(size);
|
|
155
|
+
assertArchiveEntryCountWithinLimit(directory.count, limits);
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
const spans = [];
|
|
158
|
+
let at = directory.start;
|
|
159
|
+
let count = 0;
|
|
160
|
+
while (at < directory.end) {
|
|
161
|
+
const central = yield* read(at, Math.min(46, directory.end - at), directory.end);
|
|
162
|
+
if (central.length >= 6 && central.readUInt32LE(0) === 0x05054b50) {
|
|
163
|
+
// The optional digital signature belongs to the directory size, not its
|
|
164
|
+
// entry count. It is opaque metadata, not an authenticity guarantee.
|
|
165
|
+
if (central.readUInt16LE(4) !== directory.end - at - 6)
|
|
166
|
+
zipFormat("invalid directory signature length");
|
|
167
|
+
at = directory.end;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
assertArchiveEntryCountWithinLimit(++count, limits);
|
|
171
|
+
if (central.length < 46 || central.readUInt32LE(0) !== 0x02014b50)
|
|
172
|
+
zipFormat("invalid central header");
|
|
173
|
+
const nameLength = central.readUInt16LE(28);
|
|
174
|
+
const extraLength = central.readUInt16LE(30);
|
|
175
|
+
const next = at + 46 + nameLength + extraLength + central.readUInt16LE(32);
|
|
176
|
+
if (next > directory.end)
|
|
177
|
+
zipFormat("central record exceeds directory");
|
|
178
|
+
const names = yield* read(at + 46, nameLength + extraLength, directory.end);
|
|
179
|
+
const centralName = names.subarray(0, nameLength);
|
|
180
|
+
const centralExtra = zipExtraFields(names.subarray(nameLength));
|
|
181
|
+
const values = wideValues(central, centralExtra, true);
|
|
182
|
+
const flags = central.readUInt16LE(8);
|
|
183
|
+
if (flags & ~0x080e)
|
|
184
|
+
zipFormat("unsupported entry flags");
|
|
185
|
+
const localAt = directory.base + values.offset;
|
|
186
|
+
if (localAt < directory.base)
|
|
187
|
+
zipFormat("local offset precedes archive base");
|
|
188
|
+
const local = yield* read(localAt, 30, directory.start);
|
|
189
|
+
if (local.readUInt32LE(0) !== 0x04034b50 || local.readUInt16LE(6) !== flags || local.readUInt16LE(8) !== central.readUInt16LE(10)) {
|
|
190
|
+
zipFormat("local and central framing disagree");
|
|
191
|
+
}
|
|
192
|
+
const localNameLength = local.readUInt16LE(26);
|
|
193
|
+
const localExtraLength = local.readUInt16LE(28);
|
|
194
|
+
const localNames = yield* read(localAt + 30, localNameLength + localExtraLength, directory.start);
|
|
195
|
+
const localExtra = zipExtraFields(localNames.subarray(localNameLength));
|
|
196
|
+
admitZipNames({ central: centralName, local: localNames.subarray(0, localNameLength), flags, centralExtra, localExtra, seen });
|
|
197
|
+
const localValues = wideValues(local, localExtra, false);
|
|
198
|
+
const crc = central.readUInt32LE(16);
|
|
199
|
+
if (!(flags & 8) && (local.readUInt32LE(14) !== crc || localValues.compressed !== values.compressed || localValues.uncompressed !== values.uncompressed)) {
|
|
200
|
+
zipFormat("local and central sizes or CRC disagree");
|
|
201
|
+
}
|
|
202
|
+
if ((flags & 8) && ((local.readUInt32LE(14) !== 0 && local.readUInt32LE(14) !== crc) ||
|
|
203
|
+
(localValues.compressed !== 0 && localValues.compressed !== values.compressed) ||
|
|
204
|
+
(localValues.uncompressed !== 0 && localValues.uncompressed !== values.uncompressed))) {
|
|
205
|
+
zipFormat("data descriptor placeholders disagree");
|
|
206
|
+
}
|
|
207
|
+
const dataStart = localAt + 30 + localNameLength + localExtraLength;
|
|
208
|
+
if (values.compressed > directory.start - dataStart)
|
|
209
|
+
zipFormat("entry data exceeds local area");
|
|
210
|
+
let dataEnd = dataStart + values.compressed;
|
|
211
|
+
if (flags & 8)
|
|
212
|
+
dataEnd = yield* descriptorEnd(dataEnd, directory.start, crc, values.compressed, values.uncompressed, values.wide || localValues.wide);
|
|
213
|
+
spans.push({ start: localAt, end: dataEnd });
|
|
214
|
+
at = next;
|
|
215
|
+
}
|
|
216
|
+
if (count !== directory.count)
|
|
217
|
+
zipFormat("physical and declared entry counts disagree");
|
|
218
|
+
spans.sort((a, b) => a.start - b.start);
|
|
219
|
+
for (let i = 1; i < spans.length; i++) {
|
|
220
|
+
if (spans[i].start < spans[i - 1].end)
|
|
221
|
+
zipFormat("overlapping local entries");
|
|
222
|
+
}
|
|
223
|
+
return count;
|
|
224
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-integrity.d.ts","sourceRoot":"","sources":["../src/archive-zip-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-zip-integrity.d.ts","sourceRoot":"","sources":["../src/archive-zip-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;AAIhC,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAQhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,CAgCtE"}
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
2
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
3
3
|
import { hasDeferredEmptyZipData, zipEntryIntegrityMetadata, } from "./archive-zip-entry.js";
|
|
4
|
-
|
|
5
|
-
let value = index;
|
|
6
|
-
for (let bit = 0; bit < 8; bit += 1) {
|
|
7
|
-
value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
8
|
-
}
|
|
9
|
-
return value >>> 0;
|
|
10
|
-
});
|
|
11
|
-
function updateCrc32(previous, buffer) {
|
|
12
|
-
let crc = previous ^ -1;
|
|
13
|
-
for (const byte of buffer) {
|
|
14
|
-
crc = (crc >>> 8) ^ (CRC32_TABLE[(crc ^ byte) & 0xff] ?? 0);
|
|
15
|
-
}
|
|
16
|
-
return (crc ^ -1) >>> 0;
|
|
17
|
-
}
|
|
4
|
+
import { updateCrc32 } from "./archive-crc32.js";
|
|
18
5
|
export function normalizeZipIntegrityError(error) {
|
|
19
6
|
if (error instanceof Error &&
|
|
20
7
|
error.message.includes("uncompressed data size mismatch")) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function zipFormat(message: string): never;
|
|
2
|
+
export declare function zipUInt64(bytes: Buffer, offset: number): number;
|
|
3
|
+
export declare function zipExtraFields(bytes: Buffer): Map<number, Buffer>;
|
|
4
|
+
export declare function admitZipNames(params: {
|
|
5
|
+
central: Buffer;
|
|
6
|
+
local: Buffer;
|
|
7
|
+
flags: number;
|
|
8
|
+
centralExtra: Map<number, Buffer>;
|
|
9
|
+
localExtra: Map<number, Buffer>;
|
|
10
|
+
seen: Set<string>;
|
|
11
|
+
}): void;
|
|
12
|
+
//# sourceMappingURL=archive-zip-names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AA6BD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG,IAAI,CA6BP"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
|
|
2
|
+
import { validateArchiveEntryPath } from "./archive-entry.js";
|
|
3
|
+
import { updateCrc32 } from "./archive-crc32.js";
|
|
4
|
+
export function zipFormat(message) {
|
|
5
|
+
throw new ArchiveFormatError(`invalid ZIP metadata: ${message}`);
|
|
6
|
+
}
|
|
7
|
+
export function zipUInt64(bytes, offset) {
|
|
8
|
+
if (offset + 8 > bytes.length)
|
|
9
|
+
zipFormat("truncated ZIP64 field");
|
|
10
|
+
const value = bytes.readBigUInt64LE(offset);
|
|
11
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER))
|
|
12
|
+
zipFormat("ZIP64 integer exceeds safe range");
|
|
13
|
+
return Number(value);
|
|
14
|
+
}
|
|
15
|
+
export function zipExtraFields(bytes) {
|
|
16
|
+
const critical = new Map();
|
|
17
|
+
for (let offset = 0; offset < bytes.length;) {
|
|
18
|
+
if (offset + 4 > bytes.length)
|
|
19
|
+
zipFormat("truncated extra field header");
|
|
20
|
+
const id = bytes.readUInt16LE(offset);
|
|
21
|
+
const length = bytes.readUInt16LE(offset + 2);
|
|
22
|
+
offset += 4;
|
|
23
|
+
if (length > bytes.length - offset)
|
|
24
|
+
zipFormat("truncated extra field value");
|
|
25
|
+
if (id === 1 || id === 0x7075) {
|
|
26
|
+
if (critical.has(id))
|
|
27
|
+
zipFormat("duplicate critical extra field");
|
|
28
|
+
critical.set(id, bytes.subarray(offset, offset + length));
|
|
29
|
+
}
|
|
30
|
+
offset += length;
|
|
31
|
+
}
|
|
32
|
+
return critical;
|
|
33
|
+
}
|
|
34
|
+
function utf8(bytes) {
|
|
35
|
+
try {
|
|
36
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return zipFormat("invalid UTF-8 name");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function key(name) {
|
|
43
|
+
return name.replaceAll("\\", "/").split("/").filter((part) => part && part !== ".").join("/");
|
|
44
|
+
}
|
|
45
|
+
function originalName(name, flags) {
|
|
46
|
+
// ASCII path syntax is encoding-independent. Do not reinterpret legacy bytes
|
|
47
|
+
// as UTF-8 (native ZIP uses CP437); the selected decoder still validates its name.
|
|
48
|
+
validateArchiveEntryPath(Array.from(name, (byte) => byte < 128 ? String.fromCharCode(byte) : "_").join(""));
|
|
49
|
+
if (!(flags & 0x800))
|
|
50
|
+
return undefined;
|
|
51
|
+
const decoded = utf8(name);
|
|
52
|
+
validateArchiveEntryPath(decoded);
|
|
53
|
+
return decoded;
|
|
54
|
+
}
|
|
55
|
+
function unicodeName(name, extra) {
|
|
56
|
+
const field = extra.get(0x7075);
|
|
57
|
+
if (!field)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (field.length < 6 || field[0] !== 1)
|
|
60
|
+
zipFormat("invalid Unicode Path field");
|
|
61
|
+
if (field.readUInt32LE(1) !== updateCrc32(0, name))
|
|
62
|
+
zipFormat("Unicode Path CRC mismatch");
|
|
63
|
+
const decoded = utf8(field.subarray(5));
|
|
64
|
+
validateArchiveEntryPath(decoded);
|
|
65
|
+
return decoded;
|
|
66
|
+
}
|
|
67
|
+
export function admitZipNames(params) {
|
|
68
|
+
const { central, local, flags, centralExtra, localExtra, seen } = params;
|
|
69
|
+
if (!central.length || !local.length)
|
|
70
|
+
zipFormat("empty entry name");
|
|
71
|
+
const centralUtf8 = originalName(central, flags);
|
|
72
|
+
const localUtf8 = originalName(local, flags);
|
|
73
|
+
const centralUnicode = unicodeName(central, centralExtra);
|
|
74
|
+
const localUnicode = unicodeName(local, localExtra);
|
|
75
|
+
if (key(central.toString("latin1")) !== key(local.toString("latin1"))) {
|
|
76
|
+
zipFormat("central and local names disagree");
|
|
77
|
+
}
|
|
78
|
+
const interpretations = [centralUtf8, localUtf8, centralUnicode, localUnicode].filter((value) => value !== undefined);
|
|
79
|
+
if (interpretations.some((value) => key(value) !== key(interpretations[0]))) {
|
|
80
|
+
zipFormat("conflicting Unicode name interpretations");
|
|
81
|
+
}
|
|
82
|
+
// JSZip checks the central Unicode field against the local name. A slash-only
|
|
83
|
+
// spelling difference must not make one decoder ignore a meaningful override.
|
|
84
|
+
if (centralUnicode && !central.equals(local) &&
|
|
85
|
+
key(centralUnicode) !== key(local.toString("utf8"))) {
|
|
86
|
+
zipFormat("Unicode override disagrees with local decoder name");
|
|
87
|
+
}
|
|
88
|
+
if (!centralUnicode && localUnicode && key(localUnicode) !== key(local.toString("utf8"))) {
|
|
89
|
+
zipFormat("local-only Unicode override changes the name");
|
|
90
|
+
}
|
|
91
|
+
const identities = new Set([key(central.toString("latin1"))]);
|
|
92
|
+
if (centralUnicode !== undefined)
|
|
93
|
+
identities.add(key(Buffer.from(centralUnicode).toString("latin1")));
|
|
94
|
+
if ([...identities].some((identity) => seen.has(identity))) {
|
|
95
|
+
throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
|
|
96
|
+
}
|
|
97
|
+
for (const identity of identities)
|
|
98
|
+
seen.add(identity);
|
|
99
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ArchiveExtractLimits } from "./archive-limits.js";
|
|
2
|
+
export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
|
|
2
3
|
export type ZipArchiveWithFiles = {
|
|
3
4
|
files: Record<string, unknown>;
|
|
4
5
|
};
|
|
5
|
-
export declare function readZipCentralDirectoryEntryCount(buffer: Buffer | Uint8Array): number | null;
|
|
6
6
|
export declare function loadZipArchiveWithPreflight(buffer: Buffer | Uint8Array, limits?: ArchiveExtractLimits): Promise<ZipArchiveWithFiles>;
|
|
7
7
|
//# sourceMappingURL=archive-zip-preflight.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,wBAAwB,CAAC;AAE3E,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAMF,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAwB9B"}
|