mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-10-31 22:33:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			168 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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";
 |