Spaces:
Paused
Paused
| import { NextRequest, NextResponse } from "next/server"; | |
| import { STORAGE_KEY, internalAllowedWebDavEndpoints } from "../../../constant"; | |
| import { getServerSideConfig } from "@/app/config/server"; | |
| const config = getServerSideConfig(); | |
| const mergedAllowedWebDavEndpoints = [ | |
| ...internalAllowedWebDavEndpoints, | |
| ...config.allowedWebDavEndpoints, | |
| ].filter((domain) => Boolean(domain.trim())); | |
| const normalizeUrl = (url: string) => { | |
| try { | |
| return new URL(url); | |
| } catch (err) { | |
| return null; | |
| } | |
| }; | |
| 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); | |
| let endpoint = requestUrl.searchParams.get("endpoint"); | |
| let proxy_method = requestUrl.searchParams.get("proxy_method") || req.method; | |
| // Validate the endpoint to prevent potential SSRF attacks | |
| if ( | |
| !endpoint || | |
| !mergedAllowedWebDavEndpoints.some((allowedEndpoint) => { | |
| const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint); | |
| const normalizedEndpoint = normalizeUrl(endpoint as string); | |
| return ( | |
| normalizedEndpoint && | |
| normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname && | |
| normalizedEndpoint.pathname.startsWith( | |
| normalizedAllowedEndpoint.pathname, | |
| ) | |
| ); | |
| }) | |
| ) { | |
| return NextResponse.json( | |
| { | |
| error: true, | |
| msg: "Invalid endpoint", | |
| }, | |
| { | |
| status: 400, | |
| }, | |
| ); | |
| } | |
| if (!endpoint?.endsWith("/")) { | |
| endpoint += "/"; | |
| } | |
| const endpointPath = params.path.join("/"); | |
| const targetPath = `${endpoint}${endpointPath}`; | |
| // only allow MKCOL, GET, PUT | |
| if ( | |
| proxy_method !== "MKCOL" && | |
| proxy_method !== "GET" && | |
| proxy_method !== "PUT" | |
| ) { | |
| return NextResponse.json( | |
| { | |
| error: true, | |
| msg: "you are not allowed to request " + targetPath, | |
| }, | |
| { | |
| status: 403, | |
| }, | |
| ); | |
| } | |
| // for MKCOL request, only allow request ${folder} | |
| if (proxy_method === "MKCOL" && !targetPath.endsWith(folder)) { | |
| return NextResponse.json( | |
| { | |
| error: true, | |
| msg: "you are not allowed to request " + targetPath, | |
| }, | |
| { | |
| status: 403, | |
| }, | |
| ); | |
| } | |
| // for GET request, only allow request ending with fileName | |
| if (proxy_method === "GET" && !targetPath.endsWith(fileName)) { | |
| return NextResponse.json( | |
| { | |
| error: true, | |
| msg: "you are not allowed to request " + targetPath, | |
| }, | |
| { | |
| status: 403, | |
| }, | |
| ); | |
| } | |
| // for PUT request, only allow request ending with fileName | |
| if (proxy_method === "PUT" && !targetPath.endsWith(fileName)) { | |
| return NextResponse.json( | |
| { | |
| error: true, | |
| msg: "you are not allowed to request " + targetPath, | |
| }, | |
| { | |
| status: 403, | |
| }, | |
| ); | |
| } | |
| const targetUrl = targetPath; | |
| const method = proxy_method || req.method; | |
| const shouldNotHaveBody = ["get", "head"].includes( | |
| method?.toLowerCase() ?? "", | |
| ); | |
| const fetchOptions: RequestInit = { | |
| headers: { | |
| authorization: req.headers.get("authorization") ?? "", | |
| }, | |
| body: shouldNotHaveBody ? null : req.body, | |
| redirect: "manual", | |
| method, | |
| // @ts-ignore | |
| duplex: "half", | |
| }; | |
| let fetchResult; | |
| try { | |
| fetchResult = await fetch(targetUrl, fetchOptions); | |
| } finally { | |
| console.log( | |
| "[Any Proxy]", | |
| targetUrl, | |
| { | |
| method: method, | |
| }, | |
| { | |
| status: fetchResult?.status, | |
| statusText: fetchResult?.statusText, | |
| }, | |
| ); | |
| } | |
| return fetchResult; | |
| } | |
| export const PUT = handle; | |
| export const GET = handle; | |
| export const OPTIONS = handle; | |
| export const runtime = "edge"; | |