mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-30 18:13:50 +08:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed68d9143 | ||
|
|
542d7e9ca6 | ||
|
|
35a33b20f9 | ||
|
|
74d8b1efa2 | ||
|
|
92e58d3de7 |
No files matched your search
+4
-1
@@ -2332,7 +2332,10 @@ func (s *Server) findingsExport(w http.ResponseWriter, r *http.Request) {
|
||||
stamp := now.Format("20060102-150405")
|
||||
setDownload := func(contentType, filename string) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
// Keep an ASCII fallback for older clients and provide the real UTF-8
|
||||
// name through RFC 5987. Browsers otherwise commonly decode Chinese
|
||||
// names using the platform legacy code page.
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, sanitizeFilename(filename), url.PathEscape(filename)))
|
||||
}
|
||||
|
||||
switch format {
|
||||
|
||||
@@ -78,7 +78,7 @@ function AuthenticatedImage({
|
||||
onClick={() => renderedSrc && setZoomed(true)}
|
||||
aria-label="放大查看报告截图"
|
||||
>
|
||||
<img {...props} src={renderedSrc} alt={alt || "报告截图,点击放大"} />
|
||||
<img {...props} src={renderedSrc} alt={alt || "报告截图,点击放大"} draggable={false} onDragStart={(event) => event.preventDefault()} />
|
||||
{timestamp}
|
||||
</button>
|
||||
<Dialog
|
||||
@@ -118,6 +118,8 @@ function AuthenticatedImage({
|
||||
<img
|
||||
src={renderedSrc}
|
||||
alt={alt || "报告截图"}
|
||||
draggable={false}
|
||||
onDragStart={(event) => event.preventDefault()}
|
||||
className="max-h-[90dvh] max-w-[90vw] rounded-md object-contain"
|
||||
/>
|
||||
{timestamp}
|
||||
|
||||
+22
-3
@@ -119,6 +119,24 @@ import type {
|
||||
SystemMetrics,
|
||||
} from "@/lib/types";
|
||||
|
||||
/**
|
||||
* Read a download name from Content-Disposition. RFC 5987's filename* is
|
||||
* preferred because it preserves UTF-8 names; older filename remains the
|
||||
* fallback for clients that do not understand it.
|
||||
*/
|
||||
export function downloadFilename(contentDisposition: string, fallback: string): string {
|
||||
const encoded = contentDisposition.match(/(?:^|;)\s*filename\*\s*=\s*UTF-8''([^;]+)/i)?.[1];
|
||||
if (encoded) {
|
||||
try {
|
||||
return decodeURIComponent(encoded.replace(/^"|"$/g, ""));
|
||||
} catch {
|
||||
// Fall through to the legacy token when a malformed header is received.
|
||||
}
|
||||
}
|
||||
const legacy = contentDisposition.match(/(?:^|;)\s*filename\s*=\s*(?:"([^"]*)"|([^;\s]+))/i);
|
||||
return (legacy?.[1] ?? legacy?.[2] ?? fallback).trim() || fallback;
|
||||
}
|
||||
|
||||
function getToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem("artex_token");
|
||||
@@ -534,6 +552,7 @@ export const api = {
|
||||
},
|
||||
workspaceDownload: async (path: string) => {
|
||||
let blob: Blob;
|
||||
let contentDisposition = "";
|
||||
if (MOCK) {
|
||||
blob = new Blob([`(demo)${path} 的下载内容示例。`], { type: "text/plain" });
|
||||
} else {
|
||||
@@ -542,12 +561,13 @@ export const api = {
|
||||
headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
||||
});
|
||||
if (!r.ok) throw new Error(`下载失败: ${r.status}`);
|
||||
contentDisposition = r.headers.get("Content-Disposition") ?? "";
|
||||
blob = await r.blob();
|
||||
}
|
||||
const objUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = objUrl;
|
||||
a.download = path.split("/").pop() || "download";
|
||||
a.download = downloadFilename(contentDisposition, path.split("/").pop() || "download");
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
@@ -703,8 +723,7 @@ export const api = {
|
||||
const blob = await r.blob();
|
||||
// 文件名优先取后端 Content-Disposition,取不到则用默认名。
|
||||
const disp = r.headers.get("Content-Disposition") ?? "";
|
||||
const m = disp.match(/filename="?([^"]+)"?/);
|
||||
const filename = m?.[1] ?? `findings-export`;
|
||||
const filename = downloadFilename(disp, `findings-export`);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
|
||||
Reference in new issue
Block a user