From 0f5c549fb439c79a4960f475ec52690ea86b9df8 Mon Sep 17 00:00:00 2001
From: Dario Ghunney Ware <dariogware@gmail.com>
Date: Thu, 12 Jun 2025 18:16:45 +0100
Subject: [PATCH] fixing routing to page

---
 .../api/misc/AttachmentsController.java       | 69 +++++-----------
 .../SPDF/service/PDFAttachmentService.java    | 81 +++++++++++++++++++
 .../PDFAttachmentServiceInterface.java        | 17 ++++
 .../main/resources/messages_en_GB.properties  | 14 ++++
 .../main/resources/messages_en_US.properties  |  3 +-
 .../templates/fragments/navElements.html      |  5 +-
 .../resources/templates/misc/attachments.html | 42 ++++++++++
 7 files changed, 179 insertions(+), 52 deletions(-)
 create mode 100644 stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java
 create mode 100644 stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentServiceInterface.java
 create mode 100644 stirling-pdf/src/main/resources/templates/misc/attachments.html

diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java
index 90a176df4..8dc548952 100644
--- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java
+++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java
@@ -1,6 +1,5 @@
 package stirling.software.SPDF.controller.api.misc;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.List;
 
@@ -8,8 +7,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
 import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
 import org.apache.pdfbox.pdmodel.PageMode;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
@@ -21,22 +18,26 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 
+import stirling.software.SPDF.service.PDFAttachmentServiceInterface;
 import stirling.software.common.service.CustomPDFDocumentFactory;
 import stirling.software.common.util.WebResponseUtils;
 
+@Slf4j
 @RestController
 @RequiredArgsConstructor
 @RequestMapping("/api/v1/misc")
 @Tag(name = "Misc", description = "Miscellaneous APIs")
-@Slf4j
 public class AttachmentsController {
 
     private final CustomPDFDocumentFactory pdfDocumentFactory;
 
+    private final PDFAttachmentServiceInterface pdfAttachmentService;
+
     @PostMapping(consumes = "multipart/form-data", value = "/add-attachments")
     @Operation(
             summary = "Add attachments to PDF",
-            description = "This endpoint adds embedded files (attachments) to a PDF and sets the PageMode to UseAttachments to make them visible. Input:PDF + Files Output:PDF Type:MISO")
+            description =
+                    "This endpoint adds embedded files (attachments) to a PDF and sets the PageMode to UseAttachments to make them visible. Input:PDF + Files Output:PDF Type:MISO")
     public ResponseEntity<byte[]> addAttachments(
             @RequestParam("fileInput") MultipartFile pdfFile,
             @RequestParam("attachments") List<MultipartFile> attachments)
@@ -44,23 +45,20 @@ public class AttachmentsController {
 
         // Load the PDF document
         PDDocument document = pdfDocumentFactory.load(pdfFile, true);
-        
+
         // Get or create the document catalog
         PDDocumentCatalog catalog = document.getDocumentCatalog();
-        
+
         // Create embedded files name tree if it doesn't exist
         PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();
+
         if (efTree == null) {
             efTree = new PDEmbeddedFilesNameTreeNode();
             catalog.getNames().setEmbeddedFiles(efTree);
         }
 
-        // Add each attachment
-        for (MultipartFile attachment : attachments) {
-            if (attachment != null && !attachment.isEmpty()) {
-                addEmbeddedFile(document, efTree, attachment);
-            }
-        }
+        // Add attachments
+        pdfAttachmentService.addAttachment(document, efTree, attachments);
 
         // Set PageMode to UseAttachments to show the attachments panel
         catalog.setPageMode(PageMode.USE_ATTACHMENTS);
@@ -69,23 +67,24 @@ public class AttachmentsController {
         return WebResponseUtils.pdfDocToWebResponse(
                 document,
                 Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
-                        .replaceFirst("[.][^.]+$", "") + "_with_attachments.pdf");
+                                .replaceFirst("[.][^.]+$", "")
+                        + "_with_attachments.pdf");
     }
 
     @PostMapping(consumes = "multipart/form-data", value = "/remove-attachments")
     @Operation(
             summary = "Remove attachments from PDF",
-            description = "This endpoint removes all embedded files (attachments) from a PDF. Input:PDF Output:PDF Type:SISO")
+            description =
+                    "This endpoint removes all embedded files (attachments) from a PDF. Input:PDF Output:PDF Type:SISO")
     public ResponseEntity<byte[]> removeAttachments(
-            @RequestParam("fileInput") MultipartFile pdfFile)
-            throws IOException {
+            @RequestParam("fileInput") MultipartFile pdfFile) throws IOException {
 
         // Load the PDF document
         PDDocument document = pdfDocumentFactory.load(pdfFile, true);
-        
+
         // Get the document catalog
         PDDocumentCatalog catalog = document.getDocumentCatalog();
-        
+
         // Remove embedded files
         if (catalog.getNames() != null) {
             catalog.getNames().setEmbeddedFiles(null);
@@ -98,35 +97,7 @@ public class AttachmentsController {
         return WebResponseUtils.pdfDocToWebResponse(
                 document,
                 Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
-                        .replaceFirst("[.][^.]+$", "") + "_attachments_removed.pdf");
-    }
-
-    private void addEmbeddedFile(PDDocument document, PDEmbeddedFilesNameTreeNode efTree, MultipartFile file)
-            throws IOException {
-        
-        // Create file specification
-        PDComplexFileSpecification fs = new PDComplexFileSpecification();
-        fs.setFile(file.getOriginalFilename());
-        fs.setFileDescription("Embedded file: " + file.getOriginalFilename());
-
-        // Create embedded file
-        PDEmbeddedFile ef = new PDEmbeddedFile(document, new ByteArrayInputStream(file.getBytes()));
-        ef.setSize((int) file.getSize());
-        ef.setCreationDate(new java.util.GregorianCalendar());
-        ef.setModDate(new java.util.GregorianCalendar());
-
-        // Set MIME type if available
-        String contentType = file.getContentType();
-        if (contentType != null && !contentType.isEmpty()) {
-            ef.setSubtype(contentType);
-        }
-
-        // Associate embedded file with file specification
-        fs.setEmbeddedFile(ef);
-
-        // Add to the name tree
-        efTree.setNames(java.util.Collections.singletonMap(file.getOriginalFilename(), fs));
-        
-        log.info("Added embedded file: {} ({} bytes)", file.getOriginalFilename(), file.getSize());
+                                .replaceFirst("[.][^.]+$", "")
+                        + "_attachments_removed.pdf");
     }
 }
diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java
new file mode 100644
index 000000000..dae172337
--- /dev/null
+++ b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java
@@ -0,0 +1,81 @@
+package stirling.software.SPDF.service;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+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;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+public class PDFAttachmentService implements PDFAttachmentServiceInterface {
+
+    @Override
+    public void addAttachment(
+            PDDocument document,
+            PDEmbeddedFilesNameTreeNode efTree,
+            List<MultipartFile> attachments)
+            throws IOException {
+        // Get existing names or create new map
+        Map<String, PDComplexFileSpecification> existingNames = new java.util.HashMap<>();
+        try {
+            existingNames = efTree.getNames();
+        } catch (IOException e) {
+            log.warn("Could not retrieve existing embedded files, starting with empty map", e);
+        }
+
+        Map<String, PDComplexFileSpecification> finalExistingNames = existingNames;
+        attachments.forEach(
+                attachment -> {
+                    // Create attachments specification
+                    PDComplexFileSpecification fileSpecification = new PDComplexFileSpecification();
+                    fileSpecification.setFile(attachment.getOriginalFilename());
+                    fileSpecification.setFileDescription(
+                            "Embedded attachment: " + attachment.getOriginalFilename());
+
+                    try {
+                        // Create embedded attachment
+                        PDEmbeddedFile embeddedFile =
+                                new PDEmbeddedFile(
+                                        document, new ByteArrayInputStream(attachment.getBytes()));
+                        embeddedFile.setSize((int) attachment.getSize());
+                        embeddedFile.setCreationDate(new java.util.GregorianCalendar());
+                        embeddedFile.setModDate(new java.util.GregorianCalendar());
+
+                        // Set MIME type if available
+                        String contentType = attachment.getContentType();
+                        if (StringUtils.isNotBlank(contentType)) {
+                            embeddedFile.setSubtype(contentType);
+                        }
+
+                        // Associate embedded attachment with file specification
+                        fileSpecification.setEmbeddedFile(embeddedFile);
+
+                        // Add to the existing names map
+                        finalExistingNames.put(attachment.getOriginalFilename(), fileSpecification);
+
+                        log.info(
+                                "Added embedded attachment: {} ({} bytes)",
+                                attachment.getOriginalFilename(),
+                                attachment.getSize());
+                    } catch (IOException e) {
+                        log.error(
+                                "Failed to create embedded file for attachment: {}",
+                                attachment.getOriginalFilename(),
+                                e);
+                    }
+                });
+
+        // Update the name tree with all names
+        efTree.setNames(existingNames);
+    }
+}
diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentServiceInterface.java b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentServiceInterface.java
new file mode 100644
index 000000000..90ae33013
--- /dev/null
+++ b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentServiceInterface.java
@@ -0,0 +1,17 @@
+package stirling.software.SPDF.service;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
+import org.springframework.web.multipart.MultipartFile;
+
+public interface PDFAttachmentServiceInterface {
+
+    void addAttachment(
+            PDDocument document,
+            PDEmbeddedFilesNameTreeNode efTree,
+            List<MultipartFile> attachments)
+            throws IOException;
+}
diff --git a/stirling-pdf/src/main/resources/messages_en_GB.properties b/stirling-pdf/src/main/resources/messages_en_GB.properties
index 7dc6cb571..102ea6381 100644
--- a/stirling-pdf/src/main/resources/messages_en_GB.properties
+++ b/stirling-pdf/src/main/resources/messages_en_GB.properties
@@ -513,6 +513,10 @@ home.addImage.title=Add image
 home.addImage.desc=Adds a image onto a set location on the PDF
 addImage.tags=img,jpg,picture,photo
 
