mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-23 07:55:07 +00:00
fixing access to attachments
This commit is contained in:
parent
279dc3f912
commit
da767eccbf
@ -19,11 +19,11 @@ import java.util.Map;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import lombok.Data;
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
import lombok.Getter;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import lombok.experimental.UtilityClass;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@ -35,12 +35,8 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
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
|
||||||
@ -1285,38 +1281,6 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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, "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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MIME header decoding functionality for RFC 2047 encoded headers - moved to constants
|
// MIME header decoding functionality for RFC 2047 encoded headers - moved to constants
|
||||||
|
|
||||||
private static String decodeMimeHeader(String encodedText) {
|
private static String decodeMimeHeader(String encodedText) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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");
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user