This commit is contained in:
Dario Ghunney Ware 2025-06-19 11:39:11 +01:00
parent 5c5f2ecb59
commit d133997372
2 changed files with 115 additions and 12 deletions

View File

@ -14,6 +14,7 @@ import org.apache.pdfbox.pdmodel.PageMode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -35,6 +36,7 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
try { try {
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 HashMap<>(); existingNames = new HashMap<>();
@ -46,7 +48,9 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
throw e; throw e;
} }
grantAccessPermissions(document);
final Map<String, PDComplexFileSpecification> existingEmbeddedFiles = existingNames; final Map<String, PDComplexFileSpecification> existingEmbeddedFiles = existingNames;
attachments.forEach( attachments.forEach(
attachment -> { attachment -> {
String filename = attachment.getOriginalFilename(); String filename = attachment.getOriginalFilename();
@ -58,7 +62,6 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
embeddedFile.setCreationDate(new GregorianCalendar()); embeddedFile.setCreationDate(new GregorianCalendar());
embeddedFile.setModDate(new GregorianCalendar()); embeddedFile.setModDate(new GregorianCalendar());
String contentType = attachment.getContentType(); String contentType = attachment.getContentType();
if (StringUtils.isNotBlank(contentType)) { if (StringUtils.isNotBlank(contentType)) {
embeddedFile.setSubtype(contentType); embeddedFile.setSubtype(contentType);
} }
@ -84,7 +87,6 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
}); });
embeddedFilesTree.setNames(existingNames); embeddedFilesTree.setNames(existingNames);
grantAccessPermissions(document);
PDFAttachmentUtils.setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS); PDFAttachmentUtils.setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
document.save(output); document.save(output);
@ -93,6 +95,7 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
} }
private void grantAccessPermissions(PDDocument document) { private void grantAccessPermissions(PDDocument document) {
try {
AccessPermission currentPermissions = document.getCurrentAccessPermission(); AccessPermission currentPermissions = document.getCurrentAccessPermission();
currentPermissions.setCanAssembleDocument(true); currentPermissions.setCanAssembleDocument(true);
@ -105,5 +108,18 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
currentPermissions.setCanExtractContent(true); currentPermissions.setCanExtractContent(true);
currentPermissions.setCanExtractForAccessibility(true); currentPermissions.setCanExtractForAccessibility(true);
currentPermissions.setCanModifyAnnotations(true); currentPermissions.setCanModifyAnnotations(true);
var protectionPolicy = new StandardProtectionPolicy(null, null, currentPermissions);
if (!document.isAllSecurityToBeRemoved()) {
document.setAllSecurityToBeRemoved(true);
}
document.protect(protectionPolicy);
ByteArrayOutputStream output = new ByteArrayOutputStream();
document.save(output);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
} }

View File

@ -7,6 +7,8 @@ import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -126,4 +128,89 @@ class PDFAttachmentServiceTest {
verify(embeddedFilesTree).setNames(anyMap()); verify(embeddedFilesTree).setNames(anyMap());
} }
} }
@Test
void addAttachmentToPDF_WithProtectedDocument() throws IOException {
try (var document = new PDDocument()) {
// Create a document with restricted permissions (this simulates an encrypted/protected document)
AccessPermission ap = new AccessPermission();
ap.setCanExtractContent(false); // Restrict content extraction initially
var spp = new StandardProtectionPolicy("owner", "user", ap);
document.protect(spp);
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
var existingNames = new HashMap<String, PDComplexFileSpecification>();
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
when(attachments.get(0).getInputStream()).thenReturn(
new ByteArrayInputStream("Test content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(12L);
when(attachments.get(0).getContentType()).thenReturn("text/plain");
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
assertNotNull(result);
assertTrue(result.length > 0);
verify(embeddedFilesTree).setNames(anyMap());
}
}
@Test
void addAttachmentToPDF_WithRestrictedPermissions() throws IOException {
try (var document = new PDDocument()) {
// Create a document with very restricted permissions that should block permission changes
AccessPermission ap = new AccessPermission();
ap.setCanModify(false);
ap.setCanAssembleDocument(false);
ap.setCanExtractContent(false);
var spp = new StandardProtectionPolicy("owner", "user", ap);
document.protect(spp);
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
var existingNames = new HashMap<String, PDComplexFileSpecification>();
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
when(attachments.get(0).getInputStream()).thenReturn(
new ByteArrayInputStream("Test content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(12L);
when(attachments.get(0).getContentType()).thenReturn("text/plain");
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
assertNotNull(result);
assertTrue(result.length > 0);
verify(embeddedFilesTree).setNames(anyMap());
}
}
@Test
void addAttachmentToPDF_WithNonEncryptedDocument() throws IOException {
try (var document = new PDDocument()) {
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
var attachments = List.of(mock(MultipartFile.class));
var existingNames = new HashMap<String, PDComplexFileSpecification>();
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
when(attachments.get(0).getInputStream()).thenReturn(
new ByteArrayInputStream("Test content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(12L);
when(attachments.get(0).getContentType()).thenReturn("text/plain");
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
assertNotNull(result);
assertTrue(result.length > 0);
// Verify permissions are set correctly for non-encrypted documents
AccessPermission permissions = document.getCurrentAccessPermission();
assertTrue(permissions.canExtractContent());
assertTrue(permissions.canExtractForAccessibility());
assertTrue(permissions.canModifyAnnotations());
verify(embeddedFilesTree).setNames(anyMap());
}
}
} }