fix: proxy nextcloud requests through server
This commit is contained in:
@@ -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());
|
||||||
|
|||||||
Reference in New Issue
Block a user