@openclaw/fs-safe 0.6.0 → 0.7.1
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 +79 -0
- package/README.md +7 -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-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 +24 -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 +69 -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 +104 -35
- package/dist/archive-tar-runtime.d.ts +3 -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-names.js +2 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +38 -27
- 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/directory-guard.d.ts +2 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +10 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +81 -30
- package/dist/file-observation.d.ts +9 -0
- package/dist/file-observation.d.ts.map +1 -0
- package/dist/file-observation.js +22 -0
- 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 +32 -13
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +12 -3
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +1 -1
- package/dist/opened-file-failure.d.ts +7 -0
- package/dist/opened-file-failure.d.ts.map +1 -0
- package/dist/opened-file-failure.js +41 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +8 -2
- 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/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +10 -10
- 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 +113 -82
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +6 -5
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +17 -13
- 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 +91 -37
- 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 +16 -5
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +60 -28
- package/dist/sidecar-lock-root.d.ts +3 -0
- package/dist/sidecar-lock-root.d.ts.map +1 -0
- package/dist/sidecar-lock-root.js +76 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +4 -1
- 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/docs/advanced.md +8 -1
- package/docs/archive.md +202 -26
- package/docs/atomic.md +11 -2
- package/docs/durability.md +1 -1
- package/docs/file-store.md +2 -0
- package/docs/install.md +8 -4
- package/docs/json.md +11 -4
- package/docs/native-helper.md +15 -3
- package/docs/native.md +33 -6
- package/docs/output.md +16 -3
- 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 +2 -0
- package/docs/secret-file.md +5 -1
- package/docs/secure-file.md +4 -2
- package/docs/security-model.md +18 -0
- package/docs/sidecar-lock.md +60 -4
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/docs/writing.md +6 -0
- package/package.json +10 -10
package/dist/secret-file.js
CHANGED
|
@@ -3,11 +3,13 @@ import fsp from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
5
5
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
6
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
6
7
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
7
8
|
import { FsSafeError } from "./errors.js";
|
|
8
9
|
import { sameFileIdentity } from "./file-identity.js";
|
|
9
10
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
10
11
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
12
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
11
13
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
12
14
|
import { assertSecretFilePreview, DEFAULT_SECRET_FILE_MAX_BYTES, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
13
15
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
@@ -19,7 +21,9 @@ export function readSecretFileSync(filePath, label, options = {}) {
|
|
|
19
21
|
if (!resolvedPath) {
|
|
20
22
|
throw new FsSafeError("invalid-path", `${label} file path is empty.`, { cause: undefined });
|
|
21
23
|
}
|
|
22
|
-
const maxBytes = options.maxBytes
|
|
24
|
+
const maxBytes = normalizeMaxBytes(options.maxBytes, {
|
|
25
|
+
defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
|
|
26
|
+
});
|
|
23
27
|
function inspectInput(symlinkMessage) {
|
|
24
28
|
const stat = options.rejectSymlink
|
|
25
29
|
? fs.lstatSync(resolvedPath, { bigint: true })
|
|
@@ -112,8 +116,7 @@ async function enforcePrivatePathMode(resolvedPath, expectedMode, kind) {
|
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
async function enforcePrivateFileIdentityAndMode(resolvedPath, expectedIdentity, expectedMode) {
|
|
115
|
-
const
|
|
116
|
-
const handle = await fsp.open(resolvedPath, fs.constants.O_RDONLY | noFollowFlag);
|
|
119
|
+
const handle = await fsp.open(resolvedPath, resolveReadOpenFlags());
|
|
117
120
|
try {
|
|
118
121
|
const openedStat = await handle.stat();
|
|
119
122
|
if (!openedStat.isFile() || !sameFileIdentity(openedStat, expectedIdentity)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAQA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA8DjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
4
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
4
5
|
import { FsSafeError } from "./errors.js";
|
|
5
6
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
7
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
6
8
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
7
9
|
import { assertSecretFilePreview, DEFAULT_SECRET_FILE_MAX_BYTES, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
8
10
|
export async function readSecretFile(filePath, label, options = {}) {
|
|
@@ -10,7 +12,9 @@ export async function readSecretFile(filePath, label, options = {}) {
|
|
|
10
12
|
if (!resolvedPath) {
|
|
11
13
|
throw new FsSafeError("invalid-path", `${label} file path is empty.`, { cause: undefined });
|
|
12
14
|
}
|
|
13
|
-
const maxBytes = options.maxBytes
|
|
15
|
+
const maxBytes = normalizeMaxBytes(options.maxBytes, {
|
|
16
|
+
defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
|
|
17
|
+
});
|
|
14
18
|
async function inspectInput(symlinkMessage) {
|
|
15
19
|
const stat = options.rejectSymlink
|
|
16
20
|
? await fs.lstat(resolvedPath, { bigint: true })
|
|
@@ -32,10 +36,7 @@ export async function readSecretFile(filePath, label, options = {}) {
|
|
|
32
36
|
let raw;
|
|
33
37
|
try {
|
|
34
38
|
const realPath = await fs.realpath(resolvedPath);
|
|
35
|
-
|
|
36
|
-
? fsSync.constants.O_NOFOLLOW
|
|
37
|
-
: 0;
|
|
38
|
-
handle = await fs.open(realPath, fsSync.constants.O_RDONLY | noFollow);
|
|
39
|
+
handle = await fs.open(realPath, resolveReadOpenFlags());
|
|
39
40
|
const openedHandle = handle;
|
|
40
41
|
const openedStat = await inspectFileIdentity(async () => {
|
|
41
42
|
const stat = await openedHandle.stat({ bigint: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAarC,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AA4LF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAe/B"}
|
package/dist/secure-file.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { constants as fsConstants } from "node:fs";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
4
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
5
5
|
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { isWindowsDriveLetterPath, isWindowsNetworkPath } from "./local-file-access.js";
|
|
8
8
|
import { isPathInside, isSymlinkOpenError } from "./path.js";
|
|
9
9
|
import { formatPermissionErrorDetail } from "./permission-exec.js";
|
|
10
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
11
|
import { inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, } from "./permissions.js";
|
|
11
12
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
12
|
-
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
|
|
13
|
-
const OPEN_READ_FLAGS = fsConstants.O_RDONLY | (SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
|
|
14
13
|
function isAbsolutePathname(value) {
|
|
15
14
|
return (path.isAbsolute(value) ||
|
|
16
15
|
(process.platform === "win32" &&
|
|
@@ -19,7 +18,7 @@ function isAbsolutePathname(value) {
|
|
|
19
18
|
function label(options) {
|
|
20
19
|
return options.label ?? "Secure file";
|
|
21
20
|
}
|
|
22
|
-
async function openSecureHandle(options) {
|
|
21
|
+
async function openSecureHandle(options, maxBytes) {
|
|
23
22
|
assertNoUnsafeDeviceReadPath(options.filePath);
|
|
24
23
|
if (isWindowsNetworkPath(options.filePath, "win32") && !options.trust?.allowNetworkPath) {
|
|
25
24
|
throw new FsSafeError("invalid-path", `${label(options)} must be a local absolute path.`);
|
|
@@ -32,15 +31,19 @@ async function openSecureHandle(options) {
|
|
|
32
31
|
cause: err,
|
|
33
32
|
});
|
|
34
33
|
});
|
|
35
|
-
if (preStat.
|
|
36
|
-
|
|
34
|
+
if (preStat.isSymbolicLink()) {
|
|
35
|
+
if (!options.trust?.allowSymlink) {
|
|
36
|
+
throw new FsSafeError("symlink", `${label(options)} must not be a symlink: ${options.filePath}`);
|
|
37
|
+
}
|
|
37
38
|
}
|
|
38
|
-
if (preStat.
|
|
39
|
-
throw new FsSafeError("
|
|
39
|
+
else if (!preStat.isFile()) {
|
|
40
|
+
throw new FsSafeError("not-file", `${label(options)} must be a file: ${options.filePath}`);
|
|
40
41
|
}
|
|
41
42
|
let handle;
|
|
42
43
|
try {
|
|
43
|
-
handle = await fs.open(options.filePath,
|
|
44
|
+
handle = await fs.open(options.filePath, resolveReadOpenFlags({
|
|
45
|
+
followSymlinks: options.trust?.allowSymlink === true,
|
|
46
|
+
}));
|
|
44
47
|
}
|
|
45
48
|
catch (err) {
|
|
46
49
|
if (isSymlinkOpenError(err)) {
|
|
@@ -65,8 +68,8 @@ async function openSecureHandle(options) {
|
|
|
65
68
|
}, openedIdentity);
|
|
66
69
|
const realPath = await fs.realpath(options.filePath);
|
|
67
70
|
await inspectFileIdentity(() => fs.stat(realPath, { bigint: true }), openedIdentity);
|
|
68
|
-
if (
|
|
69
|
-
throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${
|
|
71
|
+
if (maxBytes !== undefined && openedStat.size > maxBytes) {
|
|
72
|
+
throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${maxBytes}).`);
|
|
70
73
|
}
|
|
71
74
|
return { handle, pathStat: openedStat, realPath };
|
|
72
75
|
}
|
|
@@ -165,11 +168,12 @@ async function readHandleWithTimeout(handle, timeoutMs, maxBytes) {
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
export async function readSecureFile(options) {
|
|
168
|
-
const
|
|
171
|
+
const maxBytes = normalizeMaxBytes(options.io?.maxBytes);
|
|
172
|
+
const opened = await openSecureHandle(options, maxBytes);
|
|
169
173
|
try {
|
|
170
174
|
await assertTrustedDirs(options, opened.realPath);
|
|
171
175
|
const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath);
|
|
172
|
-
const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs,
|
|
176
|
+
const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs, maxBytes);
|
|
173
177
|
return { buffer, realPath: opened.realPath, stat: opened.pathStat, permissions };
|
|
174
178
|
}
|
|
175
179
|
finally {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function writeCallbackSibling<T>(params: {
|
|
2
|
+
tempPath: string;
|
|
3
|
+
write: (tempPath: string) => Promise<T>;
|
|
4
|
+
resolveFinalPath: (result: T) => string;
|
|
5
|
+
mode?: number;
|
|
6
|
+
/** Preserve the caller's historical best-effort mode behavior. */
|
|
7
|
+
ignoreModeError?: boolean;
|
|
8
|
+
maxBytes?: number;
|
|
9
|
+
syncTempFile: boolean;
|
|
10
|
+
syncParentDir: boolean;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
filePath: string;
|
|
13
|
+
result: T;
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=sibling-staged-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAkCA,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CA6G3C"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
|
+
import fs, {} from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
|
+
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
9
|
+
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
10
|
+
import { serializePathWrite } from "./write-queue.js";
|
|
11
|
+
function assertRegularFile(stat) {
|
|
12
|
+
if (stat.isSymbolicLink()) {
|
|
13
|
+
throw new FsSafeError("symlink", "symlink sibling temp not allowed");
|
|
14
|
+
}
|
|
15
|
+
if (!stat.isFile()) {
|
|
16
|
+
throw new FsSafeError("not-file", "sibling temp must be a regular file");
|
|
17
|
+
}
|
|
18
|
+
if (stat.nlink !== 1n) {
|
|
19
|
+
throw new FsSafeError("hardlink", "sibling temp must have exactly one link");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function inspectStage(inspect, expected) {
|
|
23
|
+
return await inspectFileIdentity(async () => {
|
|
24
|
+
const stat = await inspect();
|
|
25
|
+
assertRegularFile(stat);
|
|
26
|
+
return stat;
|
|
27
|
+
}, expected);
|
|
28
|
+
}
|
|
29
|
+
// Callback paths are not owned until all three admission observations agree.
|
|
30
|
+
// Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
|
|
31
|
+
// Read/write access is needed only when the caller requests file synchronization.
|
|
32
|
+
export async function writeCallbackSibling(params) {
|
|
33
|
+
const parent = path.dirname(params.tempPath);
|
|
34
|
+
const guard = await createAsyncDirectoryGuard(parent);
|
|
35
|
+
const parentIdentity = await inspectFileIdentity(() => fs.lstat(parent, { bigint: true }));
|
|
36
|
+
const assertParent = async () => {
|
|
37
|
+
await assertAsyncDirectoryGuard(guard);
|
|
38
|
+
await inspectFileIdentity(() => fs.lstat(parent, { bigint: true }), parentIdentity);
|
|
39
|
+
};
|
|
40
|
+
let handle;
|
|
41
|
+
let identity;
|
|
42
|
+
let unregister;
|
|
43
|
+
let renamed = false;
|
|
44
|
+
let failure;
|
|
45
|
+
const inspectPath = (pathname, expected) => inspectStage(() => fs.lstat(pathname, { bigint: true }), expected);
|
|
46
|
+
const assertCurrent = async (pathname) => {
|
|
47
|
+
await assertParent();
|
|
48
|
+
const opened = await inspectStage(() => handle.stat({ bigint: true }), identity);
|
|
49
|
+
const current = await inspectPath(pathname, opened);
|
|
50
|
+
if (params.maxBytes !== undefined &&
|
|
51
|
+
(opened.size > params.maxBytes || current.size > params.maxBytes)) {
|
|
52
|
+
throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${params.maxBytes})`);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
const result = await params.write(params.tempPath);
|
|
57
|
+
await assertParent();
|
|
58
|
+
const before = await inspectPath(params.tempPath);
|
|
59
|
+
try {
|
|
60
|
+
// No create/truncate flags; O_NONBLOCK also bounds a FIFO swap during open.
|
|
61
|
+
const access = params.syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
|
|
62
|
+
handle = await fs.open(params.tempPath, access | resolveReadOpenFlags());
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error?.code === "ELOOP") {
|
|
66
|
+
throw new FsSafeError("symlink", "symlink sibling temp not allowed", { cause: error });
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
const opened = await inspectStage(() => handle.stat({ bigint: true }), before);
|
|
71
|
+
await inspectPath(params.tempPath, opened);
|
|
72
|
+
await assertParent();
|
|
73
|
+
identity = opened;
|
|
74
|
+
unregister = registerTempPathForExit(params.tempPath, { identity, singleLinkFile: true });
|
|
75
|
+
const filePath = path.resolve(params.resolveFinalPath(result));
|
|
76
|
+
if (path.dirname(filePath) !== parent) {
|
|
77
|
+
throw new Error("Final path must be in the sibling temp directory.");
|
|
78
|
+
}
|
|
79
|
+
if (filePath === params.tempPath) {
|
|
80
|
+
throw new FsSafeError("invalid-path", "final path must differ from the sibling temp path");
|
|
81
|
+
}
|
|
82
|
+
await serializePathWrite(filePath, async () => {
|
|
83
|
+
await assertCurrent(params.tempPath);
|
|
84
|
+
if (params.mode !== undefined) {
|
|
85
|
+
try {
|
|
86
|
+
await handle.chmod(params.mode);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (!params.ignoreModeError)
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (params.syncTempFile) {
|
|
94
|
+
await assertCurrent(params.tempPath);
|
|
95
|
+
try {
|
|
96
|
+
await handle.sync();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error?.code !== "EPERM")
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
await assertCurrent(params.tempPath);
|
|
104
|
+
await fs.rename(params.tempPath, filePath);
|
|
105
|
+
// A later verification failure never authorizes rollback of the final name.
|
|
106
|
+
renamed = true;
|
|
107
|
+
unregister();
|
|
108
|
+
await assertCurrent(filePath);
|
|
109
|
+
if (params.syncParentDir)
|
|
110
|
+
await syncDirectoryBestEffort(parent);
|
|
111
|
+
await assertCurrent(filePath);
|
|
112
|
+
});
|
|
113
|
+
return { filePath, result };
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
failure = { error };
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
try {
|
|
121
|
+
if (!renamed && identity) {
|
|
122
|
+
try {
|
|
123
|
+
await assertParent();
|
|
124
|
+
await inspectStage(() => handle.stat({ bigint: true }), identity);
|
|
125
|
+
await inspectPath(params.tempPath, identity);
|
|
126
|
+
await fs.unlink(params.tempPath);
|
|
127
|
+
unregister?.();
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
// Preserve observed substitutes; retry only operational cleanup failures.
|
|
131
|
+
if (error instanceof FsSafeError || error?.code === "ENOENT") {
|
|
132
|
+
unregister?.();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
try {
|
|
139
|
+
await handle?.close();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (failure)
|
|
143
|
+
throw new AggregateError([failure.error, error], "sibling publication and close failed");
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
package/dist/sibling-temp.d.ts
CHANGED
|
@@ -5,8 +5,11 @@ export type WriteSiblingTempFileOptions<T> = {
|
|
|
5
5
|
tempPrefix?: string;
|
|
6
6
|
dirMode?: number;
|
|
7
7
|
chmodDir?: boolean;
|
|
8
|
+
/** Final file mode; omitted preserves the producer's mode. Applied through the retained descriptor. */
|
|
8
9
|
mode?: number;
|
|
10
|
+
/** Sync the staged descriptor before rename; defaults to false. */
|
|
9
11
|
syncTempFile?: boolean;
|
|
12
|
+
/** Best-effort parent directory sync after rename; defaults to false. */
|
|
10
13
|
syncParentDir?: boolean;
|
|
11
14
|
};
|
|
12
15
|
export type WriteSiblingTempFileResult<T> = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAoBxC;AAkBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0ChB"}
|
package/dist/sibling-temp.js
CHANGED
|
@@ -2,85 +2,40 @@ import crypto, { randomUUID } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
|
-
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
6
|
-
import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
7
5
|
import { sanitizeUntrustedFileName } from "./filename.js";
|
|
6
|
+
import { applyDirectoryMode } from "./replace-file-descriptor.js";
|
|
8
7
|
import { root } from "./root.js";
|
|
9
8
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
10
9
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
11
|
-
import {
|
|
10
|
+
import { writeCallbackSibling } from "./sibling-staged-file.js";
|
|
11
|
+
import { tempFile } from "./temp-target.js";
|
|
12
12
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
13
|
-
import { serializePathWrite } from "./write-queue.js";
|
|
14
13
|
function buildTempPath(dir, tempPrefix) {
|
|
15
14
|
const safePrefix = assertSafePathPrefix(tempPrefix ?? ".fs-safe-stream", {
|
|
16
15
|
label: "sibling temp prefix",
|
|
17
16
|
});
|
|
18
17
|
return path.join(dir, `${safePrefix}.${process.pid}.${randomUUID()}.tmp`);
|
|
19
18
|
}
|
|
20
|
-
async function syncFileBestEffort(filePath) {
|
|
21
|
-
const handle = await fs.open(filePath, "r+");
|
|
22
|
-
try {
|
|
23
|
-
await handle.sync();
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
if (error.code !== "EPERM") {
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
await handle.close();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function assertFinalPathIsSibling(dir, filePath) {
|
|
35
|
-
const resolvedDir = path.resolve(dir);
|
|
36
|
-
const resolvedFile = path.resolve(filePath);
|
|
37
|
-
if (path.dirname(resolvedFile) !== resolvedDir) {
|
|
38
|
-
throw new Error("Final path must be in the sibling temp directory.");
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
19
|
export async function writeSiblingTempFile(options) {
|
|
42
20
|
const dir = path.resolve(options.dir);
|
|
43
21
|
await fs.mkdir(dir, { recursive: true, mode: options.dirMode ?? 0o700 });
|
|
44
22
|
if (options.chmodDir !== false) {
|
|
45
|
-
await
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
let tempExists = false;
|
|
51
|
-
try {
|
|
52
|
-
tempExists = true;
|
|
53
|
-
const result = await options.writeTemp(tempPath);
|
|
54
|
-
unregisterTempPath.setIdentity(await fs.lstat(tempPath, { bigint: true }));
|
|
55
|
-
if (options.mode !== undefined) {
|
|
56
|
-
await fs.chmod(tempPath, options.mode).catch(() => undefined);
|
|
57
|
-
}
|
|
58
|
-
if (options.syncTempFile) {
|
|
59
|
-
await syncFileBestEffort(tempPath);
|
|
60
|
-
}
|
|
61
|
-
const filePath = path.resolve(options.resolveFinalPath(result));
|
|
62
|
-
assertFinalPathIsSibling(dir, filePath);
|
|
63
|
-
await serializePathWrite(filePath, async () => {
|
|
64
|
-
await withAsyncDirectoryGuards([dirGuard], async () => {
|
|
65
|
-
await fs.rename(tempPath, filePath);
|
|
66
|
-
});
|
|
67
|
-
tempExists = false;
|
|
68
|
-
unregisterTempPath();
|
|
69
|
-
if (options.mode !== undefined) {
|
|
70
|
-
await fs.chmod(filePath, options.mode).catch(() => undefined);
|
|
71
|
-
}
|
|
72
|
-
if (options.syncParentDir) {
|
|
73
|
-
await syncDirectoryBestEffort(dir);
|
|
74
|
-
}
|
|
23
|
+
await applyDirectoryMode({
|
|
24
|
+
fsModule: fs,
|
|
25
|
+
dirPath: dir,
|
|
26
|
+
mode: options.dirMode ?? 0o700,
|
|
27
|
+
ignoreChmodError: true,
|
|
75
28
|
});
|
|
76
|
-
return { filePath, result };
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
if (tempExists) {
|
|
80
|
-
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
81
|
-
}
|
|
82
|
-
unregisterTempPath();
|
|
83
29
|
}
|
|
30
|
+
return await writeCallbackSibling({
|
|
31
|
+
tempPath: buildTempPath(dir, options.tempPrefix),
|
|
32
|
+
write: options.writeTemp,
|
|
33
|
+
resolveFinalPath: options.resolveFinalPath,
|
|
34
|
+
mode: options.mode,
|
|
35
|
+
ignoreModeError: true,
|
|
36
|
+
syncTempFile: options.syncTempFile === true,
|
|
37
|
+
syncParentDir: options.syncParentDir === true,
|
|
38
|
+
});
|
|
84
39
|
}
|
|
85
40
|
function buildSiblingTempPath(params) {
|
|
86
41
|
const id = crypto.randomUUID();
|
|
@@ -107,18 +62,20 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
107
62
|
throw new Error("Target path is outside the allowed root");
|
|
108
63
|
}
|
|
109
64
|
const rootGuard = await createAsyncDirectoryGuard(rootDir);
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
fallbackFileName: params.fallbackFileName ?? "output.bin",
|
|
118
|
-
tempPrefix: params.tempPrefix ?? ".fs-safe-output-",
|
|
65
|
+
const workspace = await tempFile({
|
|
66
|
+
rootDir: resolveSecureTempRoot({
|
|
67
|
+
fallbackPrefix: "fs-safe-output",
|
|
68
|
+
unsafeFallbackLabel: "sibling temp output dir",
|
|
69
|
+
warn: () => undefined,
|
|
70
|
+
}),
|
|
71
|
+
prefix: "fs-safe-output",
|
|
119
72
|
});
|
|
120
|
-
const unregisterTempPath = registerTempPathForExit(tempDir, { recursive: true });
|
|
121
73
|
try {
|
|
74
|
+
const tempPath = buildSiblingTempPath({
|
|
75
|
+
targetPath: path.join(workspace.dir, path.basename(targetPath)),
|
|
76
|
+
fallbackFileName: params.fallbackFileName ?? "output.bin",
|
|
77
|
+
tempPrefix: params.tempPrefix ?? ".fs-safe-output-",
|
|
78
|
+
});
|
|
122
79
|
await getFsSafeTestHooks()?.beforeSiblingTempWrite?.(tempPath);
|
|
123
80
|
await params.writeTemp(tempPath);
|
|
124
81
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
@@ -127,7 +84,6 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
127
84
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
128
85
|
}
|
|
129
86
|
finally {
|
|
130
|
-
await
|
|
131
|
-
unregisterTempPath();
|
|
87
|
+
await workspace.cleanup();
|
|
132
88
|
}
|
|
133
89
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAKA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAaF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA4S5B"}
|