@poe-platform/safe-fs 0.1.724 → 0.1.725
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/README.md
CHANGED
|
@@ -339,6 +339,8 @@ Each batch lists from the beginning because previous keys have been deleted; it
|
|
|
339
339
|
|
|
340
340
|
Known identity Content-Length responses use one result buffer; other responses grow storage up to the configured ceiling and return a view without a final copy. Growth can temporarily retain the old and new buffers (up to three times the response size), plus transport chunks. These per-response defaults leave headroom in Workers; hosts must still budget for metadata parsing, text decoding, transport buffers, and concurrent reads, especially when raising the limits. Use streaming reads for large files.
|
|
341
341
|
|
|
342
|
+
WebDAV metadata parsing limits the document to 100,000 elements, 100,000 content nodes, 100,000 attributes, and 256 levels of nesting, independently of `maxEntries`. Text is bounded by `maxXmlBytes`. Exceeding a structural budget reports `EFBIG`; parsing yields cooperatively so caller cancellation and `timeoutMs` remain active.
|
|
343
|
+
|
|
342
344
|
See the [binding types](src/fs/webdav/webdav.ts) before implementing atomic directory removal. A recursive WebDAV DELETE does not satisfy that contract.
|
|
343
345
|
|
|
344
346
|
</details>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FsError, isErrnoCode, isFsError } from "../../contracts/errors.js";
|
|
2
2
|
import { composeAbortSignals } from "../../contracts/abort.js";
|
|
3
3
|
import { readBytes } from "../../contracts/io.js";
|
|
4
|
-
import { davChild, davChildren,
|
|
4
|
+
import { davChild, davChildren, parseXmlSteps, scalar, XmlResponseLimitError, XmlResourceLimitError } from "./xml.js";
|
|
5
5
|
import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
|
|
6
6
|
import { assertCallbackAuthorityAllowed, compareEntries, registerEntryAuthority } from "../mount/comparison.js";
|
|
7
7
|
import { compareWebDavResources, ownedResponseIdentifier, recordOwnedResourceStat, registerResourceQuery, resourceIdentifier } from "./resource-id.js";
|
|
@@ -561,7 +561,34 @@ export class WebDavFileSystem {
|
|
|
561
561
|
const data = await this.bytes(response, this.maxXmlBytes, signal);
|
|
562
562
|
const encoding = (data[0] === 0xff && data[1] === 0xfe) || (data[0] === 0x3c && data[1] === 0)
|
|
563
563
|
? "utf-16le" : (data[0] === 0xfe && data[1] === 0xff) || (data[0] === 0 && data[1] === 0x3c) ? "utf-16be" : "utf-8";
|
|
564
|
-
|
|
564
|
+
const parser = parseXmlSteps(new TextDecoder(encoding, { fatal: true }).decode(data), {
|
|
565
|
+
...(maxResponses === undefined ? {} : { maxResponses }),
|
|
566
|
+
maxNodes: 100_000, maxContentNodes: 100_000, maxDepth: 256,
|
|
567
|
+
maxAttributes: 100_000, maxTextLength: this.maxXmlBytes,
|
|
568
|
+
});
|
|
569
|
+
let work = 0;
|
|
570
|
+
try {
|
|
571
|
+
while (true) {
|
|
572
|
+
signal.throwIfAborted();
|
|
573
|
+
const step = parser.next();
|
|
574
|
+
if (step.done)
|
|
575
|
+
return step.value;
|
|
576
|
+
work += step.value;
|
|
577
|
+
if (work >= 4096) {
|
|
578
|
+
work = 0;
|
|
579
|
+
// Yield to the host task queue so request deadlines and caller aborts can fire.
|
|
580
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch (error) {
|
|
585
|
+
if (error instanceof XmlResourceLimitError)
|
|
586
|
+
fail("EFBIG", "webdav", "", error.message);
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
parser.return(undefined);
|
|
591
|
+
}
|
|
565
592
|
}
|
|
566
593
|
async multistatus(response, signal, method = "PROPFIND", path = "") {
|
|
567
594
|
const link = response.headers.get("link");
|
|
@@ -6,7 +6,10 @@ export interface XmlLimits extends Omit<DocumentLimits, "onElement"> {
|
|
|
6
6
|
}
|
|
7
7
|
export declare class XmlResponseLimitError extends SyntaxError {
|
|
8
8
|
}
|
|
9
|
+
export declare class XmlResourceLimitError extends SyntaxError {
|
|
10
|
+
}
|
|
9
11
|
export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
|
|
12
|
+
export declare function parseXmlSteps(input: string, limits?: XmlLimits): Generator<number, XmlElement, void>;
|
|
10
13
|
export declare function davChildren(element: XmlElement, localName: string): XmlElement[];
|
|
11
14
|
export declare function davChild(element: XmlElement, localName: string): XmlElement | undefined;
|
|
12
15
|
export declare function scalar(element: XmlElement): string;
|
|
@@ -1,15 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseXmlSteps as parseDocumentSteps, XmlLimitError } from "../../xml.js";
|
|
2
2
|
export class XmlResponseLimitError extends SyntaxError {
|
|
3
3
|
}
|
|
4
|
+
export class XmlResourceLimitError extends SyntaxError {
|
|
5
|
+
}
|
|
4
6
|
function invalid(message) { throw new SyntaxError(`Invalid WebDAV XML: ${message}`); }
|
|
5
7
|
export function parseXml(input, limits = {}) {
|
|
8
|
+
const parser = parseXmlSteps(input, limits);
|
|
9
|
+
try {
|
|
10
|
+
let step = parser.next();
|
|
11
|
+
while (!step.done)
|
|
12
|
+
step = parser.next();
|
|
13
|
+
return step.value;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error instanceof XmlResourceLimitError)
|
|
17
|
+
throw new SyntaxError(error.message);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function* parseXmlSteps(input, limits = {}) {
|
|
6
22
|
const { maxResponses, ...documentLimits } = limits;
|
|
7
23
|
if (maxResponses !== undefined && (!Number.isSafeInteger(maxResponses) || maxResponses < 1)) {
|
|
8
24
|
throw new RangeError("XML limits must be positive integers");
|
|
9
25
|
}
|
|
10
26
|
let responses = 0;
|
|
11
27
|
try {
|
|
12
|
-
return
|
|
28
|
+
return yield* parseDocumentSteps(input, { ...documentLimits, retainContent: false,
|
|
13
29
|
onElement(element, parent, depth) {
|
|
14
30
|
if (maxResponses !== undefined && depth === 2 && parent?.namespace === "DAV:" && parent.localName === "multistatus"
|
|
15
31
|
&& element.namespace === "DAV:" && element.localName === "response" && ++responses > maxResponses) {
|
|
@@ -20,7 +36,7 @@ export function parseXml(input, limits = {}) {
|
|
|
20
36
|
}
|
|
21
37
|
catch (error) {
|
|
22
38
|
if (error instanceof XmlLimitError)
|
|
23
|
-
|
|
39
|
+
throw new XmlResourceLimitError(`Invalid WebDAV XML: ${error.message}`);
|
|
24
40
|
if (error instanceof SyntaxError && error.message.startsWith("Invalid XML:")) {
|
|
25
41
|
error.message = error.message.replace("Invalid XML:", "Invalid WebDAV XML:");
|
|
26
42
|
}
|
package/dist/safe-fs/xml.js
CHANGED
|
@@ -288,9 +288,10 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
288
288
|
const parent = stack.at(-1);
|
|
289
289
|
if (parent) {
|
|
290
290
|
parent.element.text += text;
|
|
291
|
-
if (
|
|
291
|
+
if (text.length || kind === "cdata") {
|
|
292
292
|
admitContent();
|
|
293
|
-
|
|
293
|
+
if (retainContent)
|
|
294
|
+
parent.content.push({ kind, text });
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
else {
|
|
@@ -302,9 +303,10 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
302
303
|
}
|
|
303
304
|
if (text.length % 512)
|
|
304
305
|
yield text.length % 512;
|
|
305
|
-
if (
|
|
306
|
+
if (text.length) {
|
|
306
307
|
admitContent();
|
|
307
|
-
|
|
308
|
+
if (retainContent)
|
|
309
|
+
(root ? epilog : prolog).push({ kind, text });
|
|
308
310
|
}
|
|
309
311
|
}
|
|
310
312
|
};
|
|
@@ -326,8 +328,8 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
326
328
|
const parent = stack.at(-1);
|
|
327
329
|
const text = source.slice(offset + 4, end);
|
|
328
330
|
admitText(text);
|
|
331
|
+
admitContent();
|
|
329
332
|
if (retainContent) {
|
|
330
|
-
admitContent();
|
|
331
333
|
(parent?.content ?? (root ? epilog : prolog)).push({ kind: "comment", text });
|
|
332
334
|
}
|
|
333
335
|
offset = end + 3;
|
|
@@ -371,8 +373,8 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
371
373
|
yield start % 512;
|
|
372
374
|
const text = content.slice(start);
|
|
373
375
|
admitText(text);
|
|
376
|
+
admitContent();
|
|
374
377
|
if (retainContent) {
|
|
375
|
-
admitContent();
|
|
376
378
|
(parent?.content ?? (root ? epilog : prolog)).push({ kind: "processing-instruction", target, text });
|
|
377
379
|
}
|
|
378
380
|
}
|