Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

126 lines
5.2 KiB
Java
Raw Normal View History

2025-06-04 12:34:14 +01:00
package stirling.software.SPDF.service;
2025-06-18 15:13:27 +01:00
import java.io.ByteArrayOutputStream;
2025-06-04 12:34:14 +01:00
import java.io.IOException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
2025-06-18 11:35:15 +01:00
import org.apache.pdfbox.pdmodel.PageMode;
2025-06-04 12:34:14 +01:00
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
2025-06-18 15:13:27 +01:00
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
2025-06-04 12:34:14 +01:00
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.PDFAttachmentUtils;
@Slf4j
@Service
public class PDFAttachmentService implements PDFAttachmentServiceInterface {
@Override
2025-06-18 15:13:27 +01:00
public byte[] addAttachment(
2025-06-04 12:34:14 +01:00
PDDocument document,
PDEmbeddedFilesNameTreeNode embeddedFilesTree,
List<MultipartFile> attachments)
throws IOException {
Map<String, PDComplexFileSpecification> existingNames;
try {
existingNames = embeddedFilesTree.getNames();
2025-06-18 15:13:27 +01:00
2025-06-04 12:34:14 +01:00
if (existingNames == null) {
log.debug("No existing embedded files found, creating new names map.");
existingNames = new HashMap<>();
}
log.debug("Embedded files: {}", existingNames.keySet());
} catch (IOException e) {
log.error("Could not retrieve existing embedded files", e);
throw e;
}
2025-06-18 15:13:27 +01:00
grantAccessPermissions(document);
2025-06-04 12:34:14 +01:00
final Map<String, PDComplexFileSpecification> existingEmbeddedFiles = existingNames;
2025-06-18 15:13:27 +01:00
2025-06-04 12:34:14 +01:00
attachments.forEach(
attachment -> {
2025-06-18 15:13:27 +01:00
String filename = attachment.getOriginalFilename();
2025-06-04 12:34:14 +01:00
try {
PDEmbeddedFile embeddedFile =
new PDEmbeddedFile(document, attachment.getInputStream());
embeddedFile.setSize((int) attachment.getSize());
embeddedFile.setCreationDate(new GregorianCalendar());
embeddedFile.setModDate(new GregorianCalendar());
String contentType = attachment.getContentType();
if (StringUtils.isNotBlank(contentType)) {
embeddedFile.setSubtype(contentType);
}
2025-06-18 15:13:27 +01:00
// Create attachments specification and associate embedded attachment with
// file
PDComplexFileSpecification fileSpecification =
new PDComplexFileSpecification();
fileSpecification.setFile(filename);
fileSpecification.setFileUnicode(filename);
fileSpecification.setFileDescription("Embedded attachment: " + filename);
2025-06-04 12:34:14 +01:00
embeddedFile.setFile(fileSpecification);
fileSpecification.setEmbeddedFile(embeddedFile);
fileSpecification.setEmbeddedFileUnicode(embeddedFile);
// Add to the existing files map
2025-06-18 15:13:27 +01:00
existingEmbeddedFiles.put(filename, fileSpecification);
2025-06-04 12:34:14 +01:00
2025-06-18 15:13:27 +01:00
log.info("Added attachment: {} ({} bytes)", filename, attachment.getSize());
2025-06-04 12:34:14 +01:00
} catch (IOException e) {
2025-06-18 15:13:27 +01:00
log.warn("Failed to create embedded file for attachment: {}", filename, e);
2025-06-04 12:34:14 +01:00
}
});
embeddedFilesTree.setNames(existingNames);
2025-06-18 11:35:15 +01:00
PDFAttachmentUtils.setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
2025-06-18 15:13:27 +01:00
ByteArrayOutputStream output = new ByteArrayOutputStream();
document.save(output);
return output.toByteArray();
2025-06-04 12:34:14 +01:00
}
private void grantAccessPermissions(PDDocument document) {
2025-06-18 15:13:27 +01:00
try {
AccessPermission currentPermissions = document.getCurrentAccessPermission();
currentPermissions.setCanAssembleDocument(true);
currentPermissions.setCanFillInForm(currentPermissions.canFillInForm());
currentPermissions.setCanModify(true);
currentPermissions.setCanPrint(true);
currentPermissions.setCanPrintFaithful(true);
// Ensure these permissions are enabled for embedded file access
currentPermissions.setCanExtractContent(true);
currentPermissions.setCanExtractForAccessibility(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);
}
2025-06-04 12:34:14 +01:00
}
}