fixing access to attachments

This commit is contained in:
Dario Ghunney Ware 2025-06-17 13:25:18 +01:00
parent f191207245
commit 38ee18cbad
10 changed files with 67 additions and 50 deletions

View File

@ -42,10 +42,12 @@ import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.converters.EmlToPdfRequest; import stirling.software.common.model.api.converters.EmlToPdfRequest;
import static stirling.software.common.util.PDFAttachmentUtils.setCatalogViewerPreferences;
@Slf4j @Slf4j
@UtilityClass @UtilityClass
public class EmlToPdf { public class EmlToPdf {
private static final class StyleConstants { private static final class StyleConstants {
// Font and layout constants // Font and layout constants
static final int DEFAULT_FONT_SIZE = 12; static final int DEFAULT_FONT_SIZE = 12;
@ -1423,41 +1425,7 @@ public class EmlToPdf {
} }
} }
private static void setCatalogViewerPreferences(PDDocument document) { // MIME header decoding functionality for RFC 2047 encoded headers - moved to constants
try {
PDDocumentCatalog catalog = document.getDocumentCatalog();
if (catalog != null) {
// Get the catalog's COS dictionary to work with low-level PDF objects
COSDictionary catalogDict = catalog.getCOSObject();
// Set PageMode to UseAttachments - this is the standard PDF specification approach
// PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC,
// UseAttachments
catalogDict.setName(COSName.PAGE_MODE, "UseAttachments");
// Also set viewer preferences for better attachment viewing experience
COSDictionary viewerPrefs =
(COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES);
if (viewerPrefs == null) {
viewerPrefs = new COSDictionary();
catalogDict.setItem(COSName.VIEWER_PREFERENCES, viewerPrefs);
}
// Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support
// it
viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), "UseAttachments");
// Additional viewer preferences that may help with attachment display
viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true);
log.info(
"Set PDF PageMode to UseAttachments to automatically show attachments pane");
}
} catch (Exception e) {
// Log warning but don't fail the entire operation for viewer preferences
log.warn("Failed to set catalog viewer preferences for attachments", e);
}
}
private static String decodeMimeHeader(String encodedText) { private static String decodeMimeHeader(String encodedText) {
if (encodedText == null || encodedText.trim().isEmpty()) { if (encodedText == null || encodedText.trim().isEmpty()) {

View File

@ -0,0 +1,44 @@
package stirling.software.common.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PageMode;
@Slf4j
public class PDFAttachmentUtils {
public static void setCatalogViewerPreferences(PDDocument document) {
try {
PDDocumentCatalog catalog = document.getDocumentCatalog();
if (catalog != null) {
// Get the catalog's COS dictionary to work with low-level PDF objects
COSDictionary catalogDict = catalog.getCOSObject();
// Set PageMode to UseAttachments - this is the standard PDF specification approach
// PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC, UseAttachments
catalogDict.setName(COSName.PAGE_MODE, PageMode.USE_ATTACHMENTS.stringValue());
// Also set viewer preferences for better attachment viewing experience
COSDictionary viewerPrefs = (COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES);
if (viewerPrefs == null) {
viewerPrefs = new COSDictionary();
catalogDict.setItem(COSName.VIEWER_PREFERENCES, viewerPrefs);
}
// Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support it
viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), PageMode.USE_ATTACHMENTS.stringValue());
// Additional viewer preferences that may help with attachment display
viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true);
log.info("Set PDF PageMode to UseAttachments to automatically show attachments pane");
}
} catch (Exception e) {
// Log error but don't fail the entire operation for viewer preferences
log.error("Failed to set catalog viewer preferences for attachments", e);
}
}
}

View File

@ -16,12 +16,12 @@ import io.github.pixee.security.Filenames;
public class WebResponseUtils { public class WebResponseUtils {
public static ResponseEntity<byte[]> boasToWebResponse( public static ResponseEntity<byte[]> baosToWebResponse(
ByteArrayOutputStream baos, String docName) throws IOException { ByteArrayOutputStream baos, String docName) throws IOException {
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName);
} }
public static ResponseEntity<byte[]> boasToWebResponse( public static ResponseEntity<byte[]> baosToWebResponse(
ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);
} }
@ -44,7 +44,7 @@ public class WebResponseUtils {
headers.setContentType(mediaType); headers.setContentType(mediaType);
headers.setContentLength(bytes.length); headers.setContentLength(bytes.length);
String encodedDocName = String encodedDocName =
URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()) URLEncoder.encode(docName, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20"); .replaceAll("\\+", "%20");
headers.setContentDispositionFormData("attachment", encodedDocName); headers.setContentDispositionFormData("attachment", encodedDocName);
return new ResponseEntity<>(bytes, headers, HttpStatus.OK); return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
@ -64,6 +64,6 @@ public class WebResponseUtils {
// Close the document // Close the document
document.close(); document.close();
return boasToWebResponse(baos, docName); return baosToWebResponse(baos, docName);
} }
} }

View File

