@openclaw/fs-safe 0.5.6 → 0.7.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 +89 -0
- package/README.md +23 -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-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-kind.js +2 -2
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +31 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +88 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +135 -19
- 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 +4 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- 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 +63 -42
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +26 -9
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +27 -8
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +15 -3
- 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 +22 -6
- 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.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- 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-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +26 -37
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +118 -98
- 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 +49 -120
- 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 +51 -72
- 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 +42 -29
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +48 -27
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +29 -7
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- 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/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- 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 +19 -2
- package/docs/archive.md +250 -35
- package/docs/atomic.md +11 -2
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +18 -5
- package/docs/errors.md +16 -1
- package/docs/file-store.md +2 -0
- package/docs/index.md +3 -1
- package/docs/install.md +20 -8
- package/docs/json.md +8 -4
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +30 -8
- package/docs/native.md +73 -17
- package/docs/output.md +10 -0
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +3 -1
- package/docs/secret-file.md +12 -0
- package/docs/secure-file.md +21 -3
- package/docs/security-model.md +33 -1
- package/docs/sidecar-lock.md +15 -1
- package/docs/staged-file.md +178 -0
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- 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
package/dist/archive-native.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { constants as fsConstants } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
3
|
+
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, isArchiveTarPathErrorMessage, } from "./archive-errors.js";
|
|
4
4
|
import { formatErrorDetail } from "./error-detail.js";
|
|
5
5
|
import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
6
6
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
7
|
-
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker,
|
|
7
|
+
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, } from "./archive-limits.js";
|
|
8
8
|
import { resolveArchiveEntryMode, shouldExtractArchiveEntry } from "./archive-policy.js";
|
|
9
9
|
import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
10
|
+
import { admitZipFile } from "./archive-zip-admission.js";
|
|
10
11
|
function policyKind(kind) {
|
|
11
12
|
if (kind === "file" || kind === "directory")
|
|
12
13
|
return kind;
|
|
@@ -16,6 +17,9 @@ function policyKind(kind) {
|
|
|
16
17
|
}
|
|
17
18
|
function throwMappedNativeError(error) {
|
|
18
19
|
if (error instanceof Error) {
|
|
20
|
+
if (isArchiveTarPathErrorMessage(error.message)) {
|
|
21
|
+
throw new ArchiveSecurityError("entry-path", error.message, { cause: error });
|
|
22
|
+
}
|
|
19
23
|
for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
|
|
20
24
|
if (error.message.includes(code))
|
|
21
25
|
throw new ArchiveLimitError(code);
|
|
@@ -30,23 +34,35 @@ function throwMappedNativeError(error) {
|
|
|
30
34
|
throw error;
|
|
31
35
|
}
|
|
32
36
|
export async function extractNativeArchive(params) {
|
|
33
|
-
const limits =
|
|
37
|
+
const { limits, tarLimits } = params;
|
|
34
38
|
const stagedArchive = await stageArchiveFileForExtraction({
|
|
35
39
|
archivePath: params.archivePath,
|
|
36
40
|
limits,
|
|
37
41
|
deadline: params.deadline,
|
|
38
42
|
});
|
|
39
43
|
try {
|
|
44
|
+
const physicalCount = params.kind === "zip"
|
|
45
|
+
? await admitZipFile(stagedArchive.path, limits, params.deadline)
|
|
46
|
+
: undefined;
|
|
40
47
|
const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
|
|
41
48
|
await withStagedArchiveDestination({
|
|
42
49
|
destinationRealDir,
|
|
43
50
|
run: async (stagingDir) => {
|
|
44
51
|
params.deadline.check();
|
|
45
52
|
const manifest = await params.binding
|
|
46
|
-
.inspectArchiveNative(stagedArchive.path, params.kind,
|
|
53
|
+
.inspectArchiveNative(stagedArchive.path, params.kind, tarLimits, params.deadline.signal)
|
|
47
54
|
.catch(throwMappedNativeError);
|
|
48
55
|
params.deadline.check();
|
|
49
56
|
assertArchiveEntryCountWithinLimit(manifest.length, limits);
|
|
57
|
+
if (physicalCount !== undefined && manifest.length !== physicalCount) {
|
|
58
|
+
throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
|
|
59
|
+
}
|
|
60
|
+
// Recheck the native manifest at the shared policy boundary before
|
|
61
|
+
// any caller callback observes an entry.
|
|
62
|
+
if (params.kind !== "zip") {
|
|
63
|
+
for (const entry of manifest)
|
|
64
|
+
validateArchiveEntryPath(entry.path);
|
|
65
|
+
}
|
|
50
66
|
const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
|
|
51
67
|
const budget = createByteBudgetTracker(limits);
|
|
52
68
|
const trackOutputPath = createArchiveOutputPathTracker();
|
|
@@ -54,7 +70,10 @@ export async function extractNativeArchive(params) {
|
|
|
54
70
|
for (const entry of manifest) {
|
|
55
71
|
params.deadline.check();
|
|
56
72
|
validateArchiveEntryPath(entry.path);
|
|
57
|
-
const
|
|
73
|
+
const canonicalPath = stripArchivePath(entry.path, 0);
|
|
74
|
+
if (!canonicalPath)
|
|
75
|
+
continue;
|
|
76
|
+
const relPath = stripArchivePath(canonicalPath, strip);
|
|
58
77
|
if (!relPath)
|
|
59
78
|
continue;
|
|
60
79
|
validateArchiveEntryPath(relPath);
|
|
@@ -65,21 +84,22 @@ export async function extractNativeArchive(params) {
|
|
|
65
84
|
if (!shouldExtractArchiveEntry({
|
|
66
85
|
filter: params.entryFilter,
|
|
67
86
|
onFiltered: params.onFiltered,
|
|
68
|
-
entry: { path:
|
|
87
|
+
entry: { path: canonicalPath, kind, size: entry.size },
|
|
69
88
|
})) {
|
|
70
89
|
continue;
|
|
71
90
|
}
|
|
72
91
|
if (entry.kind === "sparse") {
|
|
73
92
|
throw new ArchiveFormatError(`GNU sparse archive entry is not supported: ${formatErrorDetail(entry.path)}`);
|
|
74
93
|
}
|
|
75
|
-
if (kind === "symlink") {
|
|
94
|
+
if (kind === "symlink" || entry.kind === "blocked") {
|
|
76
95
|
const label = params.kind === "zip" ? "zip" : "tar";
|
|
77
96
|
throw new ArchiveSecurityError("entry-link", `${label} entry is a link: ${formatErrorDetail(entry.path)}`);
|
|
78
97
|
}
|
|
79
98
|
if (!Number.isSafeInteger(entry.size) || entry.size < 0) {
|
|
80
99
|
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
81
100
|
}
|
|
82
|
-
|
|
101
|
+
// GNUDumpDir can carry a body; charge accepted TAR sizes as JS does.
|
|
102
|
+
if (kind === "file" || (kind === "directory" && params.kind !== "zip")) {
|
|
83
103
|
budget.startEntry();
|
|
84
104
|
budget.addEntrySize(entry.size);
|
|
85
105
|
}
|
|
@@ -101,7 +121,7 @@ export async function extractNativeArchive(params) {
|
|
|
101
121
|
(typeof fsConstants.O_DIRECTORY === "number" ? fsConstants.O_DIRECTORY : 0));
|
|
102
122
|
try {
|
|
103
123
|
params.deadline.check();
|
|
104
|
-
await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan,
|
|
124
|
+
await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan, tarLimits, params.deadline.signal).catch(throwMappedNativeError);
|
|
105
125
|
}
|
|
106
126
|
finally {
|
|
107
127
|
await directory.close().catch(() => undefined);
|
|
@@ -111,7 +131,9 @@ export async function extractNativeArchive(params) {
|
|
|
111
131
|
sourceDir: stagingDir,
|
|
112
132
|
destinationDir: params.destDir,
|
|
113
133
|
destinationRealDir,
|
|
134
|
+
deadline: params.deadline,
|
|
114
135
|
});
|
|
136
|
+
params.deadline.check();
|
|
115
137
|
},
|
|
116
138
|
});
|
|
117
139
|
}
|
package/dist/archive-policy.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type ArchiveEntryKind = "file" | "directory" | "symlink" | "other";
|
|
|
2
2
|
export type ArchiveEntryModePolicy = "clamp" | "preserve";
|
|
3
3
|
export type ArchiveFilteredEntryPolicy = "reject-archive" | "skip-entry";
|
|
4
4
|
export type ArchiveEntryFilter = (entry: {
|
|
5
|
+
/** Validated canonical archive path before stripping: / separators, no empty or . components. */
|
|
5
6
|
path: string;
|
|
6
7
|
kind: ArchiveEntryKind;
|
|
7
8
|
size: number;
|
|
@@ -12,6 +13,7 @@ export declare function resolveArchiveEntryMode(params: {
|
|
|
12
13
|
archivedMode?: number;
|
|
13
14
|
policy?: ArchiveEntryModePolicy;
|
|
14
15
|
}): number;
|
|
16
|
+
export declare function resolveArchiveFilteredEntryPolicy(value: unknown): ArchiveFilteredEntryPolicy;
|
|
15
17
|
export declare function shouldExtractArchiveEntry(params: {
|
|
16
18
|
filter?: ArchiveEntryFilter;
|
|
17
19
|
onFiltered?: ArchiveFilteredEntryPolicy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-policy.d.ts","sourceRoot":"","sources":["../src/archive-policy.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAC1E,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,UAAU,CAAC;AAC1D,MAAM,MAAM,0BAA0B,GAAG,gBAAgB,GAAG,YAAY,CAAC;AACzE,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,KAAK,SAAS,GAAG,MAAM,CAAC;AAEzB,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAK1E;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC,GAAG,MAAM,CAST;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACxC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;CAC1C,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-policy.d.ts","sourceRoot":"","sources":["../src/archive-policy.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAC1E,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,UAAU,CAAC;AAC1D,MAAM,MAAM,0BAA0B,GAAG,gBAAgB,GAAG,YAAY,CAAC;AACzE,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE;IACvC,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,KAAK,SAAS,GAAG,MAAM,CAAC;AAEzB,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAK1E;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC,GAAG,MAAM,CAST;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,OAAO,GACb,0BAA0B,CAM5B;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACxC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;CAC1C,GAAG,OAAO,CAYV"}
|
package/dist/archive-policy.js
CHANGED
|
@@ -19,11 +19,19 @@ export function resolveArchiveEntryMode(params) {
|
|
|
19
19
|
}
|
|
20
20
|
return archivedMode & 0o100 ? 0o755 : 0o644;
|
|
21
21
|
}
|
|
22
|
+
export function resolveArchiveFilteredEntryPolicy(value) {
|
|
23
|
+
if (value === undefined || value === "reject-archive")
|
|
24
|
+
return "reject-archive";
|
|
25
|
+
if (value === "skip-entry")
|
|
26
|
+
return "skip-entry";
|
|
27
|
+
throw new RangeError('archive onFiltered must be "reject-archive" or "skip-entry"');
|
|
28
|
+
}
|
|
22
29
|
export function shouldExtractArchiveEntry(params) {
|
|
30
|
+
const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
|
|
23
31
|
if (!params.filter || params.filter(params.entry) === "extract") {
|
|
24
32
|
return true;
|
|
25
33
|
}
|
|
26
|
-
if (
|
|
34
|
+
if (onFiltered === "reject-archive") {
|
|
27
35
|
throw new ArchiveSecurityError("entry-filtered", `archive entry rejected by filter: ${formatErrorDetail(params.entry.path)}`);
|
|
28
36
|
}
|
|
29
37
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgOzE,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAuFjB"}
|
package/dist/archive-read.js
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import fsSync from "node:fs";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
2
|
import { Readable } from "node:stream";
|
|
4
3
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
5
|
-
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
4
|
+
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, isArchiveTarPathErrorMessage, } from "./archive-errors.js";
|
|
6
5
|
import { formatErrorDetail } from "./error-detail.js";
|
|
7
|
-
import {
|
|
6
|
+
import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
8
7
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
9
|
-
import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP,
|
|
8
|
+
import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_META_ENTRY_BYTES, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
|
|
10
9
|
import { readTarEntryInfo } from "./archive-tar.js";
|
|
11
10
|
import { preflightTarMetadata } from "./archive-tar-meta.js";
|
|
12
|
-
import { importOptionalTar, normalizeTarParserError } from "./archive-tar-runtime.js";
|
|
11
|
+
import { importOptionalTar, NODE_TAR_RATIO_DISABLED, normalizeTarParserError } from "./archive-tar-runtime.js";
|
|
13
12
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
14
13
|
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
15
14
|
import { FsSafeError } from "./errors.js";
|
|
16
|
-
import {
|
|
15
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
16
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
17
17
|
import { getNativeBinding } from "./native.js";
|
|
18
|
+
import { admitZipBuffer } from "./archive-zip-admission.js";
|
|
19
|
+
import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
|
|
18
20
|
import { tempFile } from "./temp-target.js";
|
|
19
21
|
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
20
22
|
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
21
|
-
function
|
|
23
|
+
function canonicalEntryPath(entryPath) {
|
|
22
24
|
validateArchiveEntryPath(entryPath, { escapeLabel: "archive root" });
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
return stripArchivePath(entryPath, 0) ?? "";
|
|
26
|
+
}
|
|
27
|
+
function normalizedRequestedEntry(entryPath) {
|
|
28
|
+
const normalized = canonicalEntryPath(entryPath);
|
|
29
|
+
if (!normalized || /[/\\]$/.test(entryPath)) {
|
|
25
30
|
throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
26
31
|
}
|
|
27
32
|
return normalized;
|
|
@@ -60,30 +65,37 @@ async function readStreamBounded(stream, maxBytes) {
|
|
|
60
65
|
}
|
|
61
66
|
async function stageArchiveInput(archivePath) {
|
|
62
67
|
const resolved = await fs.realpath(archivePath);
|
|
63
|
-
const before = await
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const handle = await fs.open(resolved,
|
|
68
|
+
const before = await inspectFileIdentity(async () => {
|
|
69
|
+
const stat = await fs.lstat(archivePath, { bigint: true });
|
|
70
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
71
|
+
throw new Error(`archive is not a regular file: ${archivePath}`);
|
|
72
|
+
}
|
|
73
|
+
return stat;
|
|
74
|
+
});
|
|
75
|
+
const handle = await fs.open(resolved, resolveReadOpenFlags());
|
|
71
76
|
const staged = await tempFile({ prefix: "fs-safe-archive-read", fileName: "archive.bin" });
|
|
72
77
|
try {
|
|
73
|
-
const opened = await
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const opened = await inspectFileIdentity(async () => {
|
|
79
|
+
const stat = await handle.stat({ bigint: true });
|
|
80
|
+
if (!stat.isFile())
|
|
81
|
+
throw new Error("archive changed during validation");
|
|
82
|
+
return stat;
|
|
83
|
+
}, before);
|
|
84
|
+
await inspectFileIdentity(async () => {
|
|
85
|
+
const stat = await fs.lstat(resolved, { bigint: true });
|
|
86
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
87
|
+
throw new Error("archive changed during validation");
|
|
88
|
+
return stat;
|
|
89
|
+
}, opened);
|
|
81
90
|
const buffer = await readFileHandleBounded(handle, DEFAULT_MAX_ARCHIVE_BYTES_ZIP);
|
|
82
91
|
await fs.writeFile(staged.path, buffer, { flag: "wx", mode: 0o600 });
|
|
83
92
|
return { path: staged.path, buffer, cleanup: staged.cleanup };
|
|
84
93
|
}
|
|
85
94
|
catch (error) {
|
|
86
95
|
await staged.cleanup().catch(() => undefined);
|
|
96
|
+
if (error instanceof FsSafeError && error.code === "path-mismatch") {
|
|
97
|
+
throw new FsSafeError("path-mismatch", "archive changed during validation", { cause: error });
|
|
98
|
+
}
|
|
87
99
|
throw error;
|
|
88
100
|
}
|
|
89
101
|
finally {
|
|
@@ -96,7 +108,17 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
|
|
|
96
108
|
maxEntryBytes: maxBytes,
|
|
97
109
|
maxExtractedBytes: maxBytes,
|
|
98
110
|
});
|
|
99
|
-
|
|
111
|
+
let entry;
|
|
112
|
+
// JSZip keys retain some aliases and may use Unicode Path metadata. Scan the
|
|
113
|
+
// effective entries once, after raw ZIP admission has rejected collisions.
|
|
114
|
+
for (const candidate of Object.values(archive.files)) {
|
|
115
|
+
if (canonicalEntryPath(candidate.name) !== entryPath)
|
|
116
|
+
continue;
|
|
117
|
+
if (entry) {
|
|
118
|
+
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(entryPath)}`);
|
|
119
|
+
}
|
|
120
|
+
entry = candidate;
|
|
121
|
+
}
|
|
100
122
|
if (!entry || entry.dir) {
|
|
101
123
|
throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
|
|
102
124
|
}
|
|
@@ -113,28 +135,41 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
|
|
|
113
135
|
}
|
|
114
136
|
async function readTarEntry(archivePath, entryPath, maxBytes) {
|
|
115
137
|
const tar = await importOptionalTar();
|
|
138
|
+
const seenPaths = new Set();
|
|
116
139
|
await preflightTarMetadata({
|
|
117
140
|
archivePath,
|
|
118
|
-
|
|
141
|
+
limits: resolveTarMeterLimits(),
|
|
142
|
+
onMember(info) {
|
|
143
|
+
const normalized = canonicalEntryPath(info.path);
|
|
144
|
+
if (seenPaths.has(normalized)) {
|
|
145
|
+
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
146
|
+
}
|
|
147
|
+
seenPaths.add(normalized);
|
|
148
|
+
if (normalized === entryPath && !["File", "OldFile", "ContiguousFile"].includes(info.type)) {
|
|
149
|
+
throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
119
152
|
});
|
|
120
153
|
let matched;
|
|
121
154
|
let entryError;
|
|
122
|
-
const seenPaths = new Set();
|
|
123
155
|
try {
|
|
124
156
|
await tar.t({
|
|
125
157
|
file: archivePath,
|
|
126
158
|
strict: true,
|
|
127
159
|
maxMetaEntrySize: DEFAULT_MAX_META_ENTRY_BYTES,
|
|
160
|
+
maxDecompressionRatio: NODE_TAR_RATIO_DISABLED,
|
|
128
161
|
onReadEntry(entry) {
|
|
129
162
|
const info = readTarEntryInfo(entry);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
163
|
+
let normalized;
|
|
164
|
+
try {
|
|
165
|
+
normalized = canonicalEntryPath(info.path);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
// Throws escaping node-tar's callback do not reject its promise.
|
|
169
|
+
entryError ??= error instanceof Error ? error : new Error(String(error));
|
|
134
170
|
entry.resume();
|
|
135
171
|
return;
|
|
136
172
|
}
|
|
137
|
-
seenPaths.add(normalized);
|
|
138
173
|
if (normalized !== entryPath) {
|
|
139
174
|
entry.resume();
|
|
140
175
|
return;
|
|
@@ -175,16 +210,22 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
175
210
|
const requestedEntry = normalizedRequestedEntry(entryPath);
|
|
176
211
|
const staged = await stageArchiveInput(archivePath);
|
|
177
212
|
try {
|
|
213
|
+
const physicalCount = kind === "zip"
|
|
214
|
+
? admitZipBuffer(staged.buffer, resolveExtractLimits())
|
|
215
|
+
: undefined;
|
|
178
216
|
const native = getNativeBinding();
|
|
179
217
|
if (native) {
|
|
180
218
|
try {
|
|
181
219
|
const signal = new AbortController().signal;
|
|
182
|
-
const
|
|
220
|
+
const limits = resolveTarMeterLimits();
|
|
221
|
+
const manifest = await native.inspectArchiveNative(staged.path, kind, limits, signal);
|
|
183
222
|
let rawEntryPath;
|
|
223
|
+
if (physicalCount !== undefined && manifest.length !== physicalCount) {
|
|
224
|
+
throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
|
|
225
|
+
}
|
|
184
226
|
const seenPaths = new Set();
|
|
185
227
|
for (const entry of manifest) {
|
|
186
|
-
|
|
187
|
-
const normalized = normalizeArchiveEntryPath(entry.path).replace(/^\.\//, "");
|
|
228
|
+
const normalized = canonicalEntryPath(entry.path);
|
|
188
229
|
if (seenPaths.has(normalized)) {
|
|
189
230
|
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
190
231
|
}
|
|
@@ -199,12 +240,17 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
199
240
|
if (!rawEntryPath) {
|
|
200
241
|
throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
|
|
201
242
|
}
|
|
202
|
-
return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes,
|
|
243
|
+
return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes, limits, signal);
|
|
203
244
|
}
|
|
204
245
|
catch (error) {
|
|
205
|
-
if (error instanceof Error &&
|
|
206
|
-
error.message
|
|
207
|
-
|
|
246
|
+
if (error instanceof Error && isArchiveTarPathErrorMessage(error.message)) {
|
|
247
|
+
throw new ArchiveSecurityError("entry-path", error.message, { cause: error });
|
|
248
|
+
}
|
|
249
|
+
if (error instanceof Error) {
|
|
250
|
+
for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
|
|
251
|
+
if (error.message.includes(code))
|
|
252
|
+
throw new ArchiveLimitError(code);
|
|
253
|
+
}
|
|
208
254
|
}
|
|
209
255
|
if (error instanceof Error && isArchiveFormatErrorMessage(error.message)) {
|
|
210
256
|
throw new ArchiveFormatError(error.message, { cause: error });
|
|
@@ -213,7 +259,8 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
213
259
|
}
|
|
214
260
|
}
|
|
215
261
|
if (kind === "tar-zstd" || kind === "tar-bzip2") {
|
|
216
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require
|
|
262
|
+
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
263
|
+
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
217
264
|
}
|
|
218
265
|
return kind === "zip"
|
|
219
266
|
? await readZipEntry(staged.buffer, requestedEntry, options.maxBytes)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ExtractionDeadline } from "./archive-deadline.js";
|
|
1
2
|
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
2
3
|
export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-errors.js";
|
|
3
4
|
export declare function prepareArchiveDestinationDir(destDir: string): Promise<string>;
|
|
@@ -8,6 +9,7 @@ export declare function prepareArchiveOutputPath(params: {
|
|
|
8
9
|
outPath: string;
|
|
9
10
|
originalPath: string;
|
|
10
11
|
isDirectory: boolean;
|
|
12
|
+
deadline?: ExtractionDeadline;
|
|
11
13
|
}): Promise<void>;
|
|
12
14
|
export declare function withStagedArchiveDestination<T>(params: {
|
|
13
15
|
destinationRealDir: string;
|
|
@@ -18,6 +20,7 @@ export declare function mergeExtractedTreeIntoDestination(params: {
|
|
|
18
20
|
sourceDir: string;
|
|
19
21
|
destinationDir: string;
|
|
20
22
|
destinationRealDir: string;
|
|
23
|
+
deadline?: ExtractionDeadline;
|
|
21
24
|
}): Promise<void>;
|
|
22
25
|
export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
|
|
23
26
|
//# sourceMappingURL=archive-staging.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAgB7B,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAkC1F,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BnF;AA4DD,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DhB;AAsID,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Bb;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FhB;AAED,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}
|