diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 08da2fa3c..be065504a 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -166,6 +166,7 @@ public class EndpointConfiguration { addEndpointToGroup("Other", "sign"); addEndpointToGroup("Other", "flatten"); addEndpointToGroup("Other", "repair"); + addEndpointToGroup("Other", "remove-read-only"); addEndpointToGroup("Other", REMOVE_BLANKS); addEndpointToGroup("Other", "remove-annotations"); addEndpointToGroup("Other", "compare"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/RemoveReadOnlyController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/RemoveReadOnlyController.java new file mode 100644 index 000000000..05a4f78d5 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/RemoveReadOnlyController.java @@ -0,0 +1,124 @@ +package stirling.software.SPDF.controller.api.misc; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.pdfbox.cos.*; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.github.pixee.security.Filenames; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import lombok.extern.slf4j.Slf4j; + +import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.service.CustomPDFDocumentFactory; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@RequestMapping("/api/v1/misc") +@Slf4j +@Tag(name = "Misc", description = "Miscellaneous APIs") +public class RemoveReadOnlyController { + private final CustomPDFDocumentFactory pdfDocumentFactory; + + @Autowired + public RemoveReadOnlyController(CustomPDFDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + + @PostMapping(consumes = "multipart/form-data", value = "/remove-read-only") + @Operation( + summary = "Remove read-only property from form fields", + description = + "Removing read-only property from form fields making them fillable" + + "Input:PDF, Output:PDF. Type:SISO") + public ResponseEntity removeReadOnly(@ModelAttribute PDFFile file) { + try (PDDocument document = pdfDocumentFactory.load(file)) { + PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); + + if (acroForm != null) { + acroForm.setNeedAppearances(true); + + for (PDField field : acroForm.getFieldTree()) { + COSDictionary dict = field.getCOSObject(); + if (dict.containsKey(COSName.getPDFName("Lock"))) { + dict.removeItem(COSName.getPDFName("Lock")); + } + int currentFlags = field.getFieldFlags(); + if ((currentFlags & 1) == 1) { + int newFlags = currentFlags & ~1; + field.setFieldFlags(newFlags); + } + } + + COSBase xfaBase = acroForm.getCOSObject().getDictionaryObject(COSName.XFA); + if (xfaBase != null) { + try { + if (xfaBase instanceof COSStream xfaStream) { + InputStream is = xfaStream.createInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + is.transferTo(baos); + String xml = baos.toString(StandardCharsets.UTF_8); + + xml = xml.replaceAll("access\\s*=\\s*\"readOnly\"", "access=\"open\""); + + PDStream newStream = + new PDStream( + document, + new ByteArrayInputStream( + xml.getBytes(StandardCharsets.UTF_8))); + acroForm.getCOSObject().setItem(COSName.XFA, newStream.getCOSObject()); + } else if (xfaBase instanceof COSArray xfaArray) { + for (int i = 0; i < xfaArray.size(); i += 2) { + COSBase namePart = xfaArray.getObject(i); + COSBase streamPart = xfaArray.getObject(i + 1); + if (namePart instanceof COSString + && streamPart instanceof COSStream stream) { + InputStream is = stream.createInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + is.transferTo(baos); + String xml = baos.toString(StandardCharsets.UTF_8); + + xml = + xml.replaceAll( + "access\\s*=\\s*\"readOnly\"", + "access=\"open\""); + + PDStream newStream = + new PDStream( + document, + new ByteArrayInputStream( + xml.getBytes(StandardCharsets.UTF_8))); + xfaArray.set(i + 1, newStream.getCOSObject()); + } + } + } + } catch (Exception e) { + log.error("exception", e); + } + } + } + String mergedFileName = + file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_removed_readonly.pdf"; + return WebResponseUtils.pdfDocToWebResponse( + document, Filenames.toSimpleFileName(mergedFileName)); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java index a4b2cc962..9c9cb0852 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java @@ -99,6 +99,13 @@ public class OtherWebController { return "misc/change-metadata"; } + @GetMapping("/remove-read-only") + @Hidden + public String removeReadOnlyForm(Model model) { + model.addAttribute("currentPage", "remove-read-only"); + return "misc/remove-read-only"; + } + @GetMapping("/compare") @Hidden public String compareForm(Model model) { diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 5b7efbab4..97a801723 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -364,6 +364,9 @@ home.compressPdfs.title=Compress home.compressPdfs.desc=Compress PDFs to reduce their file size. compressPdfs.tags=squish,small,tiny +home.removeReadOnly.title=Remove Read-Only from Form Fields +home.removeReadOnly.desc=Remove read-only property of form fields in a PDF document. +removeReadOnly.tags=remove,delete,form,field,readonly home.changeMetadata.title=Change Metadata home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document @@ -377,7 +380,6 @@ home.ocr.title=OCR / Cleanup scans home.ocr.desc=Cleanup scans and detects text from images within a PDF and re-adds it as text. ocr.tags=recognition,text,image,scan,read,identify,detection,editable - home.extractImages.title=Extract Images home.extractImages.desc=Extracts all images from a PDF and saves them to zip extractImages.tags=picture,photo,save,archive,zip,capture,grab @@ -1193,6 +1195,10 @@ changeMetadata.selectText.4=Other Metadata: changeMetadata.selectText.5=Add Custom Metadata Entry changeMetadata.submit=Change +#removeReadOnly +removeReadOnly.title=Remove Read-Only from Form Fields +removeReadOnly.header=Remove Read-Only from Form Fields +removeReadOnly.submit=Remove #pdfToPDFA pdfToPDFA.title=PDF To PDF/A diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index e2a2eeb75..c7162303c 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -364,6 +364,9 @@ home.compressPdfs.title=Compress home.compressPdfs.desc=Compress PDFs to reduce their file size. compressPdfs.tags=squish,small,tiny +home.removeReadOnly.title=Remove Read-Only from Form Fields +home.removeReadOnly.desc=Remove read-only property of form fields in a PDF document. +removeReadOnly.tags=remove,delete,form,field,readonly home.changeMetadata.title=Change Metadata home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document @@ -1193,6 +1196,10 @@ changeMetadata.selectText.4=Other Metadata: changeMetadata.selectText.5=Add Custom Metadata Entry changeMetadata.submit=Change +#removeReadOnly +removeReadOnly.title=Remove Read-Only from Form Fields +removeReadOnly.header=Remove Read-Only from Form Fields +removeReadOnly.submit=Remove #pdfToPDFA pdfToPDFA.title=PDF To PDF/A diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties index 88de5abf7..5b958a9c0 100644 --- a/src/main/resources/messages_pt_PT.properties +++ b/src/main/resources/messages_pt_PT.properties @@ -364,6 +364,9 @@ home.compressPdfs.title=Comprimir home.compressPdfs.desc=Comprimir PDFs para reduzir o seu tamanho. compressPdfs.tags=comprimir,pequeno,minúsculo +home.removeReadOnly.title=Remover Apenas Leitura de Formulários +home.removeReadOnly.desc=Remover propriedades de apenas leitura dos formulários de um PDF +removeReadOnly.tags=remover,apagar,formulário,campo,apenas leitura home.changeMetadata.title=Alterar Metadados home.changeMetadata.desc=Alterar/Remover/Adicionar metadados de um documento PDF @@ -1193,6 +1196,10 @@ changeMetadata.selectText.4=Outros Metadados: changeMetadata.selectText.5=Adicionar Entrada de Metadados Personalizada changeMetadata.submit=Alterar +#removeReadOnly +removeReadOnly.title=Remover Apenas Leitura de Formulários +removeReadOnly.header=Remover Apenas Leitura de Formulários +removeReadOnly.submit=Remover #pdfToPDFA pdfToPDFA.title=PDF Para PDF/A diff --git a/src/main/resources/templates/fragments/navElements.html b/src/main/resources/templates/fragments/navElements.html index 5b293a8a1..7722ff3ea 100644 --- a/src/main/resources/templates/fragments/navElements.html +++ b/src/main/resources/templates/fragments/navElements.html @@ -231,6 +231,9 @@
+
+
diff --git a/src/main/resources/templates/home-legacy.html b/src/main/resources/templates/home-legacy.html index 62ad658f8..a6aa67093 100644 --- a/src/main/resources/templates/home-legacy.html +++ b/src/main/resources/templates/home-legacy.html @@ -287,6 +287,9 @@
+
+
diff --git a/src/main/resources/templates/misc/remove-read-only.html b/src/main/resources/templates/misc/remove-read-only.html new file mode 100644 index 000000000..b571b1fec --- /dev/null +++ b/src/main/resources/templates/misc/remove-read-only.html @@ -0,0 +1,34 @@ + + + + + + + +
+
+ +

+
+
+ +
+
+ preview_off + +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ +