@qodalis/cli-files 2.0.0-beta.13 → 2.0.0-beta.15
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/package.json +2 -2
- package/public-api.d.mts +86 -2
- package/public-api.d.ts +86 -2
- package/public-api.js +308 -17
- package/public-api.js.map +1 -1
- package/public-api.mjs +303 -19
- package/public-api.mjs.map +1 -1
- package/umd/index.global.js +178 -15
package/public-api.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ICliCompletionProvider_TOKEN, ICliFileTransferService_TOKEN, BrowserFileTransferService, DefaultLibraryAuthor, CliForegroundColor, resolveHeaders, ICliDragDropService_TOKEN } from '@qodalis/cli-core';
|
|
1
|
+
import { ICliCompletionProvider_TOKEN, ICliFileTransferService_TOKEN, CliAccessDeniedError, BrowserFileTransferService, DefaultLibraryAuthor, CliForegroundColor, resolveHeaders, cliCanAccess, ICliUserSessionService_TOKEN, ICliUserEventService_TOKEN, ICliDragDropService_TOKEN, isCliAccessDeniedError, ICliAuthorizationService_TOKEN } from '@qodalis/cli-core';
|
|
2
2
|
|
|
3
3
|
// src/lib/interfaces/i-file-system-service.ts
|
|
4
4
|
var IFileSystemService_TOKEN = "cli-file-system-service";
|
|
5
5
|
|
|
6
6
|
// src/lib/version.ts
|
|
7
|
-
var LIBRARY_VERSION = "2.0.0-beta.
|
|
7
|
+
var LIBRARY_VERSION = "2.0.0-beta.15";
|
|
8
8
|
var API_VERSION = 2;
|
|
9
9
|
|
|
10
10
|
// src/lib/processors/cli-ls-command-processor.ts
|
|
@@ -470,6 +470,14 @@ var CliMvCommandProcessor = class {
|
|
|
470
470
|
return raw.split(/\s+/).filter((t) => t && !t.startsWith("--") && !t.startsWith("-"));
|
|
471
471
|
}
|
|
472
472
|
};
|
|
473
|
+
function readableChildren(fs, path) {
|
|
474
|
+
try {
|
|
475
|
+
return fs.listDirectory(path);
|
|
476
|
+
} catch (e) {
|
|
477
|
+
if (isCliAccessDeniedError(e)) return null;
|
|
478
|
+
throw e;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
473
481
|
var CliTreeCommandProcessor = class {
|
|
474
482
|
constructor() {
|
|
475
483
|
this.command = "tree";
|
|
@@ -516,7 +524,8 @@ var CliTreeCommandProcessor = class {
|
|
|
516
524
|
)
|
|
517
525
|
);
|
|
518
526
|
const counts = { dirs: 0, files: 0 };
|
|
519
|
-
this.
|
|
527
|
+
this.fs = fs;
|
|
528
|
+
this.printTree(node, "", true, 0, maxDepth, context, counts, resolvedPath);
|
|
520
529
|
context.writer.writeln();
|
|
521
530
|
context.writer.writeln(
|
|
522
531
|
`${counts.dirs} directories, ${counts.files} files`
|
|
@@ -525,11 +534,15 @@ var CliTreeCommandProcessor = class {
|
|
|
525
534
|
context.writer.writeError(e.message);
|
|
526
535
|
}
|
|
527
536
|
}
|
|
528
|
-
printTree(node, prefix, isRoot, depth, maxDepth, context, counts) {
|
|
529
|
-
if (
|
|
537
|
+
printTree(node, prefix, isRoot, depth, maxDepth, context, counts, path = "/") {
|
|
538
|
+
if (depth >= maxDepth) {
|
|
530
539
|
return;
|
|
531
540
|
}
|
|
532
|
-
const
|
|
541
|
+
const visible = this.fs ? readableChildren(this.fs, path) : node.children;
|
|
542
|
+
if (!visible) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const children = [...visible].sort((a, b) => {
|
|
533
546
|
if (a.type !== b.type) {
|
|
534
547
|
return a.type === "directory" ? -1 : 1;
|
|
535
548
|
}
|
|
@@ -554,7 +567,8 @@ var CliTreeCommandProcessor = class {
|
|
|
554
567
|
depth + 1,
|
|
555
568
|
maxDepth,
|
|
556
569
|
context,
|
|
557
|
-
counts
|
|
570
|
+
counts,
|
|
571
|
+
path === "/" ? `/${child.name}` : `${path}/${child.name}`
|
|
558
572
|
);
|
|
559
573
|
} else {
|
|
560
574
|
counts.files++;
|
|
@@ -844,6 +858,7 @@ var CliFindCommandProcessor = class {
|
|
|
844
858
|
const resolvedBase = fs.resolvePath(searchPath);
|
|
845
859
|
const nameRegex = namePattern ? this.globToRegex(namePattern) : null;
|
|
846
860
|
const results = [];
|
|
861
|
+
this.fs = fs;
|
|
847
862
|
this.walk(
|
|
848
863
|
node,
|
|
849
864
|
resolvedBase,
|
|
@@ -871,8 +886,9 @@ var CliFindCommandProcessor = class {
|
|
|
871
886
|
return new RegExp(`^${regexStr}$`, "i");
|
|
872
887
|
}
|
|
873
888
|
walk(node, currentPath, depth, maxDepth, nameRegex, typeFilter, results) {
|
|
874
|
-
|
|
875
|
-
|
|
889
|
+
const children = this.fs ? readableChildren(this.fs, currentPath) : node.children;
|
|
890
|
+
if (!children) return;
|
|
891
|
+
for (const child of children) {
|
|
876
892
|
const childPath = currentPath === "/" ? `/${child.name}` : `${currentPath}/${child.name}`;
|
|
877
893
|
let matches = true;
|
|
878
894
|
if (nameRegex && !nameRegex.test(child.name)) {
|
|
@@ -1103,8 +1119,9 @@ var CliGrepCommandProcessor = class {
|
|
|
1103
1119
|
};
|
|
1104
1120
|
}
|
|
1105
1121
|
collectFiles(fs, basePath, node, result) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1122
|
+
const children = readableChildren(fs, basePath);
|
|
1123
|
+
if (!children) return;
|
|
1124
|
+
for (const child of children) {
|
|
1108
1125
|
const childPath = basePath === "/" ? `/${child.name}` : `${basePath}/${child.name}`;
|
|
1109
1126
|
if (child.type === "file") {
|
|
1110
1127
|
result.push(childPath);
|
|
@@ -2063,6 +2080,10 @@ var CliStatCommandProcessor = class {
|
|
|
2063
2080
|
}
|
|
2064
2081
|
for (const path of paths) {
|
|
2065
2082
|
const resolved = fs.resolvePath(path);
|
|
2083
|
+
if (fs.canTraverse && !fs.canTraverse(resolved)) {
|
|
2084
|
+
context.writer.writeError(`stat: cannot stat '${path}': Permission denied`);
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2066
2087
|
const node = fs.getNode(resolved);
|
|
2067
2088
|
if (!node) {
|
|
2068
2089
|
context.writer.writeError(
|
|
@@ -2331,6 +2352,10 @@ var CliDuCommandProcessor = class {
|
|
|
2331
2352
|
}
|
|
2332
2353
|
for (const path of paths) {
|
|
2333
2354
|
const resolved = fs.resolvePath(path);
|
|
2355
|
+
if (fs.canTraverse && !fs.canTraverse(resolved)) {
|
|
2356
|
+
context.writer.writeError(`du: cannot access '${path}': Permission denied`);
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2334
2359
|
const node = fs.getNode(resolved);
|
|
2335
2360
|
if (!node) {
|
|
2336
2361
|
context.writer.writeError(
|
|
@@ -2344,6 +2369,7 @@ var CliDuCommandProcessor = class {
|
|
|
2344
2369
|
);
|
|
2345
2370
|
} else {
|
|
2346
2371
|
const entries = [];
|
|
2372
|
+
this.fs = fs;
|
|
2347
2373
|
this.collectSizes(node, resolved, 0, maxDepth, summaryOnly, entries);
|
|
2348
2374
|
for (const entry of entries) {
|
|
2349
2375
|
context.writer.writeln(
|
|
@@ -2366,8 +2392,9 @@ var CliDuCommandProcessor = class {
|
|
|
2366
2392
|
return node.size;
|
|
2367
2393
|
}
|
|
2368
2394
|
let total = 0;
|
|
2369
|
-
|
|
2370
|
-
|
|
2395
|
+
const children = this.fs ? readableChildren(this.fs, path) : node.children;
|
|
2396
|
+
if (children) {
|
|
2397
|
+
for (const child of children) {
|
|
2371
2398
|
const childPath = path === "/" ? `/${child.name}` : `${path}/${child.name}`;
|
|
2372
2399
|
total += this.collectSizes(
|
|
2373
2400
|
child,
|
|
@@ -3830,14 +3857,14 @@ function substituteVariables(input, variables) {
|
|
|
3830
3857
|
);
|
|
3831
3858
|
return result;
|
|
3832
3859
|
}
|
|
3833
|
-
|
|
3834
|
-
// src/lib/services/indexed-db-file-system.service.ts
|
|
3835
3860
|
var DB_NAME = "qodalis-cli-filesystem";
|
|
3836
3861
|
var STORE_NAME = "filesystem";
|
|
3837
3862
|
var ROOT_KEY = "root";
|
|
3838
3863
|
var CWD_KEY = "cwd";
|
|
3839
3864
|
var HOME_KEY = "home";
|
|
3840
3865
|
var DEFAULT_HOME = "/";
|
|
3866
|
+
var SYSTEM_OWNERSHIP = { uid: "root", gid: "admin" };
|
|
3867
|
+
var ANONYMOUS_OWNERSHIP = { uid: "nobody", gid: "nogroup" };
|
|
3841
3868
|
function createSeedFileSystem() {
|
|
3842
3869
|
const now = Date.now();
|
|
3843
3870
|
return {
|
|
@@ -3885,6 +3912,88 @@ var IndexedDbFileSystemService = class {
|
|
|
3885
3912
|
this.homePath = DEFAULT_HOME;
|
|
3886
3913
|
this.currentUid = null;
|
|
3887
3914
|
this.currentGroups = [];
|
|
3915
|
+
this.systemDepth = 0;
|
|
3916
|
+
}
|
|
3917
|
+
// --- Access control ---
|
|
3918
|
+
setAccessPolicy(policy) {
|
|
3919
|
+
this.accessPolicy = policy;
|
|
3920
|
+
}
|
|
3921
|
+
runAsSystem(fn) {
|
|
3922
|
+
this.systemDepth++;
|
|
3923
|
+
try {
|
|
3924
|
+
return fn();
|
|
3925
|
+
} finally {
|
|
3926
|
+
this.systemDepth--;
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
reassignOwner(fromUid, toUid) {
|
|
3930
|
+
const from = fromUid.toLowerCase();
|
|
3931
|
+
if (!from || !toUid) return 0;
|
|
3932
|
+
let changed = 0;
|
|
3933
|
+
const visit = (node) => {
|
|
3934
|
+
if (node.ownership && node.ownership.uid.toLowerCase() === from) {
|
|
3935
|
+
node.ownership = { ...node.ownership, uid: toUid };
|
|
3936
|
+
changed++;
|
|
3937
|
+
}
|
|
3938
|
+
node.children?.forEach(visit);
|
|
3939
|
+
};
|
|
3940
|
+
visit(this.root);
|
|
3941
|
+
return changed;
|
|
3942
|
+
}
|
|
3943
|
+
canTraverse(path) {
|
|
3944
|
+
try {
|
|
3945
|
+
this.assertTraverse(this.resolvePath(path), "stat", path);
|
|
3946
|
+
return true;
|
|
3947
|
+
} catch {
|
|
3948
|
+
return false;
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
get enforcing() {
|
|
3952
|
+
return !!this.accessPolicy && this.systemDepth === 0;
|
|
3953
|
+
}
|
|
3954
|
+
deny(op, path, reason = "Permission denied") {
|
|
3955
|
+
throw new CliAccessDeniedError(`${op}: ${path}: ${reason}`);
|
|
3956
|
+
}
|
|
3957
|
+
/** `x` on every existing directory on the way to `resolved` (excluding the target). */
|
|
3958
|
+
assertTraverse(resolved, op, display) {
|
|
3959
|
+
if (!this.enforcing) return;
|
|
3960
|
+
const parts = resolved.split("/").filter(Boolean);
|
|
3961
|
+
let current = this.root;
|
|
3962
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3963
|
+
if (!current || current.type !== "directory") return;
|
|
3964
|
+
if (!this.accessPolicy.canAccess(current, "execute")) this.deny(op, display);
|
|
3965
|
+
current = current.children?.find((c) => c.name === parts[i]);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
assertAccess(node, action, op, display) {
|
|
3969
|
+
if (this.enforcing && !this.accessPolicy.canAccess(node, action)) this.deny(op, display);
|
|
3970
|
+
}
|
|
3971
|
+
/** Recursive delete: every directory inside must allow removing its entries (`w` + `x`, readable). */
|
|
3972
|
+
assertRemovableTree(dir, dirPath, display) {
|
|
3973
|
+
if (!this.enforcing) return;
|
|
3974
|
+
this.assertAccess(dir, "read", "rm", display);
|
|
3975
|
+
this.assertAccess(dir, "execute", "rm", display);
|
|
3976
|
+
if ((dir.children?.length ?? 0) > 0) this.assertAccess(dir, "write", "rm", display);
|
|
3977
|
+
for (const child of dir.children ?? []) {
|
|
3978
|
+
if (child.type === "directory") this.assertRemovableTree(child, `${dirPath}/${child.name}`, display);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
/** Copying reads everything it copies: `r` on every file, `r` + `x` on every directory. */
|
|
3982
|
+
assertReadableTree(node, display) {
|
|
3983
|
+
if (!this.enforcing) return;
|
|
3984
|
+
this.assertAccess(node, "read", "cp", display);
|
|
3985
|
+
if (node.type !== "directory") return;
|
|
3986
|
+
this.assertAccess(node, "execute", "cp", display);
|
|
3987
|
+
for (const child of node.children ?? []) {
|
|
3988
|
+
this.assertReadableTree(child, `${display.replace(/\/+$/, "")}/${child.name}`);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
/** Creating/removing entries needs `w` and `x` on the containing directory. */
|
|
3992
|
+
assertModifyDir(dir, dirPath, op, display) {
|
|
3993
|
+
if (!this.enforcing) return;
|
|
3994
|
+
this.assertTraverse(dirPath, op, display);
|
|
3995
|
+
this.assertAccess(dir, "execute", op, display);
|
|
3996
|
+
this.assertAccess(dir, "write", op, display);
|
|
3888
3997
|
}
|
|
3889
3998
|
// --- User context ---
|
|
3890
3999
|
setCurrentUser(uid, groups) {
|
|
@@ -3892,7 +4001,9 @@ var IndexedDbFileSystemService = class {
|
|
|
3892
4001
|
this.currentGroups = groups;
|
|
3893
4002
|
}
|
|
3894
4003
|
getDefaultOwnership() {
|
|
3895
|
-
if (!this.currentUid)
|
|
4004
|
+
if (!this.currentUid) {
|
|
4005
|
+
return this.accessPolicy ? { ...ANONYMOUS_OWNERSHIP } : void 0;
|
|
4006
|
+
}
|
|
3896
4007
|
return {
|
|
3897
4008
|
uid: this.currentUid,
|
|
3898
4009
|
gid: this.currentGroups[0] || "users"
|
|
@@ -3911,6 +4022,8 @@ var IndexedDbFileSystemService = class {
|
|
|
3911
4022
|
if (node.type !== "directory") {
|
|
3912
4023
|
throw new Error(`cd: ${path}: Not a directory`);
|
|
3913
4024
|
}
|
|
4025
|
+
this.assertTraverse(resolved, "cd", path);
|
|
4026
|
+
this.assertAccess(node, "execute", "cd", path);
|
|
3914
4027
|
this.cwd = resolved;
|
|
3915
4028
|
}
|
|
3916
4029
|
getHomePath() {
|
|
@@ -3967,6 +4080,8 @@ var IndexedDbFileSystemService = class {
|
|
|
3967
4080
|
if (node.type !== "directory") {
|
|
3968
4081
|
throw new Error(`ls: ${path}: Not a directory`);
|
|
3969
4082
|
}
|
|
4083
|
+
this.assertTraverse(this.resolvePath(path), "ls", path);
|
|
4084
|
+
this.assertAccess(node, "read", "ls", path);
|
|
3970
4085
|
return node.children || [];
|
|
3971
4086
|
}
|
|
3972
4087
|
readFile(path) {
|
|
@@ -3977,6 +4092,8 @@ var IndexedDbFileSystemService = class {
|
|
|
3977
4092
|
if (node.type === "directory") {
|
|
3978
4093
|
throw new Error(`cat: ${path}: Is a directory`);
|
|
3979
4094
|
}
|
|
4095
|
+
this.assertTraverse(this.resolvePath(path), "cat", path);
|
|
4096
|
+
this.assertAccess(node, "read", "cat", path);
|
|
3980
4097
|
return node.content ?? "";
|
|
3981
4098
|
}
|
|
3982
4099
|
exists(path) {
|
|
@@ -4001,6 +4118,10 @@ var IndexedDbFileSystemService = class {
|
|
|
4001
4118
|
}
|
|
4002
4119
|
let child = current.children.find((c) => c.name === part);
|
|
4003
4120
|
if (!child) {
|
|
4121
|
+
if (this.enforcing) {
|
|
4122
|
+
this.assertAccess(current, "execute", "mkdir", path);
|
|
4123
|
+
this.assertAccess(current, "write", "mkdir", path);
|
|
4124
|
+
}
|
|
4004
4125
|
const now = Date.now();
|
|
4005
4126
|
child = {
|
|
4006
4127
|
name: part,
|
|
@@ -4016,6 +4137,8 @@ var IndexedDbFileSystemService = class {
|
|
|
4016
4137
|
current.modifiedAt = now;
|
|
4017
4138
|
} else if (child.type !== "directory") {
|
|
4018
4139
|
throw new Error(`mkdir: ${part}: Not a directory`);
|
|
4140
|
+
} else {
|
|
4141
|
+
this.assertAccess(child, "execute", "mkdir", path);
|
|
4019
4142
|
}
|
|
4020
4143
|
current = child;
|
|
4021
4144
|
}
|
|
@@ -4034,6 +4157,7 @@ var IndexedDbFileSystemService = class {
|
|
|
4034
4157
|
if (parent.children?.find((c) => c.name === dirName)) {
|
|
4035
4158
|
throw new Error(`mkdir: ${path}: File exists`);
|
|
4036
4159
|
}
|
|
4160
|
+
this.assertModifyDir(parent, parentPath, "mkdir", path);
|
|
4037
4161
|
const now = Date.now();
|
|
4038
4162
|
if (!parent.children) {
|
|
4039
4163
|
parent.children = [];
|
|
@@ -4068,6 +4192,12 @@ var IndexedDbFileSystemService = class {
|
|
|
4068
4192
|
}
|
|
4069
4193
|
const existing = parent.children?.find((c) => c.name === fileName);
|
|
4070
4194
|
const now = Date.now();
|
|
4195
|
+
if (existing) {
|
|
4196
|
+
this.assertTraverse(resolved, "touch", path);
|
|
4197
|
+
this.assertAccess(existing, "write", "touch", path);
|
|
4198
|
+
} else {
|
|
4199
|
+
this.assertModifyDir(parent, parentPath, "touch", path);
|
|
4200
|
+
}
|
|
4071
4201
|
if (existing) {
|
|
4072
4202
|
existing.modifiedAt = now;
|
|
4073
4203
|
} else {
|
|
@@ -4094,6 +4224,8 @@ var IndexedDbFileSystemService = class {
|
|
|
4094
4224
|
if (node.type === "directory") {
|
|
4095
4225
|
throw new Error(`write: ${path}: Is a directory`);
|
|
4096
4226
|
}
|
|
4227
|
+
this.assertTraverse(resolved, "write", path);
|
|
4228
|
+
this.assertAccess(node, "write", "write", path);
|
|
4097
4229
|
const now = Date.now();
|
|
4098
4230
|
node.content = append ? (node.content ?? "") + content : content;
|
|
4099
4231
|
node.size = new Blob([node.content]).size;
|
|
@@ -4122,6 +4254,8 @@ var IndexedDbFileSystemService = class {
|
|
|
4122
4254
|
if (target.type === "directory" && !recursive) {
|
|
4123
4255
|
throw new Error(`rm: ${path}: Is a directory`);
|
|
4124
4256
|
}
|
|
4257
|
+
this.assertModifyDir(parent, parentPath, "rm", path);
|
|
4258
|
+
if (target.type === "directory") this.assertRemovableTree(target, resolved, path);
|
|
4125
4259
|
parent.children.splice(index, 1);
|
|
4126
4260
|
parent.modifiedAt = Date.now();
|
|
4127
4261
|
}
|
|
@@ -4136,10 +4270,13 @@ var IndexedDbFileSystemService = class {
|
|
|
4136
4270
|
`cp: -r not specified; omitting directory '${src}'`
|
|
4137
4271
|
);
|
|
4138
4272
|
}
|
|
4273
|
+
this.assertTraverse(srcResolved, "cp", src);
|
|
4274
|
+
this.assertReadableTree(srcNode, src);
|
|
4139
4275
|
const destResolved = this.resolvePath(dest);
|
|
4140
4276
|
const destNode = this.getNode(destResolved);
|
|
4141
4277
|
if (destNode && destNode.type === "directory") {
|
|
4142
|
-
|
|
4278
|
+
this.assertModifyDir(destNode, destResolved, "cp", dest);
|
|
4279
|
+
const clone = this.ownedByCurrentUser(this.cloneNode(srcNode));
|
|
4143
4280
|
if (!destNode.children) {
|
|
4144
4281
|
destNode.children = [];
|
|
4145
4282
|
}
|
|
@@ -4159,7 +4296,8 @@ var IndexedDbFileSystemService = class {
|
|
|
4159
4296
|
if (!parent || parent.type !== "directory") {
|
|
4160
4297
|
throw new Error(`cp: ${parentPath}: No such file or directory`);
|
|
4161
4298
|
}
|
|
4162
|
-
|
|
4299
|
+
this.assertModifyDir(parent, parentPath, "cp", dest);
|
|
4300
|
+
const clone = this.ownedByCurrentUser(this.cloneNode(srcNode));
|
|
4163
4301
|
clone.name = newName;
|
|
4164
4302
|
if (!parent.children) {
|
|
4165
4303
|
parent.children = [];
|
|
@@ -4191,10 +4329,12 @@ var IndexedDbFileSystemService = class {
|
|
|
4191
4329
|
if (!srcParent || !srcParent.children) {
|
|
4192
4330
|
throw new Error(`mv: ${src}: No such file or directory`);
|
|
4193
4331
|
}
|
|
4332
|
+
this.assertModifyDir(srcParent, srcParentPath, "mv", src);
|
|
4194
4333
|
const srcIndex = srcParent.children.findIndex(
|
|
4195
4334
|
(c) => c.name === srcNode.name
|
|
4196
4335
|
);
|
|
4197
4336
|
if (destNode && destNode.type === "directory") {
|
|
4337
|
+
this.assertModifyDir(destNode, destResolved, "mv", dest);
|
|
4198
4338
|
if (!destNode.children) {
|
|
4199
4339
|
destNode.children = [];
|
|
4200
4340
|
}
|
|
@@ -4217,6 +4357,7 @@ var IndexedDbFileSystemService = class {
|
|
|
4217
4357
|
`mv: ${destParentPath}: No such file or directory`
|
|
4218
4358
|
);
|
|
4219
4359
|
}
|
|
4360
|
+
this.assertModifyDir(destParent, destParentPath, "mv", dest);
|
|
4220
4361
|
srcParent.children.splice(srcIndex, 1);
|
|
4221
4362
|
srcNode.name = newName;
|
|
4222
4363
|
if (!destParent.children) {
|
|
@@ -4240,6 +4381,9 @@ var IndexedDbFileSystemService = class {
|
|
|
4240
4381
|
if (!node) {
|
|
4241
4382
|
throw new Error(`chmod: ${path}: No such file or directory`);
|
|
4242
4383
|
}
|
|
4384
|
+
if (this.enforcing && !this.accessPolicy.canChangeOwnership(node)) {
|
|
4385
|
+
this.deny("chmod", path, "Operation not permitted");
|
|
4386
|
+
}
|
|
4243
4387
|
node.permissions = permissions;
|
|
4244
4388
|
}
|
|
4245
4389
|
chown(path, ownership) {
|
|
@@ -4248,6 +4392,9 @@ var IndexedDbFileSystemService = class {
|
|
|
4248
4392
|
if (!node) {
|
|
4249
4393
|
throw new Error(`chown: ${path}: No such file or directory`);
|
|
4250
4394
|
}
|
|
4395
|
+
if (this.enforcing && !this.accessPolicy.canChangeOwnership(node, ownership)) {
|
|
4396
|
+
this.deny("chown", path, "Operation not permitted");
|
|
4397
|
+
}
|
|
4251
4398
|
node.ownership = ownership;
|
|
4252
4399
|
}
|
|
4253
4400
|
// --- Persistence ---
|
|
@@ -4289,6 +4436,18 @@ var IndexedDbFileSystemService = class {
|
|
|
4289
4436
|
}
|
|
4290
4437
|
}
|
|
4291
4438
|
// --- Private helpers ---
|
|
4439
|
+
/** A copy belongs to whoever made it (like `cp` without `-p`) */
|
|
4440
|
+
ownedByCurrentUser(node) {
|
|
4441
|
+
const owner = this.getDefaultOwnership();
|
|
4442
|
+
if (owner) {
|
|
4443
|
+
const apply = (n) => {
|
|
4444
|
+
n.ownership = { ...owner };
|
|
4445
|
+
n.children?.forEach(apply);
|
|
4446
|
+
};
|
|
4447
|
+
apply(node);
|
|
4448
|
+
}
|
|
4449
|
+
return node;
|
|
4450
|
+
}
|
|
4292
4451
|
cloneNode(node) {
|
|
4293
4452
|
const now = Date.now();
|
|
4294
4453
|
const clone = {
|
|
@@ -4963,6 +5122,115 @@ var VirtualFsFileTransferService = class {
|
|
|
4963
5122
|
return this._browserService.uploadFromBrowser(accept);
|
|
4964
5123
|
}
|
|
4965
5124
|
};
|
|
5125
|
+
var CliFilesPermissions = {
|
|
5126
|
+
/** Bypass all file permission checks */
|
|
5127
|
+
Admin: "files:admin",
|
|
5128
|
+
/** chmod/chown files you do not own */
|
|
5129
|
+
Chown: "files:chown"
|
|
5130
|
+
};
|
|
5131
|
+
var HOME_DIR_PERMISSIONS = "rwxr-x---";
|
|
5132
|
+
function authorization(context) {
|
|
5133
|
+
try {
|
|
5134
|
+
return context.services.get(ICliAuthorizationService_TOKEN);
|
|
5135
|
+
} catch {
|
|
5136
|
+
return void 0;
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5139
|
+
function createFileAccessPolicy(context) {
|
|
5140
|
+
return {
|
|
5141
|
+
// Nodes without an owner (system seed, legacy data) belong to root:admin
|
|
5142
|
+
canAccess: (node, action) => cliCanAccess(
|
|
5143
|
+
context,
|
|
5144
|
+
node.ownership ? node : { ...node, ownership: SYSTEM_OWNERSHIP },
|
|
5145
|
+
action,
|
|
5146
|
+
[CliFilesPermissions.Admin]
|
|
5147
|
+
),
|
|
5148
|
+
canChangeOwnership: (node, newOwnership) => {
|
|
5149
|
+
const auth = authorization(context);
|
|
5150
|
+
if (!auth) return true;
|
|
5151
|
+
if (auth.can(CliFilesPermissions.Admin) || auth.can(CliFilesPermissions.Chown)) return true;
|
|
5152
|
+
const principal = auth.getPrincipal();
|
|
5153
|
+
if (!principal) return false;
|
|
5154
|
+
const owns = !!node.ownership && (node.ownership.uid === principal.name || node.ownership.uid === principal.id);
|
|
5155
|
+
const keepsOwner = !newOwnership || newOwnership.uid === node.ownership?.uid || newOwnership.uid === principal.name;
|
|
5156
|
+
return owns && keepsOwner;
|
|
5157
|
+
}
|
|
5158
|
+
};
|
|
5159
|
+
}
|
|
5160
|
+
function followUserSession(context, fs) {
|
|
5161
|
+
let sessions;
|
|
5162
|
+
try {
|
|
5163
|
+
sessions = context.services.get(ICliUserSessionService_TOKEN);
|
|
5164
|
+
} catch {
|
|
5165
|
+
sessions = void 0;
|
|
5166
|
+
}
|
|
5167
|
+
if (!sessions) return void 0;
|
|
5168
|
+
let previousUser = null;
|
|
5169
|
+
return sessions.getUserSession().subscribe((session) => {
|
|
5170
|
+
if (!session?.user) {
|
|
5171
|
+
fs.setCurrentUser("", []);
|
|
5172
|
+
if (previousUser !== null || !canEnter(fs, fs.getCurrentDirectory())) {
|
|
5173
|
+
try {
|
|
5174
|
+
fs.setCurrentDirectory("/");
|
|
5175
|
+
void fs.persist();
|
|
5176
|
+
} catch {
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
previousUser = null;
|
|
5180
|
+
return;
|
|
5181
|
+
}
|
|
5182
|
+
const user = session.user;
|
|
5183
|
+
fs.setCurrentUser(user.name, user.groups ?? []);
|
|
5184
|
+
if (user.homeDir) {
|
|
5185
|
+
const home = user.homeDir;
|
|
5186
|
+
const system = (fn) => fs.runAsSystem ? fs.runAsSystem(fn) : fn();
|
|
5187
|
+
if (!fs.exists(home)) {
|
|
5188
|
+
system(() => {
|
|
5189
|
+
fs.createDirectory(home, true);
|
|
5190
|
+
if (home !== "/") {
|
|
5191
|
+
fs.chown(home, { uid: user.name, gid: user.groups?.[0] ?? "users" });
|
|
5192
|
+
fs.chmod(home, HOME_DIR_PERMISSIONS);
|
|
5193
|
+
}
|
|
5194
|
+
});
|
|
5195
|
+
void fs.persist();
|
|
5196
|
+
}
|
|
5197
|
+
fs.setHomePath(home);
|
|
5198
|
+
if (previousUser !== user.id) {
|
|
5199
|
+
try {
|
|
5200
|
+
fs.setCurrentDirectory(home);
|
|
5201
|
+
} catch {
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
previousUser = user.id;
|
|
5206
|
+
});
|
|
5207
|
+
}
|
|
5208
|
+
function canEnter(fs, path) {
|
|
5209
|
+
try {
|
|
5210
|
+
fs.setCurrentDirectory(path);
|
|
5211
|
+
return true;
|
|
5212
|
+
} catch {
|
|
5213
|
+
return false;
|
|
5214
|
+
}
|
|
5215
|
+
}
|
|
5216
|
+
function followUserAccounts(context, fs) {
|
|
5217
|
+
let events;
|
|
5218
|
+
try {
|
|
5219
|
+
events = context.services.get(ICliUserEventService_TOKEN);
|
|
5220
|
+
} catch {
|
|
5221
|
+
events = void 0;
|
|
5222
|
+
}
|
|
5223
|
+
if (!events || !fs.reassignOwner) return void 0;
|
|
5224
|
+
return events.events$.subscribe((event) => {
|
|
5225
|
+
let changed = 0;
|
|
5226
|
+
if (event.type === "user_updated" && event.previousName && event.previousName !== event.user?.name) {
|
|
5227
|
+
changed = fs.reassignOwner(event.previousName, event.user.name);
|
|
5228
|
+
} else if (event.type === "user_deleted" && event.user?.name) {
|
|
5229
|
+
changed = fs.reassignOwner(event.user.name, SYSTEM_OWNERSHIP.uid);
|
|
5230
|
+
}
|
|
5231
|
+
if (changed > 0) void fs.persist().catch(() => void 0);
|
|
5232
|
+
});
|
|
5233
|
+
}
|
|
4966
5234
|
|
|
4967
5235
|
// src/lib/completion/file-path-completion-provider.ts
|
|
4968
5236
|
var FILE_COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -5157,6 +5425,10 @@ var filesModule = {
|
|
|
5157
5425
|
new CliShCommandProcessor(),
|
|
5158
5426
|
new CliUploadCommandProcessor()
|
|
5159
5427
|
],
|
|
5428
|
+
permissions: [
|
|
5429
|
+
{ name: CliFilesPermissions.Admin, description: "Bypass file permission checks" },
|
|
5430
|
+
{ name: CliFilesPermissions.Chown, description: "chmod/chown files owned by others" }
|
|
5431
|
+
],
|
|
5160
5432
|
services: [
|
|
5161
5433
|
{
|
|
5162
5434
|
provide: IFileSystemService_TOKEN,
|
|
@@ -5228,13 +5500,25 @@ var filesModule = {
|
|
|
5228
5500
|
});
|
|
5229
5501
|
}
|
|
5230
5502
|
},
|
|
5503
|
+
async onAfterBoot(context) {
|
|
5504
|
+
const fs = context.services.getRequired(IFileSystemService_TOKEN);
|
|
5505
|
+
fs.setAccessPolicy?.(createFileAccessPolicy(context));
|
|
5506
|
+
this._sessionSubscription?.unsubscribe();
|
|
5507
|
+
this._sessionSubscription = followUserSession(context, fs);
|
|
5508
|
+
this._accountsSubscription?.unsubscribe();
|
|
5509
|
+
this._accountsSubscription = followUserAccounts(context, fs);
|
|
5510
|
+
},
|
|
5231
5511
|
async onDestroy(_context) {
|
|
5232
5512
|
const sub = this._dragDropSubscription;
|
|
5233
5513
|
sub?.unsubscribe();
|
|
5234
5514
|
this._dragDropSubscription = void 0;
|
|
5515
|
+
this._sessionSubscription?.unsubscribe();
|
|
5516
|
+
this._sessionSubscription = void 0;
|
|
5517
|
+
this._accountsSubscription?.unsubscribe();
|
|
5518
|
+
this._accountsSubscription = void 0;
|
|
5235
5519
|
}
|
|
5236
5520
|
};
|
|
5237
5521
|
|
|
5238
|
-
export { CliAwkCommandProcessor, CliBasenameCommandProcessor, CliCatCommandProcessor, CliCdCommandProcessor, CliChmodCommandProcessor, CliChownCommandProcessor, CliCpCommandProcessor, CliCutCommandProcessor, CliDiffCommandProcessor, CliDirnameCommandProcessor, CliDuCommandProcessor, CliEchoCommandProcessor, CliFindCommandProcessor, CliGrepCommandProcessor, CliHeadCommandProcessor, CliLnCommandProcessor, CliLsCommandProcessor, CliMkdirCommandProcessor, CliMvCommandProcessor, CliPasteCommandProcessor, CliPwdCommandProcessor, CliRmCommandProcessor, CliRmdirCommandProcessor, CliSedCommandProcessor, CliShCommandProcessor, CliSortCommandProcessor, CliStatCommandProcessor, CliTacCommandProcessor, CliTailCommandProcessor, CliTeeCommandProcessor, CliTouchCommandProcessor, CliTrCommandProcessor, CliTreeCommandProcessor, CliUniqCommandProcessor, CliWcCommandProcessor, CliXargsCommandProcessor, FilePathCompletionProvider, IFileSystemService_TOKEN, IndexedDbFileSystemService, ServerFileSystemService, VirtualFsFileTransferService, executeScript, filesModule };
|
|
5522
|
+
export { ANONYMOUS_OWNERSHIP, CliAwkCommandProcessor, CliBasenameCommandProcessor, CliCatCommandProcessor, CliCdCommandProcessor, CliChmodCommandProcessor, CliChownCommandProcessor, CliCpCommandProcessor, CliCutCommandProcessor, CliDiffCommandProcessor, CliDirnameCommandProcessor, CliDuCommandProcessor, CliEchoCommandProcessor, CliFilesPermissions, CliFindCommandProcessor, CliGrepCommandProcessor, CliHeadCommandProcessor, CliLnCommandProcessor, CliLsCommandProcessor, CliMkdirCommandProcessor, CliMvCommandProcessor, CliPasteCommandProcessor, CliPwdCommandProcessor, CliRmCommandProcessor, CliRmdirCommandProcessor, CliSedCommandProcessor, CliShCommandProcessor, CliSortCommandProcessor, CliStatCommandProcessor, CliTacCommandProcessor, CliTailCommandProcessor, CliTeeCommandProcessor, CliTouchCommandProcessor, CliTrCommandProcessor, CliTreeCommandProcessor, CliUniqCommandProcessor, CliWcCommandProcessor, CliXargsCommandProcessor, FilePathCompletionProvider, HOME_DIR_PERMISSIONS, IFileSystemService_TOKEN, IndexedDbFileSystemService, SYSTEM_OWNERSHIP, ServerFileSystemService, VirtualFsFileTransferService, createFileAccessPolicy, executeScript, filesModule, followUserAccounts, followUserSession };
|
|
5239
5523
|
//# sourceMappingURL=public-api.mjs.map
|
|
5240
5524
|
//# sourceMappingURL=public-api.mjs.map
|