diff --git a/src/server/request-handler.ts b/src/server/request-handler.ts index db5ee1d..3fb70cc 100644 --- a/src/server/request-handler.ts +++ b/src/server/request-handler.ts @@ -19,6 +19,34 @@ type ShareListingEntry = { isCollection: boolean; }; +function resolveDavUrlFromShareUrl(shareUrl: string): string { + const url = new URL(shareUrl); + const publicShare = url.pathname.match(/\/s\/([^/?#]+)/); + + if (publicShare) { + return `${url.origin}/public.php/dav/files/${publicShare[1]}/`; + } + + const davShare = url.pathname.match(/\/public\.php\/dav\/files\/([^/?#]+)\/?/); + + if (davShare) { + return `${url.origin}/public.php/dav/files/${davShare[1]}/`; + } + + throw new Error("Bitte einen öffentlichen Nextcloud-Share-Link einfügen."); +} + +async function proxyUpstream(url: string, init?: RequestInit) { + const upstream = await fetch(url, init); + const body = await upstream.arrayBuffer(); + + return { + body: Buffer.from(body), + contentType: upstream.headers.get("content-type") ?? "application/octet-stream", + status: upstream.status + }; +} + const demoPhotos: Photo[] = [ { id: "demo-1", @@ -581,7 +609,7 @@ function htmlPage(): string { function parseShareInput(value) { const trimmed = value.trim(); const url = new URL(trimmed); - const publicShare = url.pathname.match(/\/s\/([^/?#]+)/); + const publicShare = url.pathname.match(/\\\/s\\\/([^/?#]+)/); if (publicShare) { return { origin: url.origin, @@ -589,7 +617,7 @@ function htmlPage(): string { }; } - const davShare = url.pathname.match(/\/public\.php\/dav\/files\/([^/?#]+)\/?/); + const davShare = url.pathname.match(/\\\/public\\.php\\\/dav\\\/files\\\/([^/?#]+)\\\/?/); if (davShare) { return { origin: url.origin, @@ -640,36 +668,19 @@ function htmlPage(): string { } async function loadShareListing(davUrl) { - const response = await fetch(davUrl, { - method: "PROPFIND", - mode: "cors", - headers: { - Depth: "1", - "Content-Type": "application/xml; charset=utf-8", - Accept: "application/xml, text/xml" - }, - body: - '' + - '' + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" - }); + const response = await fetch( + "/api/nextcloud/list?share=" + encodeURIComponent(shareUrl.value.trim()) + ); if (!response.ok) { - throw new Error("WebDAV hat mit Status " + response.status + " geantwortet."); + throw new Error("Nextcloud-Liste konnte nicht geladen werden: " + response.status); } return parseListing(await response.text(), davUrl); } async function readRemotePhoto(entry) { - const response = await fetch(entry.href, { mode: "cors" }); + const response = await fetch("/api/nextcloud/blob?url=" + encodeURIComponent(entry.href)); if (!response.ok) { throw new Error("Bild konnte nicht geladen werden: " + entry.name); } @@ -781,6 +792,84 @@ export function createRequestHandler() { return; } + if (url.pathname === "/api/nextcloud/list") { + const share = url.searchParams.get("share"); + + if (!share) { + res.statusCode = 400; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ error: "share parameter missing" })); + return; + } + + try { + const davUrl = resolveDavUrlFromShareUrl(share); + const xml = + '' + + '' + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + const upstream = await proxyUpstream(davUrl, { + method: "PROPFIND", + headers: { + Depth: "1", + "Content-Type": "application/xml; charset=utf-8", + Accept: "application/xml, text/xml" + }, + body: xml + }); + + res.statusCode = upstream.status; + res.setHeader("content-type", upstream.contentType); + res.end(upstream.body); + return; + } catch (error) { + res.statusCode = 502; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end( + JSON.stringify({ + error: error instanceof Error ? error.message : "upstream request failed" + }) + ); + return; + } + } + + if (url.pathname === "/api/nextcloud/blob") { + const target = url.searchParams.get("url"); + + if (!target) { + res.statusCode = 400; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ error: "url parameter missing" })); + return; + } + + try { + const upstream = await proxyUpstream(target); + res.statusCode = upstream.status; + res.setHeader("content-type", upstream.contentType); + res.end(upstream.body); + return; + } catch (error) { + res.statusCode = 502; + res.setHeader("content-type", "application/json; charset=utf-8"); + res.end( + JSON.stringify({ + error: error instanceof Error ? error.message : "upstream request failed" + }) + ); + return; + } + } + res.statusCode = 200; res.setHeader("content-type", "text/html; charset=utf-8"); res.end(htmlPage());