mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-12 17:52:13 +02:00
Implement read-only property removal from form fields
This commit is contained in:
parent
b024fb3ede
commit
73f39420cd
@ -162,6 +162,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");
|
||||
|
@ -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<byte[]> 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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -359,6 +359,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
|
||||
@ -372,7 +375,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
|
||||
@ -1188,6 +1190,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
|
||||
|
@ -359,6 +359,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
|
||||
@ -1188,6 +1191,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
|
||||
|
@ -359,6 +359,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
|
||||
@ -1188,6 +1191,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
|
||||
|
@ -231,6 +231,9 @@
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('replace-and-invert-color-pdf', 'format_color_fill', 'home.replaceColorPdf.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-read-only', 'preview_off', 'home.removeReadOnly.title', 'home.removeReadOnly.desc', 'removeReadOnly.tags', 'other')}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="groupAdvanced" class="feature-group">
|
||||
|
@ -287,6 +287,9 @@
|
||||
<div
|
||||
th:replace="~{fragments/card :: card(id='replace-and-invert-color-pdf', cardTitle=#{home.replaceColorPdf.title}, cardText=#{home.replaceColorPdf.desc}, cardLink='replace-and-invert-color-pdf', toolIcon='format_color_fill', tags=#{replaceColorPdf.tags}, toolGroup='other')}">
|
||||
</div>
|
||||
<div
|
||||
th:replace="~{fragments/card :: card(id='remove-read-only', cardTitle=#{home.removeReadOnly.title}, cardText=#{home.removeReadOnly.desc}, cardLink='remove-read-only', toolIcon='preview_off', tags=#{removeReadOnly.tags}, toolGroup='other')}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
34
src/main/resources/templates/misc/remove-read-only.html
Normal file
34
src/main/resources/templates/misc/remove-read-only.html
Normal file
@ -0,0 +1,34 @@
|
||||
<!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=#{removeReadOnly.title}, header=#{removeReadOnly.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">preview_off</span>
|
||||
<span class="tool-header-text" th:text="#{removeReadOnly.header}"></span>
|
||||
</div>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/remove-read-only'}" id="pdfForm" class="mb-3">
|
||||
<div class="custom-file">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removeReadOnly.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user