@poe-platform/safe-fs 0.1.73 → 0.1.75
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.
|
@@ -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, parseXml, scalar } from "./xml.js";
|
|
4
|
+
import { davChild, davChildren, parseXml, scalar, XmlResponseLimitError } 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";
|
|
@@ -474,20 +474,26 @@ export class WebDavFileSystem {
|
|
|
474
474
|
reader.releaseLock();
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
|
-
async xml(response, signal) {
|
|
477
|
+
async xml(response, signal, maxResponses) {
|
|
478
478
|
const data = await this.bytes(response, this.maxXmlBytes, signal);
|
|
479
479
|
const encoding = (data[0] === 0xff && data[1] === 0xfe) || (data[0] === 0x3c && data[1] === 0)
|
|
480
480
|
? "utf-16le" : (data[0] === 0xfe && data[1] === 0xff) || (data[0] === 0 && data[1] === 0x3c) ? "utf-16be" : "utf-8";
|
|
481
|
-
return parseXml(new TextDecoder(encoding, { fatal: true }).decode(data), {
|
|
482
|
-
maxNodes: this.maxXmlBytes, maxAttributes: this.maxXmlBytes,
|
|
483
|
-
});
|
|
481
|
+
return parseXml(new TextDecoder(encoding, { fatal: true }).decode(data), maxResponses === undefined ? undefined : { maxResponses });
|
|
484
482
|
}
|
|
485
483
|
async multistatus(response, signal, method = "PROPFIND", path = "") {
|
|
486
484
|
const link = response.headers.get("link");
|
|
487
485
|
if (link && /\brel\s*=\s*(?:"[^"]*\bnext\b[^"]*"|'[^']*\bnext\b[^']*'|next\b)/i.test(link)) {
|
|
488
486
|
fail("ENOTSUP", method, path, "paginated WebDAV responses are unsupported");
|
|
489
487
|
}
|
|
490
|
-
|
|
488
|
+
let root;
|
|
489
|
+
try {
|
|
490
|
+
root = await this.xml(response, signal, this.maxEntries);
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
if (error instanceof XmlResponseLimitError)
|
|
494
|
+
fail("EFBIG", method, path, "response exceeds entry limit");
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
491
497
|
if (root.namespace !== "DAV:" || root.localName !== "multistatus")
|
|
492
498
|
throw new Error("expected DAV:multistatus");
|
|
493
499
|
const responses = davChildren(root, "response");
|
|
@@ -8,6 +8,9 @@ export interface XmlLimits {
|
|
|
8
8
|
readonly maxDepth?: number;
|
|
9
9
|
readonly maxNodes?: number;
|
|
10
10
|
readonly maxAttributes?: number;
|
|
11
|
+
readonly maxResponses?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class XmlResponseLimitError extends SyntaxError {
|
|
11
14
|
}
|
|
12
15
|
export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
|
|
13
16
|
export declare function davChildren(element: XmlElement, localName: string): XmlElement[];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export class XmlResponseLimitError extends SyntaxError {
|
|
2
|
+
}
|
|
1
3
|
const xmlNamespace = "http://www.w3.org/XML/1998/namespace";
|
|
2
4
|
const xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
|
|
3
5
|
function invalid(message) {
|
|
@@ -66,7 +68,7 @@ export function parseXml(input, limits = {}) {
|
|
|
66
68
|
const maxDepth = limits.maxDepth ?? 64;
|
|
67
69
|
const maxNodes = limits.maxNodes ?? 100_000;
|
|
68
70
|
const maxAttributes = limits.maxAttributes ?? 10_000;
|
|
69
|
-
for (const limit of [maxDepth, maxNodes, maxAttributes]) {
|
|
71
|
+
for (const limit of [maxDepth, maxNodes, maxAttributes, ...(limits.maxResponses === undefined ? [] : [limits.maxResponses])]) {
|
|
70
72
|
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
71
73
|
throw new RangeError("XML limits must be positive integers");
|
|
72
74
|
}
|
|
@@ -80,6 +82,7 @@ export function parseXml(input, limits = {}) {
|
|
|
80
82
|
let offset = 0;
|
|
81
83
|
let nodes = 0;
|
|
82
84
|
let attributeCount = 0;
|
|
85
|
+
let responses = 0;
|
|
83
86
|
const whitespace = () => {
|
|
84
87
|
while (offset < source.length && " \t\n\r".includes(source[offset]))
|
|
85
88
|
offset++;
|
|
@@ -211,9 +214,16 @@ export function parseXml(input, limits = {}) {
|
|
|
211
214
|
const [prefix, localName] = qualifiedName(name);
|
|
212
215
|
if (prefix === "xmlns" || (prefix && !namespaces.has(prefix)))
|
|
213
216
|
invalid("unbound element prefix");
|
|
214
|
-
const element = { namespace: namespaces.get(prefix) ?? "", localName, children: [], text: "" };
|
|
215
217
|
if (++nodes > maxNodes || stack.length + 1 > maxDepth)
|
|
216
218
|
invalid("XML resource limit exceeded");
|
|
219
|
+
const namespace = namespaces.get(prefix) ?? "";
|
|
220
|
+
if (limits.maxResponses !== undefined && stack.length === 1
|
|
221
|
+
&& root?.namespace === "DAV:" && root.localName === "multistatus"
|
|
222
|
+
&& namespace === "DAV:" && localName === "response"
|
|
223
|
+
&& ++responses > limits.maxResponses) {
|
|
224
|
+
throw new XmlResponseLimitError("WebDAV XML response limit exceeded");
|
|
225
|
+
}
|
|
226
|
+
const element = { namespace, localName, children: [], text: "" };
|
|
217
227
|
const parent = stack.at(-1);
|
|
218
228
|
if (parent)
|
|
219
229
|
parent.element.children.push(element);
|