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

132 lines
3.1 KiB
TypeScript
Raw Normal View History

2024-03-14 01:33:26 +09:00
import { NextRequest, NextResponse } from "next/server";
2024-04-09 19:23:52 +09:00
import { STORAGE_KEY, internalWhiteWebDavEndpoints } from "../../../constant";
2024-04-09 19:05:56 +09:00
import { getServerSideConfig } from "@/app/config/server";
const config = getServerSideConfig();
2024-04-09 19:23:52 +09:00
const mergedWhiteWebDavEndpoints = [
...internalWhiteWebDavEndpoints,
...config.whiteWebDevEndpoints,
2024-04-09 19:05:56 +09:00
].filter((domain) => Boolean(domain.trim()));
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");
// Validate the endpoint to prevent potential SSRF attacks
2024-04-09 19:05:56 +09:00
if (
2024-04-09 19:23:52 +09:00
!mergedWhiteWebDavEndpoints.some((white) => endpoint?.startsWith(white))
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("/");
const targetPath = `${endpoint}/${endpointPath}`;
2024-03-14 01:33:26 +09:00
// only allow MKCOL, GET, PUT
if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") {
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}
2024-04-09 19:05:56 +09:00
if (req.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
2024-04-09 19:05:56 +09:00
if (req.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
2024-04-09 19:05:56 +09:00
if (req.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,
},
);
}
const targetUrl = `${endpoint}/${endpointPath}`;
2024-03-14 01:33:26 +09:00
2024-03-14 02:56:36 +09:00
const 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",
};
const fetchResult = await fetch(targetUrl, fetchOptions);
2024-04-09 19:24:22 +09:00
console.log("[Any Proxy]", targetUrl, {
status: fetchResult.status,
statusText: fetchResult.statusText,
});
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";