/** * WebDAV XML generation utilities */ import type { IWebDAVResource, IWebDAVProperty, IWebDAVLock } from './webdav.types.js'; import { DAV_NAMESPACE } from './webdav.types.js'; /** * Escape XML special characters */ function escapeXml(str: string): string { return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Format date for WebDAV (RFC 1123) */ function formatDate(date: Date): string { return date.toUTCString(); } /** * Format date for creationdate (ISO 8601) */ function formatCreationDate(date: Date): string { return date.toISOString(); } /** * Generate multistatus response for PROPFIND */ export function generateMultistatus(resources: IWebDAVResource[]): string { const responses = resources.map(resource => generateResponse(resource)).join('\n'); return ` ${responses} `; } /** * Generate single response element */ function generateResponse(resource: IWebDAVResource): string { const props: string[] = []; // Resource type if (resource.isCollection) { props.push(''); } else { props.push(''); } // Display name if (resource.displayName) { props.push(`${escapeXml(resource.displayName)}`); } // Content type if (resource.contentType) { props.push(`${escapeXml(resource.contentType)}`); } // Content length if (resource.contentLength !== undefined) { props.push(`${resource.contentLength}`); } // Last modified if (resource.lastModified) { props.push(`${formatDate(resource.lastModified)}`); } // Creation date if (resource.creationDate) { props.push(`${formatCreationDate(resource.creationDate)}`); } // ETag if (resource.etag) { props.push(`"${escapeXml(resource.etag)}"`); } // Supported lock props.push(` `); // Custom properties for (const prop of resource.properties) { if (prop.value) { props.push(`<${prop.name} xmlns="${prop.namespace}">${escapeXml(prop.value)}`); } else { props.push(`<${prop.name} xmlns="${prop.namespace}"/>`); } } return ` ${escapeXml(resource.href)} ${props.join('\n ')} HTTP/1.1 200 OK `; } /** * Generate lock discovery response */ export function generateLockDiscovery(locks: IWebDAVLock[]): string { if (locks.length === 0) { return ''; } const lockEntries = locks.map(lock => ` ${lock.depth} ${escapeXml(lock.owner)} Second-${lock.timeout} ${escapeXml(lock.token)} `).join(''); return `${lockEntries} `; } /** * Generate lock response */ export function generateLockResponse(lock: IWebDAVLock): string { return ` ${generateLockDiscovery([lock])} `; } /** * Generate error response */ export function generateError(status: number, message: string): string { return ` `; } /** * Parse PROPFIND request body to extract requested properties */ export function parsePropfindRequest(body: string): { allprop: boolean; propnames: string[] } { // Simple XML parsing for PROPFIND if (!body || body.includes(']*>([\s\S]*?)<\/(?:D:)?prop>/i); if (propMatch) { const propContent = propMatch[1]; const tagMatches = propContent.matchAll(/<(?:D:)?(\w+)[^>]*\/?>/gi); for (const match of tagMatches) { propnames.push(match[1]); } } return { allprop: propnames.length === 0, propnames }; }