diff --git a/src/webdav.ts b/src/webdav.ts
index cb089c0..38db58a 100644
--- a/src/webdav.ts
+++ b/src/webdav.ts
@@ -4,8 +4,9 @@
import { SearchOptions, FileMetadata, TrashedFile, FileVersion, QuotaInfo } from "./types.js";
import { normalizePath } from "./utils.js";
-// --- XML Builders (scaffolding) ---
+// --- XML Builders ---
+/** PROPFIND standard — richiede il set di proprietà di default o quelle specificate */
export function buildPropfindBody(selectedProps?: string[]): string {
const props = selectedProps && selectedProps.length > 0
? selectedProps.map((p) => ` <${p} />`).join("\n")
@@ -28,6 +29,7 @@ ${props}
`;
}
+/** PROPFIND esteso — per get_file_info (include owner, checksum, has-preview) */
export function buildPropfindExtendedBody(): string {
return `
@@ -50,6 +52,7 @@ export function buildPropfindExtendedBody(): string {
`;
}
+/** PROPFIND per cestino — proprietà trashbin-specific */
export function buildTrashbinPropfindBody(): string {
return `
@@ -70,6 +73,7 @@ export function buildTrashbinPropfindBody(): string {
`;
}
+/** PROPFIND per versioni file */
export function buildVersionsPropfindBody(): string {
return `
@@ -82,17 +86,19 @@ export function buildVersionsPropfindBody(): string {
`;
}
+/** PROPPATCH generico — imposta una proprietà su una risorsa */
export function buildProppatchBody(namespace: string, property: string, value: string): string {
return `
- <${namespace}:${property}>${value}${namespace}:${property}>
+ <${namespace}:${property}>${escapeXml(value)}${namespace}:${property}>
`;
}
+/** REPORT — filtro per file preferiti */
export function buildFavoriteFilterBody(selectedProps?: string[]): string {
const props = selectedProps && selectedProps.length > 0
? selectedProps.map((p) => ` <${p} />`).join("\n")
@@ -118,6 +124,18 @@ ${props}
`;
}
+/** PROPFIND per quota — richiede d:quota-used-bytes e d:quota-available-bytes */
+export function buildQuotaBody(): string {
+ return `
+
+
+
+
+
+`;
+}
+
+/** SEARCH request generica (rfc5323) — costruisce dinamicamente il where-clause */
export function buildSearchRequest(options: SearchOptions): string {
const filters: string[] = [];
@@ -225,72 +243,104 @@ ${limit}
// --- XML Parsers (regex-based, like caldav.ts) ---
-function extractProperty(block: string, namespace: string, property: string): string | null {
- const regex = new RegExp(`<(?:${namespace}:)?${property}[^>]*>([^<]*)(?:${namespace}:)?${property}>`, "i");
+/** Estrae il valore testuale di una proprietà XML da un blocco .
+ * Gestisce namespace con prefisso () e senza prefisso
+ * (). */
+export function extractProperty(block: string, namespace: string, property: string): string | null {
+ const regex = new RegExp(`<(?:${namespace}:)?${property}\\b[^>]*>([^<]*)(?:${namespace}:)?${property}>`, "i");
const match = block.match(regex);
return match ? decodeXmlText(match[1]) : null;
}
-function extractNumericProperty(block: string, namespace: string, property: string): number | null {
+/** Estrae una proprietà numerica; restituisce null se mancante o non valida. */
+export function extractNumericProperty(block: string, namespace: string, property: string): number | null {
const val = extractProperty(block, namespace, property);
if (val === null || val === "") return null;
const num = Number(val);
return Number.isFinite(num) ? num : null;
}
-function extractBooleanProperty(block: string, namespace: string, property: string): boolean {
+/** Estrae una proprietà booleana (1/true = true, tutto il resto = false). */
+export function extractBooleanProperty(block: string, namespace: string, property: string): boolean {
const val = extractProperty(block, namespace, property);
return val === "1" || val?.toLowerCase() === "true";
}
-function hasCollection(block: string): boolean {
+/** Verifica se il blocco contiene un (cartella). */
+export function hasCollection(block: string): boolean {
return /<(?:\w+:)?collection\b/i.test(block);
}
-function extractHref(block: string): string | null {
- const match = block.match(/<(?:\w+:)?href[^>]*>([\s\S]*?)<\/(?:\w+:)?href>/);
+/** Estrae l'href da un blocco . */
+export function extractHref(block: string): string | null {
+ const match = block.match(/<(?:\w+:)?href\b[^>]*>([\s\S]*?)<\/(?:\w+:)?href>/);
return match ? decodeXmlText(match[1].trim()) : null;
}
+/** Converte un blocco in un oggetto FileMetadata. */
+function parseFileMetadataFromBlock(block: string, basePath: string): FileMetadata | null {
+ const href = extractHref(block);
+ if (!href) return null;
+
+ const name = extractProperty(block, "d", "displayname") || "";
+ const isFolder = hasCollection(block);
+
+ return {
+ name,
+ path: resolveRelativePathFromHref(href, basePath),
+ type: isFolder ? "folder" : "file",
+ size: extractNumericProperty(block, "oc", "size") ?? undefined,
+ contentLength: extractNumericProperty(block, "d", "getcontentlength") ?? undefined,
+ mimeType: extractProperty(block, "d", "getcontenttype") ?? undefined,
+ lastModified: extractProperty(block, "d", "getlastmodified") ?? undefined,
+ etag: extractProperty(block, "d", "getetag") ?? undefined,
+ fileId: extractNumericProperty(block, "oc", "fileid") ?? undefined,
+ permissions: extractProperty(block, "oc", "permissions") ?? undefined,
+ favorite: extractBooleanProperty(block, "oc", "favorite"),
+ ownerId: extractProperty(block, "oc", "owner-id") ?? undefined,
+ ownerDisplayName: extractProperty(block, "oc", "owner-display-name") ?? undefined,
+ hasPreview: extractBooleanProperty(block, "nc", "has-preview"),
+ };
+}
+
+/** PROPFIND response → array FileMetadata.
+ * ESCLUDE il primo elemento (la cartella root stessa). */
export function parsePropfindFilesResponse(xml: string, basePath: string): FileMetadata[] {
const files: FileMetadata[] = [];
const responseMatches = xml.matchAll(/<(?:\w+:)?response\b[\s\S]*?<\/(?:\w+:)?response>/g);
+ let isFirst = true;
for (const match of responseMatches) {
const block = match[0];
- const href = extractHref(block);
- if (!href) continue;
+ if (isFirst) {
+ isFirst = false;
+ continue; // Skip root folder
+ }
- const name = extractProperty(block, "d", "displayname") || "";
- const isFolder = hasCollection(block);
-
- files.push({
- name,
- path: resolveRelativePathFromHref(href, basePath),
- type: isFolder ? "folder" : "file",
- size: extractNumericProperty(block, "oc", "size") ?? undefined,
- contentLength: extractNumericProperty(block, "d", "getcontentlength") ?? undefined,
- mimeType: extractProperty(block, "d", "getcontenttype") ?? undefined,
- lastModified: extractProperty(block, "d", "getlastmodified") ?? undefined,
- etag: extractProperty(block, "d", "getetag") ?? undefined,
- fileId: extractNumericProperty(block, "oc", "fileid") ?? undefined,
- permissions: extractProperty(block, "oc", "permissions") ?? undefined,
- favorite: extractBooleanProperty(block, "oc", "favorite"),
- });
+ const meta = parseFileMetadataFromBlock(block, basePath);
+ if (meta) files.push(meta);
}
return files;
}
+/** PROPFIND Depth:0 response → singolo FileMetadata.
+ * Non esclude il primo elemento (è l'unico elemento richiesto). */
export function parsePropfindSingleFileResponse(xml: string, basePath: string): FileMetadata | null {
- const files = parsePropfindFilesResponse(xml, basePath);
- return files[0] || null;
+ const responseMatches = xml.matchAll(/<(?:\w+:)?response\b[\s\S]*?<\/(?:\w+:)?response>/g);
+ for (const match of responseMatches) {
+ const meta = parseFileMetadataFromBlock(match[0], basePath);
+ if (meta) return meta;
+ }
+ return null;
}
export function parseSearchResponse(xml: string, basePath: string): FileMetadata[] {
return parsePropfindFilesResponse(xml, basePath);
}
+/** PROPFIND trashbin → array TrashedFile.
+ * Il primo elemento è il cestino stesso; viene incluso (diverso da parsePropfindFilesResponse). */
export function parseTrashbinResponse(xml: string): TrashedFile[] {
const files: TrashedFile[] = [];
const responseMatches = xml.matchAll(/<(?:\w+:)?response\b[\s\S]*?<\/(?:\w+:)?response>/g);