@ -25,7 +25,7 @@ public class WebResponseUtilsTest {
String docName = "sample.pdf"; String docName = "sample.pdf";
ResponseEntity<byte[]> responseEntity = ResponseEntity<byte[]> responseEntity =
WebResponseUtils.boasToWebResponse(baos, docName); WebResponseUtils.baosToWebResponse(baos, docName);
assertNotNull(responseEntity); assertNotNull(responseEntity);
assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); assertEquals(HttpStatus.OK, responseEntity.getStatusCode());

View File

@ -225,7 +225,7 @@ public class MergeController {
String mergedFileName = String mergedFileName =
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_merged_unsigned.pdf"; + "_merged_unsigned.pdf";
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.baosToWebResponse(
baos, mergedFileName); // Return the modified PDF baos, mergedFileName); // Return the modified PDF
} catch (Exception ex) { } catch (Exception ex) {

View File

@ -144,7 +144,7 @@ public class BlankPageController {
zos.close(); zos.close();
log.info("Returning ZIP file: {}", filename + "_processed.zip"); log.info("Returning ZIP file: {}", filename + "_processed.zip");
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.baosToWebResponse(
baos, filename + "_processed.zip", MediaType.APPLICATION_OCTET_STREAM); baos, filename + "_processed.zip", MediaType.APPLICATION_OCTET_STREAM);
} catch (IOException e) { } catch (IOException e) {

View File

@ -148,7 +148,7 @@ public class ExtractImagesController {
// Create ByteArrayResource from byte array // Create ByteArrayResource from byte array
byte[] zipContents = baos.toByteArray(); byte[] zipContents = baos.toByteArray();
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.baosToWebResponse(
baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM); baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM);
} }

View File

@ -118,7 +118,7 @@ public class PipelineController {
} }
zipOut.close(); zipOut.close();
log.info("Returning zipped file response..."); log.info("Returning zipped file response...");
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.baosToWebResponse(
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
} catch (Exception e) { } catch (Exception e) {
log.error("Error handling data: ", e); log.error("Error handling data: ", e);

View File

@ -205,7 +205,7 @@ public class CertSignController {
location, location,
reason, reason,
showLogo); showLogo);
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.baosToWebResponse(
baos, baos,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_signed.pdf"); + "_signed.pdf");

View File

@ -1,6 +1,8 @@
package stirling.software.SPDF.service; package stirling.software.SPDF.service;
import java.io.IOException; import java.io.IOException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -15,6 +17,8 @@ import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.PDFAttachmentUtils;
@Slf4j @Slf4j
@Service @Service
public class PDFAttachmentService implements PDFAttachmentServiceInterface { public class PDFAttachmentService implements PDFAttachmentServiceInterface {
@ -31,7 +35,7 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
existingNames = embeddedFilesTree.getNames(); existingNames = embeddedFilesTree.getNames();
if (existingNames == null) { if (existingNames == null) {
log.debug("No existing embedded files found, creating new names map."); log.debug("No existing embedded files found, creating new names map.");
existingNames = new java.util.HashMap<>(); existingNames = new HashMap<>();
} }
log.debug("Embedded files: {}", existingNames.keySet()); log.debug("Embedded files: {}", existingNames.keySet());
@ -55,9 +59,8 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
PDEmbeddedFile embeddedFile = PDEmbeddedFile embeddedFile =
new PDEmbeddedFile(document, attachment.getInputStream()); new PDEmbeddedFile(document, attachment.getInputStream());
embeddedFile.setSize((int) attachment.getSize()); embeddedFile.setSize((int) attachment.getSize());
embeddedFile.setCreationDate(new java.util.GregorianCalendar()); embeddedFile.setCreationDate(new GregorianCalendar());
embeddedFile.setFile(fileSpecification); embeddedFile.setModDate(new GregorianCalendar());
embeddedFile.setModDate(new java.util.GregorianCalendar());
// Set MIME type if available // Set MIME type if available
String contentType = attachment.getContentType(); String contentType = attachment.getContentType();
@ -66,10 +69,11 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
} }
// Associate embedded attachment with file specification // Associate embedded attachment with file specification
embeddedFile.setFile(fileSpecification);
fileSpecification.setEmbeddedFile(embeddedFile); fileSpecification.setEmbeddedFile(embeddedFile);
fileSpecification.setEmbeddedFileUnicode(embeddedFile); fileSpecification.setEmbeddedFileUnicode(embeddedFile);
// Add to the existing names map // Add to the existing files map
existingEmbeddedFiles.put( existingEmbeddedFiles.put(
attachment.getOriginalFilename(), fileSpecification); attachment.getOriginalFilename(), fileSpecification);
@ -89,6 +93,7 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
// Ensure document has proper access permissions for embedded files // Ensure document has proper access permissions for embedded files
grantAccessPermissions(document); grantAccessPermissions(document);
PDFAttachmentUtils.setCatalogViewerPreferences(document);
} }
private void grantAccessPermissions(PDDocument document) { private void grantAccessPermissions(PDDocument document) {