+home.attachments.title=Attachments
+home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF
+attachments.tags=embed,attach,file,attachment,attachments
+
 home.watermark.title=Add Watermark
 home.watermark.desc=Add a custom watermark to your PDF document.
 watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo
@@ -1193,6 +1197,16 @@ addImage.everyPage=Every Page?
 addImage.upload=Add image
 addImage.submit=Add image
 
+#attachments
+attachments.title=Attachments
+attachments.header=Add attachments to PDF
+attachments.removeHeader=Remove attachments from PDF
+attachments.selectFiles=Select files to attach
+attachments.description=Allows you to add attachments to the PDF
+attachments.descriptionPlaceholder=Enter a description for the attachments...
+attachments.addButton=Add Attachments
+attachments.removeDescription=This will remove all embedded files from the PDF.
+attachments.removeButton=Remove All Attachments
 
 #merge
 merge.title=Merge
diff --git a/stirling-pdf/src/main/resources/messages_en_US.properties b/stirling-pdf/src/main/resources/messages_en_US.properties
index fb814f8e1..9506ff80b 100644
--- a/stirling-pdf/src/main/resources/messages_en_US.properties
+++ b/stirling-pdf/src/main/resources/messages_en_US.properties
@@ -474,7 +474,7 @@ addImage.tags=img,jpg,picture,photo
 
 home.attachments.title=Attachments
 home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF
-attachments.tags=embed,attach,file,attachment
+attachments.tags=embed,attach,file,attachment,attachments
 
 home.watermark.title=Add Watermark
 home.watermark.desc=Add a custom watermark to your PDF document.
@@ -484,7 +484,6 @@ home.permissions.title=Change Permissions
 home.permissions.desc=Change the permissions of your PDF document
 permissions.tags=read,write,edit,print
 
-
 home.removePages.title=Remove
 home.removePages.desc=Delete unwanted pages from your PDF document.
 removePages.tags=Remove pages,delete pages
diff --git a/stirling-pdf/src/main/resources/templates/fragments/navElements.html b/stirling-pdf/src/main/resources/templates/fragments/navElements.html
index cd7fae74b..f26abd0a4 100644
--- a/stirling-pdf/src/main/resources/templates/fragments/navElements.html
+++ b/stirling-pdf/src/main/resources/templates/fragments/navElements.html
@@ -236,7 +236,10 @@
       </div>
       <div
         th:replace="~{fragments/navbarEntry :: navbarEntry('unlock-pdf-forms', 'preview_off', 'home.unlockPDFForms.title', 'home.unlockPDFForms.desc', 'unlockPDFForms.tags', 'other')}">
-      </div>  
+      </div>
+      <div
+        th:replace="~{fragments/navbarEntry :: navbarEntry('attachments', 'attachment', 'home.attachments.title', 'home.attachments.desc', 'attachments.tags', 'other')}">
+      </div>
     </div>
   </div>
   <div id="groupAdvanced" class="feature-group">
diff --git a/stirling-pdf/src/main/resources/templates/misc/attachments.html b/stirling-pdf/src/main/resources/templates/misc/attachments.html
new file mode 100644
index 000000000..60bb16c96
--- /dev/null
+++ b/stirling-pdf/src/main/resources/templates/misc/attachments.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
+  xmlns:th="https://www.thymeleaf.org">
+
+<head>
+  <th:block th:insert="~{fragments/common :: head(title=#{attachments.title}, header=#{attachments.header})}"></th:block>
+</head>
+
+<body>
+  <div id="page-container">
+    <div id="content-wrap">
+      <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
+      <br><br>
+      <div class="container">
+        <div class="row justify-content-center">
+          <div class="col-md-6 bg-card">
+            <div class="tool-header">
+              <span class="material-symbols-rounded tool-header-icon other">attachment</span>
+              <span class="tool-header-text" th:text="#{attachments.header}"></span>
+            </div>
+
+            <form action="#" th:action="@{/api/v1/misc/add-attachments}" method="post" enctype="multipart/form-data">
+              <!-- PDF file selector -->
+              <div th:replace="~{fragments/common :: fileSelector(name='fileInput', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}">
+              </div>
+
+              <!-- Attachment files selector -->
+              <div th:replace="~{fragments/common :: fileSelector(name='attachments', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='*/*', inputText=#{attachments.selectFiles})}">
+              </div>
+
+              <!-- Submit button -->
+              <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{attachments.addButton}">Add Attachments</button>
+            </form>
+          </div>
+        </div>
+      </div>
+    </div>
+    <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
+  </div>
+</body>
+
+</html>
\ No newline at end of file