@poe-platform/safe-fs 0.1.630 → 0.1.631
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/dist/safe-fs/bridge/stats.js +1 -1
- package/dist/safe-fs/contracts/descriptor.d.ts +31 -0
- package/dist/safe-fs/contracts/descriptor.js +1 -0
- package/dist/safe-fs/contracts/errors.d.ts +1 -1
- package/dist/safe-fs/contracts/errors.js +1 -1
- package/dist/safe-fs/contracts/filesystem.d.ts +14 -1
- package/dist/safe-fs/core.d.ts +3 -0
- package/dist/safe-fs/core.js +2 -0
- package/dist/safe-fs/fs/capabilities.d.ts +1 -1
- package/dist/safe-fs/fs/capabilities.js +5 -3
- package/dist/safe-fs/fs/descriptor.d.ts +21 -0
- package/dist/safe-fs/fs/descriptor.js +244 -0
- package/dist/safe-fs/fs/devices/index.d.ts +5 -1
- package/dist/safe-fs/fs/devices/index.js +53 -4
- package/dist/safe-fs/fs/memory/index.d.ts +8 -1
- package/dist/safe-fs/fs/memory/index.js +125 -8
- package/dist/safe-fs/fs/memory/limits.d.ts +1 -0
- package/dist/safe-fs/fs/memory/limits.js +7 -1
- package/dist/safe-fs/fs/mount/index.d.ts +5 -1
- package/dist/safe-fs/fs/mount/index.js +69 -6
- package/dist/safe-fs/fs/overlay/index.d.ts +2 -0
- package/dist/safe-fs/fs/overlay/index.js +9 -1
- package/dist/safe-fs/fs/quota/index.js +5 -1
- package/dist/safe-fs/fs/readonly/index.d.ts +2 -0
- package/dist/safe-fs/fs/readonly/index.js +35 -2
- package/dist/safe-fs/fs/real/index.d.ts +3 -0
- package/dist/safe-fs/fs/real/index.js +73 -3
- package/dist/safe-fs/fs/s3/filesystem.d.ts +3 -0
- package/dist/safe-fs/fs/s3/filesystem.js +5 -0
- package/dist/safe-fs/fs/scoped.js +68 -17
- package/dist/safe-fs/fs/webdav/webdav.d.ts +3 -0
- package/dist/safe-fs/fs/webdav/webdav.js +5 -0
- package/dist/safe-fs/index.d.ts +3 -0
- package/dist/safe-fs/index.js +2 -0
- package/dist/safe-fs/python/emscripten.d.ts +14 -0
- package/dist/safe-fs/python/emscripten.js +354 -0
- package/dist/safe-fs/python/filesystem.d.ts +51 -0
- package/dist/safe-fs/python/filesystem.js +216 -0
- package/dist/safe-fs/python/flags.d.ts +3 -0
- package/dist/safe-fs/python/flags.js +14 -0
- package/dist/safe-fs/python/index.d.ts +7 -0
- package/dist/safe-fs/python/index.js +4 -0
- package/dist/safe-fs/python/request.d.ts +2 -0
- package/dist/safe-fs/python/request.js +61 -0
- package/dist/safe-fs/python/stat.d.ts +8 -0
- package/dist/safe-fs/python/stat.js +31 -0
- package/dist/safe-fs/xml.d.ts +3 -0
- package/dist/safe-fs/xml.js +41 -19
- package/package.json +5 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FsError } from "../contracts/errors.js";
|
|
2
|
+
const argumentKinds = {
|
|
3
|
+
open: ["string", "object"], read: ["number", "number", "position"], write: ["number", "bytes", "position"],
|
|
4
|
+
descriptorCapabilities: ["number"], fstat: ["number"], close: ["number"], position: ["number"], ftruncate: ["number", "number"], sync: ["number", "boolean"],
|
|
5
|
+
stat: ["string"], lstat: ["string"], readdir: ["string"], realpath: ["string"], readlink: ["string"], rm: ["string"], rmdir: ["string"],
|
|
6
|
+
rename: ["string", "string"], symlink: ["string", "string"], link: ["string", "string"], mkdir: ["string", "optionalObject"],
|
|
7
|
+
chmod: ["string", "number"], truncate: ["string", "number"], access: ["string", "number"], utimes: ["string", "number", "number"],
|
|
8
|
+
};
|
|
9
|
+
export function parsePythonFsRequest(value, maxTransferBytes) {
|
|
10
|
+
if (!value || typeof value !== "object" || Reflect.ownKeys(value).some(key => key !== "op" && key !== "args"))
|
|
11
|
+
throw new FsError("EINVAL");
|
|
12
|
+
const op = Object.getOwnPropertyDescriptor(value, "op")?.value;
|
|
13
|
+
const args = Object.getOwnPropertyDescriptor(value, "args")?.value;
|
|
14
|
+
if (typeof op !== "string" || !Object.hasOwn(argumentKinds, op))
|
|
15
|
+
throw new FsError("ENOTSUP");
|
|
16
|
+
const kinds = argumentKinds[op];
|
|
17
|
+
if (!Array.isArray(args) || args.length > kinds.length || args.length < kinds.length - (kinds.at(-1) === "optionalObject" ? 1 : 0))
|
|
18
|
+
throw new FsError("EINVAL", { syscall: op });
|
|
19
|
+
const selected = [];
|
|
20
|
+
for (let index = 0; index < kinds.length; index++) {
|
|
21
|
+
const kind = kinds[index];
|
|
22
|
+
const descriptor = Object.getOwnPropertyDescriptor(args, String(index));
|
|
23
|
+
if (descriptor && !Object.hasOwn(descriptor, "value"))
|
|
24
|
+
throw new FsError("EINVAL", { syscall: op });
|
|
25
|
+
const argument = descriptor?.value;
|
|
26
|
+
if (kind === "bytes") {
|
|
27
|
+
if (!(argument instanceof Uint8Array) && !Array.isArray(argument))
|
|
28
|
+
throw new FsError("EINVAL", { syscall: op });
|
|
29
|
+
if (argument.length > maxTransferBytes)
|
|
30
|
+
throw new FsError("EFBIG", { syscall: op });
|
|
31
|
+
const owned = new Uint8Array(argument.length);
|
|
32
|
+
for (let byteIndex = 0; byteIndex < argument.length; byteIndex++) {
|
|
33
|
+
const entry = Object.getOwnPropertyDescriptor(argument, String(byteIndex));
|
|
34
|
+
const byte = entry?.value;
|
|
35
|
+
if (!entry || !Object.hasOwn(entry, "value") || typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255)
|
|
36
|
+
throw new FsError("EINVAL", { syscall: op });
|
|
37
|
+
owned[byteIndex] = byte;
|
|
38
|
+
}
|
|
39
|
+
selected.push(owned);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (kind === "position" ? argument !== null && typeof argument !== "number"
|
|
43
|
+
: kind === "optionalObject" ? argument !== undefined && (typeof argument !== "object" || argument === null || Array.isArray(argument))
|
|
44
|
+
: kind === "object" ? typeof argument !== "object" || argument === null || Array.isArray(argument)
|
|
45
|
+
: typeof argument !== kind)
|
|
46
|
+
throw new FsError("EINVAL", { syscall: op });
|
|
47
|
+
if (argument !== null && typeof argument === "object") {
|
|
48
|
+
const snapshot = {};
|
|
49
|
+
for (const key of Reflect.ownKeys(argument)) {
|
|
50
|
+
const field = Object.getOwnPropertyDescriptor(argument, key);
|
|
51
|
+
if (typeof key !== "string" || !field || !Object.hasOwn(field, "value"))
|
|
52
|
+
throw new FsError("EINVAL", { syscall: op });
|
|
53
|
+
Object.defineProperty(snapshot, key, { value: field.value, enumerable: true });
|
|
54
|
+
}
|
|
55
|
+
selected.push(snapshot);
|
|
56
|
+
}
|
|
57
|
+
else
|
|
58
|
+
selected.push(argument);
|
|
59
|
+
}
|
|
60
|
+
return { op, args: selected };
|
|
61
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FileStat } from "../contracts/filesystem.js";
|
|
2
|
+
export type PythonWireStat = Omit<FileStat, "identityScope">;
|
|
3
|
+
/** Invocation-local device numbers qualify canonical scope/dev without serializing opaque authority. */
|
|
4
|
+
export declare class PythonStatTranslator {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(maxDevices?: number);
|
|
7
|
+
translate(stat: FileStat): PythonWireStat;
|
|
8
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FsError } from "../contracts/errors.js";
|
|
2
|
+
/** Invocation-local device numbers qualify canonical scope/dev without serializing opaque authority. */
|
|
3
|
+
export class PythonStatTranslator {
|
|
4
|
+
#devices = new Map();
|
|
5
|
+
#maxDevices;
|
|
6
|
+
#next = 1;
|
|
7
|
+
constructor(maxDevices = 1024) {
|
|
8
|
+
if (!Number.isSafeInteger(maxDevices) || maxDevices < 1)
|
|
9
|
+
throw new FsError("EINVAL");
|
|
10
|
+
this.#maxDevices = maxDevices;
|
|
11
|
+
}
|
|
12
|
+
translate(stat) {
|
|
13
|
+
const { identityScope, dev, ino, ...rest } = stat;
|
|
14
|
+
if (!(typeof identityScope === "symbol" || typeof identityScope === "object" && identityScope !== null)
|
|
15
|
+
|| !Number.isSafeInteger(dev) || dev < 0 || !Number.isSafeInteger(ino) || ino < 0)
|
|
16
|
+
return rest;
|
|
17
|
+
let devices = this.#devices.get(identityScope);
|
|
18
|
+
let guestDevice = devices?.get(dev);
|
|
19
|
+
if (guestDevice === undefined) {
|
|
20
|
+
if (this.#next > this.#maxDevices)
|
|
21
|
+
throw new FsError("EFBIG", { syscall: "stat", message: "Python device identity limit exceeded" });
|
|
22
|
+
if (!devices) {
|
|
23
|
+
devices = new Map();
|
|
24
|
+
this.#devices.set(identityScope, devices);
|
|
25
|
+
}
|
|
26
|
+
guestDevice = this.#next++;
|
|
27
|
+
devices.set(dev, guestDevice);
|
|
28
|
+
}
|
|
29
|
+
return { ...rest, dev: guestDevice, ino: ino };
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/safe-fs/xml.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface XmlElement extends XmlName {
|
|
|
22
22
|
readonly namespaces: ReadonlyMap<string, string>;
|
|
23
23
|
text: string;
|
|
24
24
|
readonly declaration?: string;
|
|
25
|
+
readonly prolog?: readonly XmlContent[];
|
|
26
|
+
readonly epilog?: readonly XmlContent[];
|
|
25
27
|
}
|
|
26
28
|
export interface XmlLimits {
|
|
27
29
|
readonly expectedEncoding?: "UTF-8" | "UTF-16" | "UTF-16LE" | "UTF-16BE";
|
|
@@ -32,6 +34,7 @@ export interface XmlLimits {
|
|
|
32
34
|
readonly maxAttributesPerElement?: number;
|
|
33
35
|
readonly maxNamespaces?: number;
|
|
34
36
|
readonly maxContentNodes?: number;
|
|
37
|
+
readonly maxTextLength?: number;
|
|
35
38
|
readonly onElement?: (element: XmlName, parent: XmlName | undefined, depth: number) => void;
|
|
36
39
|
}
|
|
37
40
|
export declare class XmlLimitError extends SyntaxError {
|
package/dist/safe-fs/xml.js
CHANGED
|
@@ -137,7 +137,7 @@ function* validDeclaration(content, expectedEncoding) {
|
|
|
137
137
|
return offset - start;
|
|
138
138
|
};
|
|
139
139
|
const field = function* (name) {
|
|
140
|
-
if (content.slice(offset, offset + name.length)
|
|
140
|
+
if (content.slice(offset, offset + name.length) !== name)
|
|
141
141
|
return undefined;
|
|
142
142
|
offset += name.length;
|
|
143
143
|
yield name.length;
|
|
@@ -163,21 +163,22 @@ function* validDeclaration(content, expectedEncoding) {
|
|
|
163
163
|
if (!(yield* whitespace()) || (yield* field("version")) !== "1.0")
|
|
164
164
|
return false;
|
|
165
165
|
let spacing = yield* whitespace();
|
|
166
|
-
if (content.slice(offset, offset + 8)
|
|
166
|
+
if (content.slice(offset, offset + 8) === "encoding") {
|
|
167
167
|
if (!spacing)
|
|
168
168
|
return false;
|
|
169
169
|
const encoding = yield* field("encoding");
|
|
170
170
|
if (encoding === undefined || encoding.length > 8 || !["utf-8", "utf-16", "utf-16le", "utf-16be"].includes(encoding.toLowerCase()))
|
|
171
171
|
return false;
|
|
172
|
-
if (expectedEncoding !== undefined && encoding.toLowerCase() !== expectedEncoding.toLowerCase()
|
|
172
|
+
if (expectedEncoding !== undefined && encoding.toLowerCase() !== expectedEncoding.toLowerCase()
|
|
173
|
+
&& !(encoding.toLowerCase() === "utf-16" && (expectedEncoding === "UTF-16LE" || expectedEncoding === "UTF-16BE")))
|
|
173
174
|
return false;
|
|
174
175
|
spacing = yield* whitespace();
|
|
175
176
|
}
|
|
176
|
-
if (content.slice(offset, offset + 10)
|
|
177
|
+
if (content.slice(offset, offset + 10) === "standalone") {
|
|
177
178
|
if (!spacing)
|
|
178
179
|
return false;
|
|
179
180
|
const standalone = yield* field("standalone");
|
|
180
|
-
if (standalone === undefined || standalone.length > 3 || !["yes", "no"].includes(standalone
|
|
181
|
+
if (standalone === undefined || standalone.length > 3 || !["yes", "no"].includes(standalone))
|
|
181
182
|
return false;
|
|
182
183
|
yield* whitespace();
|
|
183
184
|
}
|
|
@@ -198,6 +199,15 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
198
199
|
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
199
200
|
throw new RangeError("XML limits must be positive integers");
|
|
200
201
|
}
|
|
202
|
+
const maxTextLength = limits.maxTextLength ?? Number.MAX_SAFE_INTEGER;
|
|
203
|
+
if (!Number.isSafeInteger(maxTextLength) || maxTextLength < 1)
|
|
204
|
+
throw new RangeError("XML limits must be positive integers");
|
|
205
|
+
let textLength = 0;
|
|
206
|
+
const admitText = (text) => {
|
|
207
|
+
if (text.length > maxTextLength - textLength)
|
|
208
|
+
throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
|
|
209
|
+
textLength += text.length;
|
|
210
|
+
};
|
|
201
211
|
const chunks = [];
|
|
202
212
|
let chunk = "";
|
|
203
213
|
for (let index = input.charCodeAt(0) === 0xfeff ? 1 : 0; index < input.length; index++) {
|
|
@@ -226,6 +236,8 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
226
236
|
const source = chunks.join("");
|
|
227
237
|
const stack = [];
|
|
228
238
|
let root;
|
|
239
|
+
const prolog = [];
|
|
240
|
+
const epilog = [];
|
|
229
241
|
let declaration;
|
|
230
242
|
let offset = 0;
|
|
231
243
|
let nodes = 0;
|
|
@@ -261,6 +273,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
261
273
|
return name;
|
|
262
274
|
};
|
|
263
275
|
const appendText = function* (text, kind = "text") {
|
|
276
|
+
admitText(text);
|
|
264
277
|
const parent = stack.at(-1);
|
|
265
278
|
if (parent) {
|
|
266
279
|
parent.element.text += text;
|
|
@@ -278,6 +291,10 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
278
291
|
}
|
|
279
292
|
if (text.length % 512)
|
|
280
293
|
yield text.length % 512;
|
|
294
|
+
if (retainContent && text.length) {
|
|
295
|
+
admitContent();
|
|
296
|
+
(root ? epilog : prolog).push({ kind, text });
|
|
297
|
+
}
|
|
281
298
|
}
|
|
282
299
|
};
|
|
283
300
|
while (offset < source.length) {
|
|
@@ -287,7 +304,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
287
304
|
const text = source.slice(offset, next < 0 ? source.length : next);
|
|
288
305
|
if ((yield* find(text, "]]>", 0)) >= 0)
|
|
289
306
|
invalid("CDATA terminator in text");
|
|
290
|
-
yield* appendText(yield* entities(text));
|
|
307
|
+
yield* appendText(stack.length ? yield* entities(text) : text);
|
|
291
308
|
offset += text.length;
|
|
292
309
|
}
|
|
293
310
|
else if (source.startsWith("<!--", offset)) {
|
|
@@ -296,9 +313,11 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
296
313
|
invalid("malformed comment");
|
|
297
314
|
}
|
|
298
315
|
const parent = stack.at(-1);
|
|
299
|
-
|
|
316
|
+
const text = source.slice(offset + 4, end);
|
|
317
|
+
admitText(text);
|
|
318
|
+
if (retainContent) {
|
|
300
319
|
admitContent();
|
|
301
|
-
parent
|
|
320
|
+
(parent?.content ?? (root ? epilog : prolog)).push({ kind: "comment", text });
|
|
302
321
|
}
|
|
303
322
|
offset = end + 3;
|
|
304
323
|
}
|
|
@@ -331,17 +350,19 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
331
350
|
invalid("invalid processing instruction");
|
|
332
351
|
if (!(target.length === 3 && target.toLowerCase() === "xml")) {
|
|
333
352
|
const parent = stack.at(-1);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
353
|
+
let start = 0;
|
|
354
|
+
while (start < content.length && " \t\n\r".includes(content[start])) {
|
|
355
|
+
start++;
|
|
356
|
+
if (start % 512 === 0)
|
|
357
|
+
yield 512;
|
|
358
|
+
}
|
|
359
|
+
if (start % 512)
|
|
360
|
+
yield start % 512;
|
|
361
|
+
const text = content.slice(start);
|
|
362
|
+
admitText(text);
|
|
363
|
+
if (retainContent) {
|
|
343
364
|
admitContent();
|
|
344
|
-
parent
|
|
365
|
+
(parent?.content ?? (root ? epilog : prolog)).push({ kind: "processing-instruction", target, text });
|
|
345
366
|
}
|
|
346
367
|
}
|
|
347
368
|
offset = end + 2;
|
|
@@ -399,6 +420,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
399
420
|
if (raw.length % 512)
|
|
400
421
|
yield raw.length % 512;
|
|
401
422
|
const value = yield* entities(normalized);
|
|
423
|
+
admitText(value);
|
|
402
424
|
attributes.set(attribute, value);
|
|
403
425
|
offset = end + 1;
|
|
404
426
|
if (attribute === "xmlns" || attribute.startsWith("xmlns:")) {
|
|
@@ -452,7 +474,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
452
474
|
yield 1;
|
|
453
475
|
}
|
|
454
476
|
const content = retainContent ? [] : undefined;
|
|
455
|
-
const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent ? namespaces : emptyNamespaces, ...(root === undefined && declaration
|
|
477
|
+
const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
|
|
456
478
|
const parent = stack.at(-1);
|
|
457
479
|
if (parent) {
|
|
458
480
|
parent.element.children.push(element);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poe-platform/safe-fs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.631",
|
|
4
4
|
"description": "Composable filesystem with a portable core and explicit Node adapters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"browser": "./dist/safe-fs/core.js",
|
|
28
28
|
"import": "./dist/safe-fs/contracts/index.js"
|
|
29
29
|
},
|
|
30
|
+
"./xml": {
|
|
31
|
+
"types": "./dist/safe-fs/xml.d.ts",
|
|
32
|
+
"import": "./dist/safe-fs/xml.js"
|
|
33
|
+
},
|
|
30
34
|
"./core": {
|
|
31
35
|
"types": "./dist/safe-fs/core.d.ts",
|
|
32
36
|
"import": "./dist/safe-fs/core.js"
|