|
|
@@ -18,6 +18,18 @@ export function getAuthToken(): string | null {
|
|
|
return authToken;
|
|
|
}
|
|
|
|
|
|
+function parseContentDispositionFilename(header: string | null): string | null {
|
|
|
+ if (!header) return null;
|
|
|
+ // RFC 5987: filename*=utf-8''percent-encoded-name
|
|
|
+ const rfc5987Match = header.match(/filename\*=(?:UTF-8|utf-8)''(.+?)(?:;|$)/);
|
|
|
+ if (rfc5987Match) {
|
|
|
+ try { return decodeURIComponent(rfc5987Match[1]); } catch { /* fall through */ }
|
|
|
+ }
|
|
|
+ // Standard: filename="name" or filename=name
|
|
|
+ const standardMatch = header.match(/filename="?([^";\n]+)"?/);
|
|
|
+ return standardMatch?.[1] || null;
|
|
|
+}
|
|
|
+
|
|
|
async function request<T>(
|
|
|
endpoint: string,
|
|
|
options: RequestInit = {}
|
|
|
@@ -2019,7 +2031,7 @@ export const api = {
|
|
|
request<{ message: string; auth_enabled: boolean }>('/auth/disable', {
|
|
|
method: 'POST',
|
|
|
}),
|
|
|
-
|
|
|
+
|
|
|
// Advanced Authentication
|
|
|
testSMTP: (data: TestSMTPRequest) =>
|
|
|
request<TestSMTPResponse>('/auth/smtp/test', {
|
|
|
@@ -2263,8 +2275,7 @@ export const api = {
|
|
|
throw new Error(error.detail || `HTTP ${response.status}`);
|
|
|
}
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
|
- const filenameMatch = disposition?.match(/filename="?([^";\n]+)"?/);
|
|
|
- const filename = filenameMatch?.[1] || path.split('/').pop() || 'download';
|
|
|
+ const filename = parseContentDispositionFilename(disposition) || path.split('/').pop() || 'download';
|
|
|
const blob = await response.blob();
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
@@ -2464,8 +2475,7 @@ export const api = {
|
|
|
throw new Error(error.detail || `HTTP ${response.status}`);
|
|
|
}
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
|
- const filenameMatch = disposition?.match(/filename="?([^";\n]+)"?/);
|
|
|
- const downloadFilename = filenameMatch?.[1] || filename || `archive_${id}.3mf`;
|
|
|
+ const downloadFilename = parseContentDispositionFilename(disposition) || filename || `archive_${id}.3mf`;
|
|
|
const blob = await response.blob();
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
@@ -2605,8 +2615,7 @@ export const api = {
|
|
|
throw new Error(error.detail || `HTTP ${response.status}`);
|
|
|
}
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
|
- const filenameMatch = disposition?.match(/filename="?([^";\n]+)"?/);
|
|
|
- const filename = filenameMatch?.[1] || `source_${archiveId}.3mf`;
|
|
|
+ const filename = parseContentDispositionFilename(disposition) || `source_${archiveId}.3mf`;
|
|
|
const blob = await response.blob();
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
@@ -2655,8 +2664,7 @@ export const api = {
|
|
|
throw new Error(error.detail || `HTTP ${response.status}`);
|
|
|
}
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
|
- const filenameMatch = disposition?.match(/filename="?([^";\n]+)"?/);
|
|
|
- const filename = filenameMatch?.[1] || `archive_${archiveId}.f3d`;
|
|
|
+ const filename = parseContentDispositionFilename(disposition) || `archive_${archiveId}.f3d`;
|
|
|
const blob = await response.blob();
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
@@ -3539,8 +3547,7 @@ export const api = {
|
|
|
throw new Error(error.detail || `HTTP ${response.status}`);
|
|
|
}
|
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
|
- const filenameMatch = contentDisposition?.match(/filename="(.+)"/);
|
|
|
- const filename = filenameMatch?.[1] || `project_${projectId}.zip`;
|
|
|
+ const filename = parseContentDispositionFilename(contentDisposition) || `project_${projectId}.zip`;
|
|
|
const blob = await response.blob();
|
|
|
return { blob, filename };
|
|
|
},
|
|
|
@@ -3668,8 +3675,7 @@ export const api = {
|
|
|
throw new Error(error.detail || `HTTP ${response.status}`);
|
|
|
}
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
|
- const filenameMatch = disposition?.match(/filename="?([^";\n]+)"?/);
|
|
|
- const downloadFilename = filenameMatch?.[1] || filename || `file_${id}`;
|
|
|
+ const downloadFilename = parseContentDispositionFilename(disposition) || filename || `file_${id}`;
|
|
|
const blob = await response.blob();
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
@@ -4348,8 +4354,7 @@ export const supportApi = {
|
|
|
}
|
|
|
// Get filename from Content-Disposition header or use default
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
|
- const filenameMatch = disposition?.match(/filename=(.+)/);
|
|
|
- const filename = filenameMatch ? filenameMatch[1] : 'bambuddy-support.zip';
|
|
|
+ const filename = parseContentDispositionFilename(disposition) || 'bambuddy-support.zip';
|
|
|
|
|
|
// Download the blob
|
|
|
const blob = await response.blob();
|