From 190baeebed3b21247112a41f9846f0f92c7a312a Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Fri, 20 Mar 2026 17:43:19 +0100 Subject: [PATCH] Use actual MIME type for trust center file exports The export endpoint was hardcoding application/pdf for all trust center files. Now the real MIME type from the stored file metadata is threaded through the service layer and returned as a data URI, so non-PDF files are handled correctly on the frontend. Watermarking is only applied when the file is actually a PDF. Signed-off-by: Bryan Frimin --- apps/trust/src/pages/DocumentPage.tsx | 25 ++++++++++++++++------ pkg/server/api/trust/v1/v1_resolver.go | 8 +++---- pkg/trust/trust_center_file_service.go | 29 ++++++++++++++------------ 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/apps/trust/src/pages/DocumentPage.tsx b/apps/trust/src/pages/DocumentPage.tsx index 9aa169b46..91aaf4733 100644 --- a/apps/trust/src/pages/DocumentPage.tsx +++ b/apps/trust/src/pages/DocumentPage.tsx @@ -164,8 +164,19 @@ type Props = { queryRef: PreloadedQuery; }; -function isPdfFilename(name: string): boolean { - return name.toLowerCase().endsWith(".pdf"); +function isPdfDataUri(dataUri: string): boolean { + return dataUri.startsWith("data:application/pdf;"); +} + +function extractBase64Data(dataUri: string): string { + const commaIndex = dataUri.indexOf(","); + if (commaIndex === -1) return dataUri; + return dataUri.substring(commaIndex + 1); +} + +function extractMimeType(dataUri: string): string { + const match = dataUri.match(/^data:([^;]+);/); + return match?.[1] ?? "application/octet-stream"; } function getNodeTitle(node: DocumentPageQueryType["response"]["node"]): string | undefined { @@ -276,7 +287,7 @@ export function DocumentPage({ queryRef }: Props) { variables: { input: { trustCenterFileId: node.id } }, onCompleted: (response, errors) => { if (onCompletedErrors(errors)) return; - if (isPdfFilename(node.name)) { + if (isPdfDataUri(response.exportTrustCenterFile.data)) { setPdfData(response.exportTrustCenterFile.data); } else { setFileData(response.exportTrustCenterFile.data); @@ -359,16 +370,18 @@ export function DocumentPage({ queryRef }: Props) { const isRequesting = isRequestingAccess || isRequestingFileAccess || isRequestingReportAccess; const hasRequested = node.access?.status === "REQUESTED"; - const isPdf = node.__typename === "Document" || node.__typename === "Report" || (node.__typename === "TrustCenterFile" && isPdfFilename(node.name)); + const isPdf = node.__typename === "Document" || node.__typename === "Report" || (node.__typename === "TrustCenterFile" && pdfData !== null); const handleDownload = () => { if (!fileData || !nodeTitle) return; - const byteCharacters = atob(fileData); + const base64 = extractBase64Data(fileData); + const mimeType = extractMimeType(fileData); + const byteCharacters = atob(base64); const byteNumbers = new Uint8Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } - const blob = new Blob([byteNumbers]); + const blob = new Blob([byteNumbers], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go index 009081d78..0d73fe2ef 100644 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ b/pkg/server/api/trust/v1/v1_resolver.go @@ -490,14 +490,14 @@ func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input type } if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - fileData, err := trustService.TrustCenterFiles.ExportFileWithoutWatermark(ctx, input.TrustCenterFileID) + fileData, mimeType, err := trustService.TrustCenterFiles.ExportFileWithoutWatermark(ctx, input.TrustCenterFileID) if err != nil { r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) return nil, gqlutils.Internal(ctx) } return &types.ExportTrustCenterFilePayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(fileData)), + Data: fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(fileData)), }, nil } @@ -519,14 +519,14 @@ func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input type return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this file") } - fileData, err := trustService.TrustCenterFiles.ExportFile(ctx, input.TrustCenterFileID, identity.EmailAddress) + fileData, mimeType, err := trustService.TrustCenterFiles.ExportFile(ctx, input.TrustCenterFileID, identity.EmailAddress) if err != nil { r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) return nil, gqlutils.Internal(ctx) } return &types.ExportTrustCenterFilePayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(fileData)), + Data: fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(fileData)), }, nil } diff --git a/pkg/trust/trust_center_file_service.go b/pkg/trust/trust_center_file_service.go index 5c9377a71..d0860c723 100644 --- a/pkg/trust/trust_center_file_service.go +++ b/pkg/trust/trust_center_file_service.go @@ -97,31 +97,34 @@ func (s *TrustCenterFileService) ExportFile( ctx context.Context, trustCenterFileID gid.GID, email mail.Addr, -) ([]byte, error) { - pdfData, err := s.exportFileData(ctx, trustCenterFileID) +) ([]byte, string, error) { + fileData, mimeType, err := s.exportFileData(ctx, trustCenterFileID) if err != nil { - return nil, fmt.Errorf("cannot export trust center file: %w", err) + return nil, "", fmt.Errorf("cannot export trust center file: %w", err) } - watermarkedPDF, err := watermarkpdf.AddConfidentialWithTimestamp(pdfData, email) - if err != nil { - return nil, fmt.Errorf("cannot add watermark to PDF: %w", err) + if mimeType == "application/pdf" { + watermarkedPDF, err := watermarkpdf.AddConfidentialWithTimestamp(fileData, email) + if err != nil { + return nil, "", fmt.Errorf("cannot add watermark to PDF: %w", err) + } + return watermarkedPDF, mimeType, nil } - return watermarkedPDF, nil + return fileData, mimeType, nil } func (s *TrustCenterFileService) ExportFileWithoutWatermark( ctx context.Context, trustCenterFileID gid.GID, -) ([]byte, error) { +) ([]byte, string, error) { return s.exportFileData(ctx, trustCenterFileID) } func (s *TrustCenterFileService) exportFileData( ctx context.Context, trustCenterFileID gid.GID, -) ([]byte, error) { +) ([]byte, string, error) { var trustCenterFile *coredata.TrustCenterFile var file *coredata.File @@ -139,7 +142,7 @@ func (s *TrustCenterFileService) exportFileData( return nil }) if err != nil { - return nil, err + return nil, "", err } result, err := s.svc.s3.GetObject(ctx, &s3.GetObjectInput{ @@ -147,14 +150,14 @@ func (s *TrustCenterFileService) exportFileData( Key: new(file.FileKey), }) if err != nil { - return nil, fmt.Errorf("cannot download file from S3: %w", err) + return nil, "", fmt.Errorf("cannot download file from S3: %w", err) } defer func() { _ = result.Body.Close() }() fileData, err := io.ReadAll(result.Body) if err != nil { - return nil, fmt.Errorf("cannot read file data: %w", err) + return nil, "", fmt.Errorf("cannot read file data: %w", err) } - return fileData, nil + return fileData, file.MimeType, nil }