build(release): 加固 v0.1.0 桌面发布链路 (#3)
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getContentDispositionFilename } from "./contentDisposition";
|
||||
import { getContentDispositionFilename, getHttpHeaderString } from "./contentDisposition";
|
||||
|
||||
describe("getHttpHeaderString", () => {
|
||||
it("keeps string header values and joins string arrays", () => {
|
||||
expect(getHttpHeaderString("application/pdf")).toBe("application/pdf");
|
||||
expect(getHttpHeaderString(["attachment", "filename=protocol.pdf"])).toBe("attachment, filename=protocol.pdf");
|
||||
});
|
||||
|
||||
it("rejects non-string Axios header values", () => {
|
||||
expect(getHttpHeaderString(200)).toBeUndefined();
|
||||
expect(getHttpHeaderString(true)).toBeUndefined();
|
||||
expect(getHttpHeaderString({ value: "application/pdf" })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContentDispositionFilename", () => {
|
||||
it("prefers the UTF-8 filename even when the ASCII fallback appears first", () => {
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
export const getHttpHeaderString = (value: unknown): string | undefined => {
|
||||
if (typeof value === "string") return value;
|
||||
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
||||
return value.join(", ");
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getContentDispositionFilename = (header?: string | null): string | null => {
|
||||
if (!header) return null;
|
||||
const encoded = /filename\*\s*=\s*UTF-8''([^;]+)/i.exec(header)?.[1];
|
||||
|
||||
@@ -381,7 +381,7 @@ import StateLoading from "../../components/StateLoading.vue";
|
||||
import { onDesktopRefreshCurrentView } from "../../composables/useDesktopRefresh";
|
||||
import { openFileWithFeedback, pickFilesWithFeedback, saveFileWithFeedback } from "../../utils/fileTaskFeedback";
|
||||
import { getApiErrorMessage } from "../../utils/apiErrorMessage";
|
||||
import { getContentDispositionFilename } from "../../utils/contentDisposition";
|
||||
import { getContentDispositionFilename, getHttpHeaderString } from "../../utils/contentDisposition";
|
||||
import { prepareSaveFile } from "../../runtime";
|
||||
import { detectFilePreviewKind, ONLYOFFICE_FILE_EXTENSIONS } from "../../utils/officePreview";
|
||||
|
||||
@@ -803,7 +803,8 @@ const previewVersion = async (version: DocumentVersion) => {
|
||||
previewLoading.value = true;
|
||||
try {
|
||||
const response = await downloadDocumentVersion(version.id);
|
||||
const contentType = response.headers?.["content-type"] || version.mime_type || "application/octet-stream";
|
||||
const contentType =
|
||||
getHttpHeaderString(response.headers?.["content-type"]) || version.mime_type || "application/octet-stream";
|
||||
const blob = new Blob([response.data], { type: contentType });
|
||||
previewObjectUrl.value = URL.createObjectURL(blob);
|
||||
previewUrl.value = previewObjectUrl.value;
|
||||
@@ -823,8 +824,9 @@ const downloadVersion = async (version: DocumentVersion) => {
|
||||
return;
|
||||
}
|
||||
const response = await downloadDocumentVersion(version.id);
|
||||
const contentType = response.headers?.["content-type"] || "application/octet-stream";
|
||||
const filename = getContentDispositionFilename(response.headers?.["content-disposition"]) || suggestedName;
|
||||
const contentType = getHttpHeaderString(response.headers?.["content-type"]) || "application/octet-stream";
|
||||
const filename =
|
||||
getContentDispositionFilename(getHttpHeaderString(response.headers?.["content-disposition"])) || suggestedName;
|
||||
const blob = new Blob([response.data], { type: contentType });
|
||||
await saveFileWithFeedback({ suggestedName: filename, mimeType: contentType, data: blob }, {}, destination);
|
||||
} catch (e: any) { ElMessage.error(await getApiErrorMessage(e, TEXT.common.messages.downloadFailed)); }
|
||||
@@ -840,10 +842,11 @@ const openVersion = async (version: DocumentVersion) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const contentType = response.headers?.["content-type"] || "application/octet-stream";
|
||||
const filename = getContentDispositionFilename(response.headers?.["content-disposition"])
|
||||
|| getVersionFileName(version)
|
||||
|| `document-${version.version_no || version.id}.bin`;
|
||||
const contentType = getHttpHeaderString(response.headers?.["content-type"]) || "application/octet-stream";
|
||||
const filename =
|
||||
getContentDispositionFilename(getHttpHeaderString(response.headers?.["content-disposition"])) ||
|
||||
getVersionFileName(version) ||
|
||||
`document-${version.version_no || version.id}.bin`;
|
||||
await openFileWithFeedback({
|
||||
suggestedName: filename,
|
||||
mimeType: contentType,
|
||||
|
||||
@@ -474,6 +474,7 @@ import { isApiPermissionAllowed } from "../../utils/apiPermissionValue";
|
||||
import { displayDateTime } from "../../utils/display";
|
||||
import { getProjectRole, isSystemAdmin } from "../../utils/roles";
|
||||
import { useDrawerDirtyGuard } from "../../utils/drawerDirtyGuard";
|
||||
import { getContentDispositionFilename, getHttpHeaderString } from "../../utils/contentDisposition";
|
||||
|
||||
interface MonitoringIssueRow {
|
||||
id: string;
|
||||
@@ -937,13 +938,6 @@ const removeIssue = async (row: MonitoringIssueRow) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getFilename = (header?: string | null) => {
|
||||
if (!header) return null;
|
||||
const match = /filename\*=UTF-8''([^;]+)|filename="?([^\";]+)"?/i.exec(header);
|
||||
if (!match) return null;
|
||||
return decodeURIComponent(match[1] || match[2] || "");
|
||||
};
|
||||
|
||||
const handleExportExcel = async () => {
|
||||
const studyId = study.currentStudy?.id;
|
||||
if (!studyId || !canListIssues.value) return;
|
||||
@@ -975,8 +969,9 @@ const handleExportExcel = async () => {
|
||||
}
|
||||
|
||||
const response = await exportMonitoringVisitIssues(studyId, params);
|
||||
const contentType = response.headers?.["content-type"] || "application/octet-stream";
|
||||
const filename = getFilename(response.headers?.["content-disposition"]) || "监查访视问题.xlsx";
|
||||
const contentType = getHttpHeaderString(response.headers?.["content-type"]) || "application/octet-stream";
|
||||
const filename =
|
||||
getContentDispositionFilename(getHttpHeaderString(response.headers?.["content-disposition"])) || "监查访视问题.xlsx";
|
||||
const blob = new Blob([response.data], { type: contentType });
|
||||
await saveFileWithFeedback(
|
||||
{ suggestedName: filename, mimeType: contentType, data: blob },
|
||||
|
||||
Reference in New Issue
Block a user