fix: proxy nextcloud requests through server

This commit is contained in:
2026-06-07 17:30:35 +02:00
parent 7bbe4c698c
commit 9fe4752c2e

View File

@@ -19,6 +19,34 @@ type ShareListingEntry = {
isCollection: boolean; 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[] = [ const demoPhotos: Photo[] = [
{ {
id: "demo-1", id: "demo-1",
@@ -581,7 +609,7 @@ function htmlPage(): string {
function parseShareInput(value) { function parseShareInput(value) {
const trimmed = value.trim(); const trimmed = value.trim();
const url = new URL(trimmed); const url = new URL(trimmed);
const publicShare = url.pathname.match(/\/s\/([^/?#]+)/); const publicShare = url.pathname.match(/\\\/s\\\/([^/?#]+)/);
if (publicShare) { if (publicShare) {
return { return {
origin: url.origin, 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) { if (davShare) {
return { return {
origin: url.origin, origin: url.origin,
@@ -640,36 +668,19 @@ function htmlPage(): string {
} }
async function loadShareListing(davUrl) { async function loadShareListing(davUrl) {
const response = await fetch(davUrl, { const response = await fetch(
method: "PROPFIND", "/api/nextcloud/list?share=" + encodeURIComponent(shareUrl.value.trim())
mode: "cors", );
headers: {
Depth: "1",
"Content-Type": "application/xml; charset=utf-8",
Accept: "application/xml, text/xml"
},
body:
'<?xml version="1.0" encoding="UTF-8"?>' +
'<d:propfind xmlns:d="DAV:">' +
"<d:prop>" +
"<d:displayname/>" +
"<d:getlastmodified/>" +
"<d:getcontenttype/>" +
"<d:getcontentlength/>" +
"<d:resourcetype/>" +
"</d:prop>" +
"</d:propfind>"
});
if (!response.ok) { 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); return parseListing(await response.text(), davUrl);
} }
async function readRemotePhoto(entry) { 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) { if (!response.ok) {
throw new Error("Bild konnte nicht geladen werden: " + entry.name); throw new Error("Bild konnte nicht geladen werden: " + entry.name);
} }
@@ -781,6 +792,84 @@ export function createRequestHandler() {
return; 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 =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<d:propfind xmlns:d="DAV:">' +
"<d:prop>" +
"<d:displayname/>" +
"<d:getlastmodified/>" +
"<d:getcontenttype/>" +
"<d:getcontentlength/>" +
"<d:resourcetype/>" +
"</d:prop>" +
"</d:propfind>";
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.statusCode = 200;
res.setHeader("content-type", "text/html; charset=utf-8"); res.setHeader("content-type", "text/html; charset=utf-8");
res.end(htmlPage()); res.end(htmlPage());