-
Notifications
You must be signed in to change notification settings - Fork 60k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Shiwoslj patch 1 #5905
Shiwoslj patch 1 #5905
Changes from all commits
66e42a0
08c2c43
6b8230a
f01685d
a901d33
05a125b
3d610ce
46136a9
cb7f6fe
7c1dc9b
abba5c8
564fcdc
a2083ca
1e1c1f3
3bac81f
460d6af
5b2c860
7930996
5af5aa5
215fb94
8625d8a
9cca606
1314113
946b1dd
a97676a
7035242
a3964dc
471762f
e203113
f4f4b2a
8bc3b37
a0199a9
121e351
6d2d0a7
9457855
dd0d4d1
cfd248a
99cfa5f
3357911
b6f2223
f2ea00a
733c80c
9920834
05ddc4a
41add66
9fab531
7ac2f69
10b5637
385db10
c576c18
1b5d345
06444ac
25a073e
241f5af
bd6be12
37fe9d6
3838c29
23a2c67
7e623e8
a637926
5faf537
7f3ccf8
a50a4fd
e4e34c0
4f99f4a
2a81b64
dd88585
5cc5f16
059d4e1
c9c2918
6d65bda
919fdca
8813743
9403aac
07846e9
ca77458
05a96d0
33942d3
09f6a6f
98d75da
f73f66a
a8ac114
e2b3a4d
49d1279
2a5f3e7
fe0ba31
b988711
68ffd1f
1b9bf62
4c378ec
50fcd3a
4efd103
8ba2140
aaecb12
d9cd0bb
0842fe3
671f6b4
add661c
4dd92be
03ec6bc
e6222f2
1f1289e
eac6dab
b4866a5
98020c7
0c94ab9
d1e42f7
faeba81
28a4706
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,167 +1,187 @@ | ||||||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
// 配置常量 | ||||||||||||||||||||||||||||||||||||||
const ALLOWED_METHODS = ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PROPFIND", "MKCOL"]; | ||||||||||||||||||||||||||||||||||||||
const ALLOWED_HEADERS = [ | ||||||||||||||||||||||||||||||||||||||
"authorization", | ||||||||||||||||||||||||||||||||||||||
"content-type", | ||||||||||||||||||||||||||||||||||||||
"accept", | ||||||||||||||||||||||||||||||||||||||
"depth", | ||||||||||||||||||||||||||||||||||||||
"destination", | ||||||||||||||||||||||||||||||||||||||
"overwrite", | ||||||||||||||||||||||||||||||||||||||
"content-length" | ||||||||||||||||||||||||||||||||||||||
]; | ||||||||||||||||||||||||||||||||||||||
const CORS_HEADERS = { | ||||||||||||||||||||||||||||||||||||||
"Access-Control-Allow-Origin": "*", | ||||||||||||||||||||||||||||||||||||||
"Access-Control-Allow-Methods": ALLOWED_METHODS.join(","), | ||||||||||||||||||||||||||||||||||||||
"Access-Control-Allow-Headers": ALLOWED_HEADERS.join(", "), | ||||||||||||||||||||||||||||||||||||||
"Access-Control-Max-Age": "86400", | ||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||
const TIMEOUT = 30000; // 30 seconds | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
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, | ||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
// WebDAV 服务器端点配置 | ||||||||||||||||||||||||||||||||||||||
const ENDPOINT = process.env.WEBDAV_ENDPOINT || "http://localhost:8080"; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
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, | ||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
export const runtime = "edge"; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
// 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, | ||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
// 路径拼接函数 | ||||||||||||||||||||||||||||||||||||||
function joinPaths(...parts: string[]): string { | ||||||||||||||||||||||||||||||||||||||
return parts | ||||||||||||||||||||||||||||||||||||||
.map(part => part.replace(/^\/+|\/+$/g, '')) | ||||||||||||||||||||||||||||||||||||||
.filter(Boolean) | ||||||||||||||||||||||||||||||||||||||
.join('/'); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+28
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add path traversal protection to joinPaths function The current implementation doesn't prevent directory traversal attacks using Add path normalization: function joinPaths(...parts: string[]): string {
+ // Normalize and validate path segments
+ const normalizedParts = parts.map(part => {
+ const cleaned = part.replace(/^\/+|\/+$/g, '');
+ if (cleaned.includes('../') || cleaned === '..') {
+ throw new Error('Path traversal not allowed');
+ }
+ return cleaned;
+ });
+
return parts
- .map(part => part.replace(/^\/+|\/+$/g, ''))
.filter(Boolean)
.join('/');
}
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
// 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, | ||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
// 重试机制 | ||||||||||||||||||||||||||||||||||||||
async function makeRequest(url: string, options: RequestInit, retries = 3) { | ||||||||||||||||||||||||||||||||||||||
const controller = new AbortController(); | ||||||||||||||||||||||||||||||||||||||
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT); | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
for (let i = 0; i < retries; i++) { | ||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||
const response = await fetch(url, { | ||||||||||||||||||||||||||||||||||||||
...options, | ||||||||||||||||||||||||||||||||||||||
signal: controller.signal | ||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||
clearTimeout(timeoutId); | ||||||||||||||||||||||||||||||||||||||
return response; | ||||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||||
console.error(`Attempt ${i + 1} failed:`, error); | ||||||||||||||||||||||||||||||||||||||
if (error.name === 'AbortError') { | ||||||||||||||||||||||||||||||||||||||
throw new Error('Request Timeout'); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
if (i === retries - 1) throw error; | ||||||||||||||||||||||||||||||||||||||
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
throw new Error('Max retries reached'); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
export async function handler( | ||||||||||||||||||||||||||||||||||||||
req: NextRequest, | ||||||||||||||||||||||||||||||||||||||
{ params }: { params: { path: string[] } } | ||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||
fetchResult = await fetch(targetUrl, fetchOptions); | ||||||||||||||||||||||||||||||||||||||
} finally { | ||||||||||||||||||||||||||||||||||||||
console.log( | ||||||||||||||||||||||||||||||||||||||
"[Any Proxy]", | ||||||||||||||||||||||||||||||||||||||
targetUrl, | ||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||
method: method, | ||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||
status: fetchResult?.status, | ||||||||||||||||||||||||||||||||||||||
statusText: fetchResult?.statusText, | ||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||
const method = req.method; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
console.log(`[Proxy Request] ${method} ${req.url}`); | ||||||||||||||||||||||||||||||||||||||
console.log("Request Headers:", Object.fromEntries(req.headers)); | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+67
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid logging full request headers to prevent sensitive data exposure Logging complete request headers may inadvertently expose sensitive information such as authentication tokens or personal data. It's important to sanitize or omit sensitive headers to enhance security. Apply this diff to sanitize or remove sensitive headers from logs: // Log request method and URL
console.log(`[Proxy Request] ${method} ${req.url}`);
- console.log("Request Headers:", Object.fromEntries(req.headers));
+ // Optionally, log non-sensitive headers if necessary
+ // const loggedHeaders = { ... };
+ // console.log("Request Headers:", loggedHeaders);
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if (method === "OPTIONS") { | ||||||||||||||||||||||||||||||||||||||
return new NextResponse(null, { | ||||||||||||||||||||||||||||||||||||||
status: 204, | ||||||||||||||||||||||||||||||||||||||
headers: CORS_HEADERS, | ||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if (!ALLOWED_METHODS.includes(method)) { | ||||||||||||||||||||||||||||||||||||||
return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||
{ error: "Method Not Allowed" }, | ||||||||||||||||||||||||||||||||||||||
{ status: 405, headers: CORS_HEADERS } | ||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
// 构建目标 URL(使用自定义的 joinPaths 函数替代 path.join) | ||||||||||||||||||||||||||||||||||||||
const targetUrlObj = new URL(ENDPOINT); | ||||||||||||||||||||||||||||||||||||||
const pathSegments = params.path || []; | ||||||||||||||||||||||||||||||||||||||
targetUrlObj.pathname = joinPaths(targetUrlObj.pathname, ...pathSegments); | ||||||||||||||||||||||||||||||||||||||
const targetUrl = targetUrlObj.toString(); | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
// 其余代码保持不变... | ||||||||||||||||||||||||||||||||||||||
const headers = new Headers(); | ||||||||||||||||||||||||||||||||||||||
req.headers.forEach((value, key) => { | ||||||||||||||||||||||||||||||||||||||
if (ALLOWED_HEADERS.includes(key.toLowerCase())) { | ||||||||||||||||||||||||||||||||||||||
headers.set(key, value); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const depth = req.headers.get('depth'); | ||||||||||||||||||||||||||||||||||||||
if (depth) { | ||||||||||||||||||||||||||||||||||||||
headers.set('depth', depth); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const authHeader = req.headers.get('authorization'); | ||||||||||||||||||||||||||||||||||||||
if (authHeader) { | ||||||||||||||||||||||||||||||||||||||
headers.set('authorization', authHeader); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
let requestBody: BodyInit | null = null; | ||||||||||||||||||||||||||||||||||||||
if (["POST", "PUT"].includes(method)) { | ||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||
const contentLength = req.headers.get('content-length'); | ||||||||||||||||||||||||||||||||||||||
if (contentLength && parseInt(contentLength) > 10 * 1024 * 1024) { | ||||||||||||||||||||||||||||||||||||||
requestBody = req.body; | ||||||||||||||||||||||||||||||||||||||
headers.set('transfer-encoding', 'chunked'); | ||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+114
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid manually setting 'transfer-encoding' header Manually setting the Apply this diff to remove the manual header setting: requestBody = req.body;
- headers.set('transfer-encoding', 'chunked');
|
||||||||||||||||||||||||||||||||||||||
requestBody = await req.blob(); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+111
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve large file handling configuration The large file threshold is hardcoded and the handling logic could be improved. Consider these improvements: +const MAX_MEMORY_FILE_SIZE = 10 * 1024 * 1024; // 10MB
+
const contentLength = req.headers.get('content-length');
-if (contentLength && parseInt(contentLength) > 10 * 1024 * 1024) {
+const size = contentLength ? parseInt(contentLength) : 0;
+if (size > MAX_MEMORY_FILE_SIZE) {
requestBody = req.body;
- headers.set('transfer-encoding', 'chunked');
+ // Let the underlying fetch implementation handle transfer encoding
} else {
- requestBody = await req.blob();
+ // For smaller files, use ArrayBuffer for better memory efficiency
+ requestBody = await req.arrayBuffer();
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||||
console.error("[Request Body Error]", error); | ||||||||||||||||||||||||||||||||||||||
return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||
{ error: "Invalid Request Body" }, | ||||||||||||||||||||||||||||||||||||||
{ status: 400, headers: CORS_HEADERS } | ||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+108
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add content type validation for uploads The current implementation doesn't validate content types for uploaded files. Add content type validation: let requestBody: BodyInit | null = null;
if (["POST", "PUT"].includes(method)) {
try {
+ const contentType = req.headers.get('content-type');
+ if (contentType) {
+ // Add your allowed content types
+ const allowedTypes = ['application/octet-stream', 'text/plain'];
+ if (!allowedTypes.some(type => contentType.includes(type))) {
+ return NextResponse.json(
+ { error: "Unsupported content type" },
+ { status: 415, headers: CORS_HEADERS }
+ );
+ }
+ }
const contentLength = req.headers.get('content-length');
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const fetchOptions: RequestInit = { | ||||||||||||||||||||||||||||||||||||||
method, | ||||||||||||||||||||||||||||||||||||||
headers, | ||||||||||||||||||||||||||||||||||||||
body: requestBody, | ||||||||||||||||||||||||||||||||||||||
redirect: "manual", | ||||||||||||||||||||||||||||||||||||||
cache: 'no-store', | ||||||||||||||||||||||||||||||||||||||
next: { | ||||||||||||||||||||||||||||||||||||||
revalidate: 0 | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
let response: Response; | ||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||
console.log(`[Proxy Forward] ${method} ${targetUrl}`); | ||||||||||||||||||||||||||||||||||||||
response = await makeRequest(targetUrl, fetchOptions); | ||||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||||
console.error("[Proxy Error]", error); | ||||||||||||||||||||||||||||||||||||||
const status = error.message === 'Request Timeout' ? 504 : 500; | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use error types instead of messages for error handling logic Relying on error messages for control flow is fragile and can lead to bugs if messages change. Using custom error classes or properties provides a more reliable way to handle specific errors. Consider defining a custom error class for timeouts and updating the error handling: +// Define a custom error class
+class RequestTimeoutError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = 'RequestTimeoutError';
+ }
+}
// In makeRequest function
if (error.name === 'AbortError') {
- throw new Error('Request Timeout');
+ throw new RequestTimeoutError('Request Timeout');
}
// In the handler function
try {
// ...
} catch (error) {
console.error("[Proxy Error]", error);
- const status = error.message === 'Request Timeout' ? 504 : 500;
+ const status = error instanceof RequestTimeoutError ? 504 : 500;
return NextResponse.json(
{ error: "Internal Server Error" },
{ status, headers: CORS_HEADERS }
);
}
|
||||||||||||||||||||||||||||||||||||||
return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||
{ error: error.message || "Internal Server Error" }, | ||||||||||||||||||||||||||||||||||||||
{ status, headers: CORS_HEADERS } | ||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+146
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent exposure of internal error messages in responses Returning internal error messages to clients can expose sensitive information. It's safer to return a generic error message and log the detailed error server-side. Apply this diff to use a generic error message: return NextResponse.json(
- { error: error.message || "Internal Server Error" },
+ { error: "Internal Server Error" },
{ status, headers: CORS_HEADERS }
); Ensure that detailed errors are logged internally: console.error("[Proxy Error]", error);
+// Detailed error logged above 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const responseHeaders = new Headers(response.headers); | ||||||||||||||||||||||||||||||||||||||
["set-cookie", "server"].forEach((header) => { | ||||||||||||||||||||||||||||||||||||||
responseHeaders.delete(header); | ||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Object.entries({ | ||||||||||||||||||||||||||||||||||||||
...CORS_HEADERS, | ||||||||||||||||||||||||||||||||||||||
"Content-Security-Policy": "upgrade-insecure-requests", | ||||||||||||||||||||||||||||||||||||||
"Strict-Transport-Security": "max-age=31536000; includeSubDomains" | ||||||||||||||||||||||||||||||||||||||
}).forEach(([key, value]) => { | ||||||||||||||||||||||||||||||||||||||
responseHeaders.set(key, value); | ||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
console.log(`[Proxy Response] ${response.status} ${response.statusText}`); | ||||||||||||||||||||||||||||||||||||||
console.log("Response Headers:", Object.fromEntries(responseHeaders)); | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+164
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid logging full response headers to prevent sensitive data exposure Logging all response headers could expose sensitive information. It's advisable to sanitize or avoid logging headers that may contain confidential data. Apply this diff to sanitize or remove sensitive headers from logs: // Log response status
console.log(`[Proxy Response] ${response.status} ${response.statusText}`);
- console.log("Response Headers:", Object.fromEntries(responseHeaders));
+ // Optionally, log non-sensitive headers if necessary
+ // const loggedHeaders = { ... };
+ // console.log("Response Headers:", loggedHeaders);
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
return new NextResponse(response.body, { | ||||||||||||||||||||||||||||||||||||||
status: response.status, | ||||||||||||||||||||||||||||||||||||||
statusText: response.statusText, | ||||||||||||||||||||||||||||||||||||||
headers: responseHeaders, | ||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||||
console.error("[Global Error]", error); | ||||||||||||||||||||||||||||||||||||||
return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||
{ error: "Internal Server Error", details: error.message }, | ||||||||||||||||||||||||||||||||||||||
{ status: 500, headers: CORS_HEADERS } | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+175
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent exposure of internal error details in responses Including Apply this diff to remove internal error details from the response: return NextResponse.json(
- { error: "Internal Server Error", details: error.message },
+ { error: "Internal Server Error" },
{ status: 500, headers: CORS_HEADERS }
); Ensure that the error is logged for debugging purposes: console.error("[Global Error]", error);
+// Detailed error logged above
|
||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
return fetchResult; | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
export const PUT = handle; | ||||||||||||||||||||||||||||||||||||||
export const GET = handle; | ||||||||||||||||||||||||||||||||||||||
export const OPTIONS = handle; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
export const runtime = "edge"; | ||||||||||||||||||||||||||||||||||||||
export const GET = handler; | ||||||||||||||||||||||||||||||||||||||
export const POST = handler; | ||||||||||||||||||||||||||||||||||||||
export const PUT = handler; | ||||||||||||||||||||||||||||||||||||||
export const DELETE = handler; | ||||||||||||||||||||||||||||||||||||||
export const OPTIONS = handler; | ||||||||||||||||||||||||||||||||||||||
export const PROPFIND = handler; | ||||||||||||||||||||||||||||||||||||||
export const MKCOL = handler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restrict CORS configuration for production environments
The current CORS configuration allows requests from any origin (
"*"
), which could expose your WebDAV endpoint to unauthorized access.Consider implementing environment-specific CORS: