NextChat-U/app/api/webdav/[...path]/route.ts

168 lines
3.8 KiB
TypeScript
Raw Normal View History

2024-03-14 01:33:26 +09:00
import { NextRequest, NextResponse } from "next/server";
import { STORAGE_KEY, internalAllowedWebDavEndpoints } from "../../../constant";
2024-04-09 19:05:56 +09:00
import { getServerSideConfig } from "@/app/config/server";
const config = getServerSideConfig();
const mergedAllowedWebDavEndpoints = [
...internalAllowedWebDavEndpoints,
...config.allowedWebDevEndpoints,
2024-04-09 19:05:56 +09:00
].filter((domain) => Boolean(domain.trim()));
2024-06-24 15:31:50 +09:00
const normalizeUrl = (url: string) => {
try {
return new URL(url);
} catch (err) {
return null;
}
};
2024-03-14 01:33:26 +09:00
async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}
const folder = STORAGE_KEY;
const fileName = `${folder}/backup.json`;
const requestUrl = new URL(req.url);
2024-03-14 02:58:25 +09:00
let endpoint = requestUrl.searchParams.get("endpoint");
let proxy_method = requestUrl.searchParams.get("proxy_method") || req.method;
// Validate the endpoint to prevent potential SSRF attacks
2024-04-09 19:05:56 +09:00
if (
2024-06-24 15:31:50 +09:00
!endpoint ||
!mergedAllowedWebDavEndpoints.some((allowedEndpoint) => {
const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint);
const normalizedEndpoint = normalizeUrl(endpoint as string);
2024-07-11 16:29:47 +09:00
return (
normalizedEndpoint &&
2024-06-24 15:31:50 +09:00
normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname &&
2024-07-11 16:29:47 +09:00
normalizedEndpoint.pathname.startsWith(
normalizedAllowedEndpoint.pathname,
)
);
2024-06-24 15:31:50 +09:00
})
2024-04-09 19:05:56 +09:00
) {
return NextResponse.json(
{
error: true,
msg: "Invalid endpoint",
},
{
status: 400,
},
);
2024-03-14 02:58:25 +09:00
}
2024-04-09 19:05:56 +09:00
if (!endpoint?.endsWith("/")) {
endpoint += "/";
}
2024-03-14 02:58:25 +09:00
const endpointPath = params.path.join("/");
2024-04-12 14:40:37 +09:00
const targetPath = `${endpoint}${endpointPath}`;
2024-03-14 01:33:26 +09:00
// only allow MKCOL, GET, PUT
if (
proxy_method !== "MKCOL" &&
proxy_method !== "GET" &&
proxy_method !== "PUT"
) {
2024-03-14 01:33:26 +09:00
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + targetPath,
2024-03-14 01:33:26 +09:00
},
{
status: 403,
},
);
}
// for MKCOL request, only allow request ${folder}
if (proxy_method === "MKCOL" && !targetPath.endsWith(folder)) {
2024-03-14 01:33:26 +09:00
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + targetPath,
2024-03-14 01:33:26 +09:00
},
{
status: 403,
},
);
}
// for GET request, only allow request ending with fileName
if (proxy_method === "GET" && !targetPath.endsWith(fileName)) {
2024-03-14 01:33:26 +09:00
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + targetPath,
2024-03-14 01:33:26 +09:00
},
{
status: 403,
},
);
}
// for PUT request, only allow request ending with fileName
if (proxy_method === "PUT" && !targetPath.endsWith(fileName)) {
2024-03-14 01:33:26 +09:00
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + targetPath,
2024-03-14 01:33:26 +09:00
},
{
status: 403,
},
);
}
2024-04-12 14:40:37 +09:00
const targetUrl = targetPath;
2024-03-14 01:33:26 +09:00
const method = proxy_method || req.method;
2024-03-14 01:33:26 +09:00
const shouldNotHaveBody = ["get", "head"].includes(
method?.toLowerCase() ?? "",
);
const fetchOptions: RequestInit = {
headers: {
authorization: req.headers.get("authorization") ?? "",
},
body: shouldNotHaveBody ? null : req.body,
2024-04-09 19:05:56 +09:00
redirect: "manual",
2024-03-14 01:33:26 +09:00
method,
// @ts-ignore
duplex: "half",
};
2024-04-12 14:40:37 +09:00
let fetchResult;
try {
fetchResult = await fetch(targetUrl, fetchOptions);
} finally {
2024-04-12 14:46:37 +09:00
console.log(
"[Any Proxy]",
targetUrl,
{
method: method,
2024-04-12 14:46:37 +09:00
},
{
status: fetchResult?.status,
statusText: fetchResult?.statusText,
},
);
2024-04-12 14:40:37 +09:00
}
2024-03-14 01:33:26 +09:00
return fetchResult;
}
2024-04-09 19:05:56 +09:00
export const PUT = handle;
2024-03-14 01:33:26 +09:00
export const GET = handle;
export const OPTIONS = handle;
export const runtime = "edge";