itext changes

This commit is contained in:
Anthony Stirling 2023-09-02 00:05:50 +01:00
parent 648df7b3d3
commit a7cd6bfd2e
12 changed files with 668 additions and 497 deletions

View file

@ -86,6 +86,7 @@ dependencies {
//general PDF //general PDF
implementation 'org.apache.pdfbox:pdfbox:2.0.29' implementation 'org.apache.pdfbox:pdfbox:2.0.29'
implementation 'org.apache.pdfbox:xmpbox:2.0.29'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70' implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
implementation 'com.itextpdf:itext7-core:7.2.5' implementation 'com.itextpdf:itext7-core:7.2.5'

View file

@ -66,7 +66,6 @@ public class SPdfApplication {
GeneralUtils.createDir("customFiles/static/"); GeneralUtils.createDir("customFiles/static/");
GeneralUtils.createDir("customFiles/templates/"); GeneralUtils.createDir("customFiles/templates/");
GeneralUtils.createDir("config");

View file

@ -18,14 +18,9 @@ public class OpenApiConfig {
public OpenAPI customOpenAPI() { public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion(); String version = getClass().getPackage().getImplementationVersion();
if (version == null) { if (version == null) {
Properties props = new Properties();
try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) {
props.load(input);
version = props.getProperty("version");
} catch (IOException ex) {
ex.printStackTrace();
version = "1.0.0"; // default version if all else fails version = "1.0.0"; // default version if all else fails
}
} }
return new OpenAPI().components(new Components()).info( return new OpenAPI().components(new Components()).info(

View file

@ -14,25 +14,26 @@ import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.*;
@Component @Component
public class InitialSecuritySetup { public class InitialSecuritySetup {
@Autowired @Autowired
private UserService userService; private UserService userService;
@Autowired @Autowired
ApplicationProperties applicationProperties; ApplicationProperties applicationProperties;
@PostConstruct @PostConstruct
public void init() { public void init() {
if (!userService.hasUsers()) { if (!userService.hasUsers()) {
String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername(); String initialUsername = "admin";
String initialPassword = applicationProperties.getSecurity().getInitialLogin().getPassword(); String initialPassword = "stirling";
if (initialUsername != null && initialPassword != null) { userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
}
} }
} }

View file

@ -1,8 +1,14 @@
package stirling.software.SPDF.controller.api; package stirling.software.SPDF.controller.api;
import java.awt.geom.AffineTransform;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -24,6 +30,8 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.multipdf.LayerUtility;
@RestController @RestController
@Tag(name = "General", description = "General APIs") @Tag(name = "General", description = "General APIs")
public class ToSinglePageController { public class ToSinglePageController {
@ -41,37 +49,34 @@ public class ToSinglePageController {
@Parameter(description = "The input multi-page PDF file to be converted into a single page", required = true) @Parameter(description = "The input multi-page PDF file to be converted into a single page", required = true)
MultipartFile file) throws IOException { MultipartFile file) throws IOException {
PdfReader reader = new PdfReader(file.getInputStream()); PDDocument sourceDocument = PDDocument.load(file.getInputStream());
PdfDocument sourceDocument = new PdfDocument(reader);
float totalHeight = 0; float totalHeight = 0;
float width = 0; float width = 0;
for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) { for (PDPage page : sourceDocument.getPages()) {
Rectangle pageSize = sourceDocument.getPage(i).getPageSize(); PDRectangle pageSize = page.getMediaBox();
totalHeight += pageSize.getHeight(); totalHeight += pageSize.getHeight();
if(width < pageSize.getWidth()) if(width < pageSize.getWidth())
width = pageSize.getWidth(); width = pageSize.getWidth();
}
PDDocument newDocument = new PDDocument();
PDPage newPage = new PDPage(new PDRectangle(width, totalHeight));
newDocument.addPage(newPage);
LayerUtility layerUtility = new LayerUtility(newDocument);
float yOffset = totalHeight;
for (PDPage page : sourceDocument.getPages()) {
PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, sourceDocument.getPages().indexOf(page));
AffineTransform af = AffineTransform.getTranslateInstance(0, yOffset - page.getMediaBox().getHeight());
layerUtility.appendFormAsLayer(newDocument.getPage(0), form, af, page.getResources().getCOSObject().toString());
yOffset -= page.getMediaBox().getHeight();
} }
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(baos); newDocument.save(baos);
PdfDocument newDocument = new PdfDocument(writer); newDocument.close();
PageSize newPageSize = new PageSize(width, totalHeight);
newDocument.addNewPage(newPageSize);
Document layoutDoc = new Document(newDocument);
float yOffset = totalHeight;
for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) {
PdfFormXObject pageCopy = sourceDocument.getPage(i).copyAsFormXObject(newDocument);
Image copiedPage = new Image(pageCopy);
copiedPage.setFixedPosition(0, yOffset - sourceDocument.getPage(i).getPageSize().getHeight());
yOffset -= sourceDocument.getPage(i).getPageSize().getHeight();
layoutDoc.add(copiedPage);
}
layoutDoc.close();
sourceDocument.close(); sourceDocument.close();
byte[] result = baos.toByteArray(); byte[] result = baos.toByteArray();

View file

@ -6,6 +6,11 @@ import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.List; import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -15,18 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@ -51,11 +45,10 @@ public class PageNumbersController {
@Parameter(description = "Which pages to number, default all", required = false, schema = @Schema(type = "string")) @RequestParam(value = "pagesToNumber", required = false) String pagesToNumber, @Parameter(description = "Which pages to number, default all", required = false, schema = @Schema(type = "string")) @RequestParam(value = "pagesToNumber", required = false) String pagesToNumber,
@Parameter(description = "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"", required = false, schema = @Schema(type = "string")) @RequestParam(value = "customText", required = false) String customText) @Parameter(description = "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"", required = false, schema = @Schema(type = "string")) @RequestParam(value = "customText", required = false) String customText)
throws IOException { throws IOException {
int pageNumber = startingNumber;
byte[] fileBytes = file.getBytes();
PDDocument document = PDDocument.load(fileBytes);
byte[] fileBytes = file.getBytes();
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
int pageNumber = startingNumber;
float marginFactor; float marginFactor;
switch (customMargin.toLowerCase()) { switch (customMargin.toLowerCase()) {
case "small": case "small":
@ -67,79 +60,67 @@ public class PageNumbersController {
case "large": case "large":
marginFactor = 0.05f; marginFactor = 0.05f;
break; break;
case "x-large":
marginFactor = 0.1f;
break;
default: default:
marginFactor = 0.035f; marginFactor = 0.035f;
break; break;
} }
float fontSize = 12.0f; float fontSize = 12.0f;
PDType1Font font = PDType1Font.HELVETICA;
PdfReader reader = new PdfReader(bais); List<Integer> pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(baos);
PdfDocument pdfDoc = new PdfDocument(reader, writer);
List<Integer> pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), pdfDoc.getNumberOfPages());
for (int i : pagesToNumberList) { for (int i : pagesToNumberList) {
PdfPage page = pdfDoc.getPage(i+1); PDPage page = document.getPage(i);
Rectangle pageSize = page.getPageSize(); PDRectangle pageSize = page.getMediaBox();
PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), pdfDoc);
String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(pdfDoc.getNumberOfPages())).replace("{filename}", file.getOriginalFilename().replaceFirst("[.][^.]+$", "")) : String.valueOf(pageNumber); String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(document.getNumberOfPages())).replace("{filename}", file.getOriginalFilename().replaceFirst("[.][^.]+$", "")) : String.valueOf(pageNumber);
PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA);
float textWidth = font.getWidth(text, fontSize);
float textHeight = font.getAscent(text, fontSize) - font.getDescent(text, fontSize);
float x, y; float x, y;
TextAlignment alignment;
int xGroup = (position - 1) % 3; int xGroup = (position - 1) % 3;
int yGroup = 2 - (position - 1) / 3; int yGroup = 2 - (position - 1) / 3;
switch (xGroup) { switch (xGroup) {
case 0: // left case 0: // left
x = pageSize.getLeft() + marginFactor * pageSize.getWidth(); x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
alignment = TextAlignment.LEFT;
break; break;
case 1: // center case 1: // center
x = pageSize.getLeft() + (pageSize.getWidth()) / 2; x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
alignment = TextAlignment.CENTER;
break; break;
default: // right default: // right
x = pageSize.getRight() - marginFactor * pageSize.getWidth(); x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
alignment = TextAlignment.RIGHT;
break; break;
} }
switch (yGroup) { switch (yGroup) {
case 0: // bottom case 0: // bottom
y = pageSize.getBottom() + marginFactor * pageSize.getHeight(); y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
break; break;
case 1: // middle case 1: // middle
y = pageSize.getBottom() + (pageSize.getHeight() ) / 2; y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
break; break;
default: // top default: // top
y = pageSize.getTop() - marginFactor * pageSize.getHeight(); y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
break; break;
} }
new Canvas(pdfCanvas, page.getPageSize()) PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
.showTextAligned(new Paragraph(text).setFont(font).setFontSize(fontSize), x, y, alignment); contentStream.beginText();
contentStream.setFont(font, fontSize);
contentStream.newLineAtOffset(x, y);
contentStream.showText(text);
contentStream.endText();
contentStream.close();
pageNumber++; pageNumber++;
} }
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
document.close();
pdfDoc.close(); return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), URLEncoder.encode(file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", "UTF-8"), MediaType.APPLICATION_PDF);
byte[] resultBytes = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse(resultBytes, URLEncoder.encode(file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", "UTF-8"), MediaType.APPLICATION_PDF);
} }

View file

@ -1,7 +1,11 @@
package stirling.software.SPDF.controller.api.other; package stirling.software.SPDF.controller.api.other;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -10,16 +14,6 @@ import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfStream;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@ -28,55 +22,33 @@ public class ShowJavascript {
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class); private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript") @PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
@Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> extractHeader( public ResponseEntity<byte[]> extractHeader(
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the javascript is to be extracted.", required = true) MultipartFile inputFile) @RequestPart(value = "fileInput") MultipartFile inputFile) throws Exception {
throws Exception {
String script = "";
try ( try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream()))
) {
String name = "";
String script = "";
String entryName = "File: "+inputFile.getOriginalFilename() + ", Script: ";
//Javascript
PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
if (namesDict != null) {
PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript);
if (javascriptDict != null) {
PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); PDNameTreeNode<PDActionJavaScript> jsTree = document.getDocumentCatalog().getNames().getJavaScript();
for (int i = 0; i < namesArray.size(); i += 2) {
if(namesArray.getAsString(i) != null)
name = namesArray.getAsString(i).toString();
PdfObject jsCode = namesArray.get(i+1); if (jsTree != null) {
if (jsCode instanceof PdfStream) { Map<String, PDActionJavaScript> jsEntries = jsTree.getNames();
byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes();
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
script = "//" + entryName + name + "\n" +jsCodeStr;
} else if (jsCode instanceof PdfDictionary) { for (Map.Entry<String, PDActionJavaScript> entry : jsEntries.entrySet()) {
// If the JS code is in a dictionary, you'll need to know the key to use. String name = entry.getKey();
// Assuming the key is PdfName.JS: PDActionJavaScript jsAction = entry.getValue();
PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS); String jsCodeStr = jsAction.getAction();
if (jsCodeStream != null) {
byte[] jsCodeBytes = jsCodeStream.getBytes();
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
script = "//" + entryName + name + "\n" +jsCodeStr;
}
}
}
} script += "// File: " + inputFile.getOriginalFilename() + ", Script: " + name + "\n" + jsCodeStr + "\n";
} }
if(script.equals("")) { }
script = "PDF '" +inputFile.getOriginalFilename() + "' does not contain Javascript";
} if (script.isEmpty()) {
return WebResponseUtils.bytesToWebResponse(script.getBytes(), name + ".js"); script = "PDF '" + inputFile.getOriginalFilename() + "' does not contain Javascript";
} }
return WebResponseUtils.bytesToWebResponse(script.getBytes(StandardCharsets.UTF_8), inputFile.getOriginalFilename() + ".js");
}
} }

View file

@ -0,0 +1,258 @@
package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.cms.CMSTypedData;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collections;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ContentDisposition;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.ResponseEntity;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.utils.WebResponseUtils;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@RestController
@Tag(name = "Security", description = "Security APIs")
public class CertSignController2 {
private static final Logger logger = LoggerFactory.getLogger(CertSignController2.class);
static {
Security.addProvider(new BouncyCastleProvider());
}
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
@Operation(summary = "Sign PDF with a Digital Certificate",
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
public ResponseEntity<byte[]> signPDF(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be signed")
MultipartFile pdf,
@RequestParam(value = "certType", required = false)
@Parameter(description = "The type of the digital certificate", schema = @Schema(allowableValues = {"PKCS12", "PEM"}))
String certType,
@RequestParam(value = "key", required = false)
@Parameter(description = "The private key for the digital certificate (required for PEM type certificates)")
MultipartFile privateKeyFile,
@RequestParam(value = "cert", required = false)
@Parameter(description = "The digital certificate (required for PEM type certificates)")
MultipartFile certFile,
@RequestParam(value = "p12", required = false)
@Parameter(description = "The PKCS12 keystore file (required for PKCS12 type certificates)")
MultipartFile p12File,
@RequestParam(value = "password", required = false)
@Parameter(description = "The password for the keystore or the private key")
String password,
@RequestParam(value = "showSignature", required = false)
@Parameter(description = "Whether to visually show the signature in the PDF file")
Boolean showSignature,
@RequestParam(value = "reason", required = false)
@Parameter(description = "The reason for signing the PDF")
String reason,
@RequestParam(value = "location", required = false)
@Parameter(description = "The location where the PDF is signed")
String location,
@RequestParam(value = "name", required = false)
@Parameter(description = "The name of the signer")
String name,
@RequestParam(value = "pageNumber", required = false)
@Parameter(description = "The page number where the signature should be visible. This is required if showSignature is set to true")
Integer pageNumber) throws Exception {
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
PrivateKey privateKey = null;
X509Certificate cert = null;
if (certType != null) {
switch (certType) {
case "PKCS12":
if (p12File != null) {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray());
String alias = ks.aliases().nextElement();
privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
cert = (X509Certificate) ks.getCertificate(alias);
}
break;
case "PEM":
if (privateKeyFile != null && certFile != null) {
// Load private key
KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider);
if (isPEM(privateKeyFile.getBytes())) {
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes())));
} else {
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes()));
}
// Load certificate
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider);
if (isPEM(certFile.getBytes())) {
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes())));
} else {
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes()));
}
}
break;
}
}
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName(name);
signature.setLocation(location);
signature.setReason(reason);
// Load the PDF
try (PDDocument document = PDDocument.load(pdf.getBytes())) {
SignatureOptions signatureOptions = new SignatureOptions();
// If you want to show the signature
if (showSignature != null && showSignature) {
// Calculate signature field position based on your requirements
PDPage page = document.getPage(pageNumber - 1); // zero-based
PDVisibleSignDesigner signDesigner = new PDVisibleSignDesigner(new ByteArrayInputStream(pdf.getBytes()));
//TODO signDesigner
PDVisibleSigProperties sigProperties = new PDVisibleSigProperties();
//TODO sigProperties extra
signatureOptions.setVisualSignature(sigProperties);
signatureOptions.setPage(pageNumber - 1);
}
document.addSignature(signature, signatureOptions);
// External signing
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(new ByteArrayOutputStream());
byte[] content = IOUtils.toByteArray(externalSigning.getContent());
// Using BouncyCastle to sign
CMSTypedData cmsData = new CMSProcessableByteArray(content);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").setProvider(provider).build(privateKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider(provider).build()).build(signer, cert));
gen.addCertificates(new JcaCertStore(Collections.singletonList(cert)));
CMSSignedData signedData = gen.generate(cmsData, false);
byte[] cmsSignature = signedData.getEncoded();
externalSigning.setSignature(cmsSignature);
// After setting the signature, return the resultant PDF
try (ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream()) {
document.save(signedPdfOutput);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDisposition(ContentDisposition.builder("attachment").filename("signed.pdf").build());
return new ResponseEntity<>(signedPdfOutput.toByteArray(), headers, HttpStatus.OK);
}
}
}
private byte[] parsePEM(byte[] content) throws IOException {
PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
return pemReader.readPemObject().getContent();
}
private boolean isPEM(byte[] content) {
String contentStr = new String(content);
return contentStr.contains("-----BEGIN") && contentStr.contains("-----END");
}
}

View file

@ -10,14 +10,52 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.io.ByteArrayOutputStream;
import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.cos.COSInputStream;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.PDJavascriptNameTreeNode;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.COSObjectable;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode; import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
import org.apache.pdfbox.pdmodel.encryption.PDEncryption; import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -25,33 +63,13 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.xml.XmpParsingException;
import org.apache.xmpbox.xml.XmpSerializer;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfCatalog;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfOutline;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfResources;
import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.kernel.pdf.PdfString;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfFileAttachmentAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
import com.itextpdf.kernel.pdf.layer.PdfLayer;
import com.itextpdf.kernel.pdf.layer.PdfOCProperties;
import com.itextpdf.kernel.xmp.XMPException;
import com.itextpdf.kernel.xmp.XMPMeta;
import com.itextpdf.kernel.xmp.XMPMetaFactory;
import com.itextpdf.kernel.xmp.options.SerializeOptions;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@ -72,7 +90,6 @@ public class GetInfoOnPDF {
try ( try (
PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream());
PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream()))
) { ) {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
ObjectNode jsonOutput = objectMapper.createObjectNode(); ObjectNode jsonOutput = objectMapper.createObjectNode();
@ -120,21 +137,17 @@ public class GetInfoOnPDF {
boolean hasCompression = false; boolean hasCompression = false;
String compressionType = "None"; String compressionType = "None";
// Check for object streams COSDocument cosDoc = pdfBoxDoc.getDocument();
for (int i = 1; i <= itextDoc.getNumberOfPdfObjects(); i++) { for (COSObject cosObject : cosDoc.getObjects()) {
PdfObject obj = itextDoc.getPdfObject(i); if (cosObject.getObject() instanceof COSStream) {
if (obj != null && obj.isStream() && ((PdfStream) obj).get(PdfName.Type) == PdfName.ObjStm) { COSStream cosStream = (COSStream) cosObject.getObject();
hasCompression = true; if (COSName.OBJ_STM.equals(cosStream.getItem(COSName.TYPE))) {
compressionType = "Object Streams"; hasCompression = true;
break; compressionType = "Object Streams";
break;
}
} }
} }
// If not compressed using object streams, check for compressed Xref tables
if (!hasCompression && itextDoc.getReader().hasRebuiltXref()) {
hasCompression = true;
compressionType = "Compressed Xref or Rebuilt Xref";
}
basicInfo.put("Compression", hasCompression); basicInfo.put("Compression", hasCompression);
if(hasCompression) if(hasCompression)
basicInfo.put("CompressionType", compressionType); basicInfo.put("CompressionType", compressionType);
@ -144,9 +157,8 @@ public class GetInfoOnPDF {
basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages()); basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages());
// Page Mode using iText7 PDDocumentCatalog catalog = pdfBoxDoc.getDocumentCatalog();
PdfCatalog catalog = itextDoc.getCatalog(); String pageMode = catalog.getPageMode().name();
PdfName pageMode = catalog.getPdfObject().getAsName(PdfName.PageMode);
// Document Information using PDFBox // Document Information using PDFBox
docInfoNode.put("PDF version", pdfBoxDoc.getVersion()); docInfoNode.put("PDF version", pdfBoxDoc.getVersion());
@ -157,49 +169,54 @@ public class GetInfoOnPDF {
PdfAcroForm acroForm = PdfAcroForm.getAcroForm(itextDoc, false); PDAcroForm acroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm();
ObjectNode formFieldsNode = objectMapper.createObjectNode(); ObjectNode formFieldsNode = objectMapper.createObjectNode();
if (acroForm != null) { if (acroForm != null) {
for (Map.Entry<String, PdfFormField> entry : acroForm.getFormFields().entrySet()) { for (PDField field : acroForm.getFieldTree()) {
formFieldsNode.put(entry.getKey(), entry.getValue().getValueAsString()); formFieldsNode.put(field.getFullyQualifiedName(), field.getValueAsString());
} }
} }
jsonOutput.set("FormFields", formFieldsNode); jsonOutput.set("FormFields", formFieldsNode);
//embeed files TODO size //embeed files TODO size
PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();
ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); ArrayNode embeddedFilesArray = objectMapper.createArrayNode();
if(itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) != null) if (efTree != null) {
{ Map<String, PDComplexFileSpecification> efMap = efTree.getNames();
PdfDictionary embeddedFiles = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) if (efMap != null) {
.getAsDictionary(PdfName.EmbeddedFiles); for (Map.Entry<String, PDComplexFileSpecification> entry : efMap.entrySet()) {
if (embeddedFiles != null) { ObjectNode embeddedFileNode = objectMapper.createObjectNode();
embeddedFileNode.put("Name", entry.getKey());
PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names); PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile();
if(namesArray != null) { if (embeddedFile != null) {
for (int i = 0; i < namesArray.size(); i += 2) { embeddedFileNode.put("FileSize", embeddedFile.getLength()); // size in bytes
ObjectNode embeddedFileNode = objectMapper.createObjectNode(); }
embeddedFileNode.put("Name", namesArray.getAsString(i).toString()); embeddedFilesArray.add(embeddedFileNode);
// Add other details if required }
embeddedFilesArray.add(embeddedFileNode);
}
} }
}
} }
other.set("EmbeddedFiles", embeddedFilesArray); other.set("EmbeddedFiles", embeddedFilesArray);
//attachments TODO size //attachments TODO size
ArrayNode attachmentsArray = objectMapper.createArrayNode(); ArrayNode attachmentsArray = objectMapper.createArrayNode();
for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { for (PDPage page : pdfBoxDoc.getPages()) {
for (PdfAnnotation annotation : itextDoc.getPage(pageNum).getAnnotations()) { for (PDAnnotation annotation : page.getAnnotations()) {
if (annotation instanceof PdfFileAttachmentAnnotation) { if (annotation instanceof PDAnnotationFileAttachment) {
PDAnnotationFileAttachment fileAttachmentAnnotation = (PDAnnotationFileAttachment) annotation;
ObjectNode attachmentNode = objectMapper.createObjectNode(); ObjectNode attachmentNode = objectMapper.createObjectNode();
attachmentNode.put("Name", ((PdfFileAttachmentAnnotation) annotation).getName().toString()); attachmentNode.put("Name", fileAttachmentAnnotation.getAttachmentName());
attachmentNode.put("Description", annotation.getContents().getValue()); attachmentNode.put("Description", fileAttachmentAnnotation.getContents());
attachmentsArray.add(attachmentNode); attachmentsArray.add(attachmentNode);
} }
} }
@ -207,65 +224,54 @@ public class GetInfoOnPDF {
other.set("Attachments", attachmentsArray); other.set("Attachments", attachmentsArray);
//Javascript //Javascript
PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); PDDocumentNameDictionary namesDict = catalog.getNames();
ArrayNode javascriptArray = objectMapper.createArrayNode(); ArrayNode javascriptArray = objectMapper.createArrayNode();
if (namesDict != null) { if (namesDict != null) {
PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); PDJavascriptNameTreeNode javascriptDict = namesDict.getJavaScript();
if (javascriptDict != null) { if (javascriptDict != null) {
try {
Map<String, PDActionJavaScript> jsEntries = javascriptDict.getNames();
PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); for (Map.Entry<String, PDActionJavaScript> entry : jsEntries.entrySet()) {
for (int i = 0; i < namesArray.size(); i += 2) { ObjectNode jsNode = objectMapper.createObjectNode();
ObjectNode jsNode = objectMapper.createObjectNode(); jsNode.put("JS Name", entry.getKey());
if(namesArray.getAsString(i) != null)
jsNode.put("JS Name", namesArray.getAsString(i).toString());
// Here we check for a PdfStream object and retrieve the JS code from it PDActionJavaScript jsAction = entry.getValue();
PdfObject jsCode = namesArray.get(i+1); if (jsAction != null) {
if (jsCode instanceof PdfStream) { String jsCodeStr = jsAction.getAction();
byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes(); if (jsCodeStr != null) {
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); jsNode.put("JS Script Length", jsCodeStr.length());
jsNode.put("JS Script Length", jsCodeStr.length()); }
} else if (jsCode instanceof PdfDictionary) {
// If the JS code is in a dictionary, you'll need to know the key to use.
// Assuming the key is PdfName.JS:
PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS);
if (jsCodeStream != null) {
byte[] jsCodeBytes = jsCodeStream.getBytes();
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
jsNode.put("JS Script Character Length", jsCodeStr.length());
} }
javascriptArray.add(jsNode);
} }
} catch (IOException e) {
javascriptArray.add(jsNode); e.printStackTrace();
} }
} }
} }
other.set("JavaScript", javascriptArray); other.set("JavaScript", javascriptArray);
//TODO size //TODO size
PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false); PDOptionalContentProperties ocProperties = pdfBoxDoc.getDocumentCatalog().getOCProperties();
ArrayNode layersArray = objectMapper.createArrayNode(); ArrayNode layersArray = objectMapper.createArrayNode();
if (ocProperties != null) { if (ocProperties != null) {
for (PDOptionalContentGroup ocg : ocProperties.getOptionalContentGroups()) {
for (PdfLayer layer : ocProperties.getLayers()) {
ObjectNode layerNode = objectMapper.createObjectNode(); ObjectNode layerNode = objectMapper.createObjectNode();
layerNode.put("Name", layer.getPdfObject().getAsString(PdfName.Name).toString()); layerNode.put("Name", ocg.getName());
layersArray.add(layerNode); layersArray.add(layerNode);
} }
} }
other.set("Layers", layersArray); other.set("Layers", layersArray);
//TODO Security //TODO Security
// Digital Signatures using iText7 TODO
@ -282,13 +288,13 @@ public class GetInfoOnPDF {
} }
boolean isPdfACompliant = checkOutputIntent(itextDoc, "PDF/A"); boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A");
boolean isPdfXCompliant = checkOutputIntent(itextDoc, "PDF/X"); boolean isPdfXCompliant = checkForStandard(pdfBoxDoc, "PDF/X");
boolean isPdfECompliant = checkForStandard(itextDoc, "PDF/E"); boolean isPdfECompliant = checkForStandard(pdfBoxDoc, "PDF/E");
boolean isPdfVTCompliant = checkForStandard(itextDoc, "PDF/VT"); boolean isPdfVTCompliant = checkForStandard(pdfBoxDoc, "PDF/VT");
boolean isPdfUACompliant = checkForStandard(itextDoc, "PDF/UA"); boolean isPdfUACompliant = checkForStandard(pdfBoxDoc, "PDF/UA");
boolean isPdfBCompliant = checkForStandard(itextDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard. boolean isPdfBCompliant = checkForStandard(pdfBoxDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard.
boolean isPdfSECCompliant = checkForStandard(itextDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021. boolean isPdfSECCompliant = checkForStandard(pdfBoxDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021.
compliancy.put("IsPDF/ACompliant", isPdfACompliant); compliancy.put("IsPDF/ACompliant", isPdfACompliant);
compliancy.put("IsPDF/XCompliant", isPdfXCompliant); compliancy.put("IsPDF/XCompliant", isPdfXCompliant);
@ -302,27 +308,39 @@ public class GetInfoOnPDF {
PDOutlineNode root = pdfBoxDoc.getDocumentCatalog().getDocumentOutline();
ArrayNode bookmarksArray = objectMapper.createArrayNode(); ArrayNode bookmarksArray = objectMapper.createArrayNode();
PdfOutline root = itextDoc.getOutlines(false);
if (root != null) { if (root != null) {
for (PdfOutline child : root.getAllChildren()) { for (PDOutlineItem child : root.children()) {
addOutlinesToArray(child, bookmarksArray); addOutlinesToArray(child, bookmarksArray);
} }
} }
other.set("Bookmarks/Outline/TOC", bookmarksArray); other.set("Bookmarks/Outline/TOC", bookmarksArray);
byte[] xmpBytes = itextDoc.getXmpMetadata();
String xmpString = null;
if (xmpBytes != null) { PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata();
try {
XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes); String xmpString = null;
xmpString = new String(XMPMetaFactory.serializeToBuffer(xmpMeta, new SerializeOptions()));
} catch (XMPException e) { if (pdMetadata != null) {
e.printStackTrace(); try {
} COSInputStream is = pdMetadata.createInputStream();
} DomXmpParser domXmpParser = new DomXmpParser();
other.put("XMPMetadata", xmpString); XMPMetadata xmpMeta = domXmpParser.parse(is);
ByteArrayOutputStream os = new ByteArrayOutputStream();
new XmpSerializer().serialize(xmpMeta, os, true);
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
} catch (XmpParsingException | IOException e) {
e.printStackTrace();
}
}
other.put("XMPMetadata", xmpString);
if (pdfBoxDoc.isEncrypted()) { if (pdfBoxDoc.isEncrypted()) {
@ -356,23 +374,40 @@ public class GetInfoOnPDF {
ObjectNode pageInfoParent = objectMapper.createObjectNode(); ObjectNode pageInfoParent = objectMapper.createObjectNode();
for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { for (int pageNum = 1; pageNum <= pdfBoxDoc.getNumberOfPages(); pageNum++) {
ObjectNode pageInfo = objectMapper.createObjectNode(); ObjectNode pageInfo = objectMapper.createObjectNode();
// Retrieve the page
PDPage page = pdfBoxDoc.getPage(pageNum);
// Page-level Information // Page-level Information
Rectangle pageSize = itextDoc.getPage(pageNum).getPageSize(); PDRectangle mediaBox = page.getMediaBox();
pageInfo.put("Width", pageSize.getWidth());
pageInfo.put("Height", pageSize.getHeight()); float width = mediaBox.getWidth();
pageInfo.put("Rotation", itextDoc.getPage(pageNum).getRotation()); float height = mediaBox.getHeight();
pageInfo.put("Page Orientation", getPageOrientation(pageSize.getWidth(),pageSize.getHeight()));
pageInfo.put("Standard Size", getPageSize(pageSize.getWidth(),pageSize.getHeight())); pageInfo.put("Width", width);
pageInfo.put("Height", height);
pageInfo.put("Rotation", page.getRotation());
pageInfo.put("Page Orientation", getPageOrientation(width, height));
pageInfo.put("Standard Size", getPageSize(width, height));
// Boxes // Boxes
pageInfo.put("MediaBox", itextDoc.getPage(pageNum).getMediaBox().toString()); pageInfo.put("MediaBox", mediaBox.toString());
pageInfo.put("CropBox", itextDoc.getPage(pageNum).getCropBox().toString());
pageInfo.put("BleedBox", itextDoc.getPage(pageNum).getBleedBox().toString()); // Assuming the following boxes are defined for your document; if not, you may get null values.
pageInfo.put("TrimBox", itextDoc.getPage(pageNum).getTrimBox().toString()); PDRectangle cropBox = page.getCropBox();
pageInfo.put("ArtBox", itextDoc.getPage(pageNum).getArtBox().toString()); pageInfo.put("CropBox", cropBox == null ? "Undefined" : cropBox.toString());
PDRectangle bleedBox = page.getBleedBox();
pageInfo.put("BleedBox", bleedBox == null ? "Undefined" : bleedBox.toString());
PDRectangle trimBox = page.getTrimBox();
pageInfo.put("TrimBox", trimBox == null ? "Undefined" : trimBox.toString());
PDRectangle artBox = page.getArtBox();
pageInfo.put("ArtBox", artBox == null ? "Undefined" : artBox.toString());
// Content Extraction // Content Extraction
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();
@ -382,17 +417,18 @@ public class GetInfoOnPDF {
pageInfo.put("Text Characters Count", pageText.length()); // pageInfo.put("Text Characters Count", pageText.length()); //
// Annotations // Annotations
List<PdfAnnotation> annotations = itextDoc.getPage(pageNum).getAnnotations();
List<PDAnnotation> annotations = page.getAnnotations();
int subtypeCount = 0; int subtypeCount = 0;
int contentsCount = 0; int contentsCount = 0;
for (PdfAnnotation annotation : annotations) { for (PDAnnotation annotation : annotations) {
if(annotation.getSubtype() != null) { if (annotation.getSubtype() != null) {
subtypeCount++; // Increase subtype count subtypeCount++; // Increase subtype count
} }
if(annotation.getContents() != null) { if (annotation.getContents() != null) {
contentsCount++; // Increase contents count contentsCount++; // Increase contents count
} }
} }
@ -403,25 +439,31 @@ public class GetInfoOnPDF {
annotationsObject.put("ContentsCount", contentsCount); annotationsObject.put("ContentsCount", contentsCount);
pageInfo.set("Annotations", annotationsObject); pageInfo.set("Annotations", annotationsObject);
// Images (simplified) // Images (simplified)
// This part is non-trivial as images can be embedded in multiple ways in a PDF. // This part is non-trivial as images can be embedded in multiple ways in a PDF.
// Here is a basic structure to recognize image XObjects on a page. // Here is a basic structure to recognize image XObjects on a page.
ArrayNode imagesArray = objectMapper.createArrayNode(); ArrayNode imagesArray = objectMapper.createArrayNode();
PdfResources resources = itextDoc.getPage(pageNum).getResources(); PDResources resources = page.getResources();
for (PdfName name : resources.getResourceNames()) {
PdfObject obj = resources.getResource(name);
if (obj instanceof PdfStream) { for (COSName name : resources.getXObjectNames()) {
PdfStream stream = (PdfStream) obj; PDXObject xObject = resources.getXObject(name);
if (PdfName.Image.equals(stream.getAsName(PdfName.Subtype))) { if (xObject instanceof PDImageXObject) {
ObjectNode imageNode = objectMapper.createObjectNode(); PDImageXObject image = (PDImageXObject) xObject;
imageNode.put("Width", stream.getAsNumber(PdfName.Width).intValue());
imageNode.put("Height", stream.getAsNumber(PdfName.Height).intValue()); ObjectNode imageNode = objectMapper.createObjectNode();
PdfObject colorSpace = stream.get(PdfName.ColorSpace); imageNode.put("Width", image.getWidth());
if (colorSpace != null) { imageNode.put("Height", image.getHeight());
imageNode.put("ColorSpace", colorSpace.toString()); if(image.getMetadata() != null && image.getMetadata().getFile() != null && image.getMetadata().getFile().getFile() != null) {
} imageNode.put("Name", image.getMetadata().getFile().getFile());
imagesArray.add(imageNode);
} }
if (image.getColorSpace() != null) {
imageNode.put("ColorSpace", image.getColorSpace().getName());
}
imagesArray.add(imageNode);
} }
} }
pageInfo.set("Images", imagesArray); pageInfo.set("Images", imagesArray);
@ -431,12 +473,13 @@ public class GetInfoOnPDF {
ArrayNode linksArray = objectMapper.createArrayNode(); ArrayNode linksArray = objectMapper.createArrayNode();
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
for (PdfAnnotation annotation : annotations) { for (PDAnnotation annotation : annotations) {
if (annotation instanceof PdfLinkAnnotation) { if (annotation instanceof PDAnnotationLink) {
PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation; PDAnnotationLink linkAnnotation = (PDAnnotationLink) annotation;
if(linkAnnotation != null && linkAnnotation.getAction() != null) { if (linkAnnotation.getAction() instanceof PDActionURI) {
String uri = linkAnnotation.getAction().toString(); PDActionURI uriAction = (PDActionURI) linkAnnotation.getAction();
uniqueURIs.add(uri); // Add to set to ensure uniqueness String uri = uriAction.getURI();
uniqueURIs.add(uri); // Add to set to ensure uniqueness
} }
} }
} }
@ -449,96 +492,52 @@ public class GetInfoOnPDF {
} }
pageInfo.set("Links", linksArray); pageInfo.set("Links", linksArray);
// Fonts
// Fonts
ArrayNode fontsArray = objectMapper.createArrayNode(); ArrayNode fontsArray = objectMapper.createArrayNode();
PdfDictionary fontDicts = resources.getResource(PdfName.Font);
Set<String> uniqueSubtypes = new HashSet<>(); // To store unique subtypes
// Map to store unique fonts and their counts
Map<String, ObjectNode> uniqueFontsMap = new HashMap<>(); Map<String, ObjectNode> uniqueFontsMap = new HashMap<>();
if (fontDicts != null) { for (COSName fontName : resources.getFontNames()) {
for (PdfName key : fontDicts.keySet()) { PDFont font = resources.getFont(fontName);
ObjectNode fontNode = objectMapper.createObjectNode(); // Create a new font node for each font ObjectNode fontNode = objectMapper.createObjectNode();
PdfDictionary font = fontDicts.getAsDictionary(key);
boolean isEmbedded = font.containsKey(PdfName.FontFile) || fontNode.put("IsEmbedded", font.isEmbedded());
font.containsKey(PdfName.FontFile2) ||
font.containsKey(PdfName.FontFile3);
fontNode.put("IsEmbedded", isEmbedded);
if (font.containsKey(PdfName.Encoding)) { // PDFBox provides Font's BaseFont (i.e., the font name) directly
String encoding = font.getAsName(PdfName.Encoding).toString(); fontNode.put("Name", font.getName());
fontNode.put("Encoding", encoding);
}
if (font.getAsString(PdfName.BaseFont) != null) { fontNode.put("Subtype", font.getType());
fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString());
}
String subtype = null; PDFontDescriptor fontDescriptor = font.getFontDescriptor();
if (font.containsKey(PdfName.Subtype)) {
subtype = font.getAsName(PdfName.Subtype).toString();
uniqueSubtypes.add(subtype); // Add to set to ensure uniqueness
}
fontNode.put("Subtype", subtype);
PdfDictionary fontDescriptor = font.getAsDictionary(PdfName.FontDescriptor); if (fontDescriptor != null) {
if (fontDescriptor != null) { fontNode.put("ItalicAngle", fontDescriptor.getItalicAngle());
if (fontDescriptor.containsKey(PdfName.ItalicAngle)) { int flags = fontDescriptor.getFlags();
fontNode.put("ItalicAngle", fontDescriptor.getAsNumber(PdfName.ItalicAngle).floatValue()); fontNode.put("IsItalic", (flags & 1) != 0);
} fontNode.put("IsBold", (flags & 64) != 0);
fontNode.put("IsFixedPitch", (flags & 2) != 0);
fontNode.put("IsSerif", (flags & 4) != 0);
fontNode.put("IsSymbolic", (flags & 8) != 0);
fontNode.put("IsScript", (flags & 16) != 0);
fontNode.put("IsNonsymbolic", (flags & 32) != 0);
if (fontDescriptor.containsKey(PdfName.Flags)) { fontNode.put("FontFamily", fontDescriptor.getFontFamily());
int flags = fontDescriptor.getAsNumber(PdfName.Flags).intValue(); // Font stretch and BBox are not directly available in PDFBox's API, so these are omitted for simplicity
fontNode.put("IsItalic", (flags & 64) != 0); fontNode.put("FontWeight", fontDescriptor.getFontWeight());
fontNode.put("IsBold", (flags & 1 << 16) != 0); }
fontNode.put("IsFixedPitch", (flags & 1) != 0);
fontNode.put("IsSerif", (flags & 2) != 0);
fontNode.put("IsSymbolic", (flags & 4) != 0);
fontNode.put("IsScript", (flags & 8) != 0);
fontNode.put("IsNonsymbolic", (flags & 16) != 0);
}
if (fontDescriptor.containsKey(PdfName.FontFamily)) {
String fontFamily = fontDescriptor.getAsString(PdfName.FontFamily).toString();
fontNode.put("FontFamily", fontFamily);
}
if (fontDescriptor.containsKey(PdfName.FontStretch)) { // Create a unique key for this font node based on its attributes
String fontStretch = fontDescriptor.getAsName(PdfName.FontStretch).toString(); String uniqueKey = fontNode.toString();
fontNode.put("FontStretch", fontStretch);
}
if (fontDescriptor.containsKey(PdfName.FontBBox)) { // Increment count if this font exists, or initialize it if new
PdfArray bbox = fontDescriptor.getAsArray(PdfName.FontBBox); if (uniqueFontsMap.containsKey(uniqueKey)) {
fontNode.put("FontBoundingBox", bbox.toString()); ObjectNode existingFontNode = uniqueFontsMap.get(uniqueKey);
} int count = existingFontNode.get("Count").asInt() + 1;
existingFontNode.put("Count", count);
if (fontDescriptor.containsKey(PdfName.FontWeight)) { } else {
float fontWeight = fontDescriptor.getAsNumber(PdfName.FontWeight).floatValue(); fontNode.put("Count", 1);
fontNode.put("FontWeight", fontWeight); uniqueFontsMap.put(uniqueKey, fontNode);
}
}
if (font.containsKey(PdfName.ToUnicode)) {
fontNode.put("HasToUnicodeMap", true);
}
if (fontNode.size() > 0) {
// Create a unique key for this font node based on its attributes
String uniqueKey = fontNode.toString();
// Increment count if this font exists, or initialize it if new
if (uniqueFontsMap.containsKey(uniqueKey)) {
ObjectNode existingFontNode = uniqueFontsMap.get(uniqueKey);
int count = existingFontNode.get("Count").asInt() + 1;
existingFontNode.put("Count", count);
} else {
fontNode.put("Count", 1);
uniqueFontsMap.put(uniqueKey, fontNode);
}
}
} }
} }
@ -552,41 +551,49 @@ public class GetInfoOnPDF {
// Access resources dictionary
PdfDictionary resourcesDict = itextDoc.getPage(pageNum).getResources().getPdfObject();
// Color Spaces & ICC Profiles
// Access resources dictionary
ArrayNode colorSpacesArray = objectMapper.createArrayNode(); ArrayNode colorSpacesArray = objectMapper.createArrayNode();
PdfDictionary colorSpaces = resourcesDict.getAsDictionary(PdfName.ColorSpace);
if (colorSpaces != null) { Iterable<COSName> colorSpaceNames = resources.getColorSpaceNames();
for (PdfName name : colorSpaces.keySet()) { for (COSName name : colorSpaceNames) {
PdfObject colorSpaceObject = colorSpaces.get(name); PDColorSpace colorSpace = resources.getColorSpace(name);
if (colorSpaceObject instanceof PdfArray) { if (colorSpace instanceof PDICCBased) {
PdfArray colorSpaceArray = (PdfArray) colorSpaceObject; PDICCBased iccBased = (PDICCBased) colorSpace;
if (colorSpaceArray.size() > 1 && colorSpaceArray.get(0) instanceof PdfName && PdfName.ICCBased.equals(colorSpaceArray.get(0))) { PDStream iccData = iccBased.getPDStream();
ObjectNode iccProfileNode = objectMapper.createObjectNode(); byte[] iccBytes = iccData.toByteArray();
PdfStream iccStream = (PdfStream) colorSpaceArray.get(1);
byte[] iccData = iccStream.getBytes(); // TODO: Further decode and analyze the ICC data if needed
// TODO: Further decode and analyze the ICC data if needed ObjectNode iccProfileNode = objectMapper.createObjectNode();
iccProfileNode.put("ICC Profile Length", iccData.length); iccProfileNode.put("ICC Profile Length", iccBytes.length);
colorSpacesArray.add(iccProfileNode); colorSpacesArray.add(iccProfileNode);
}
}
} }
} }
pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray); pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray);
// Other XObjects // Other XObjects
Map<String, Integer> xObjectCountMap = new HashMap<>(); // To store the count for each type Map<String, Integer> xObjectCountMap = new HashMap<>(); // To store the count for each type
PdfDictionary xObjects = resourcesDict.getAsDictionary(PdfName.XObject); for (COSName name : resources.getXObjectNames()) {
if (xObjects != null) { PDXObject xObject = resources.getXObject(name);
for (PdfName name : xObjects.keySet()) { String xObjectType;
PdfStream xObjectStream = xObjects.getAsStream(name);
String xObjectType = xObjectStream.getAsName(PdfName.Subtype).toString(); if (xObject instanceof PDImageXObject) {
xObjectType = "Image";
// Increment the count for this type in the map } else if (xObject instanceof PDFormXObject) {
xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1); xObjectType = "Form";
} else {
xObjectType = "Other";
} }
// Increment the count for this type in the map
xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1);
} }
// Add the count map to pageInfo (or wherever you want to store it) // Add the count map to pageInfo (or wherever you want to store it)
@ -598,14 +605,17 @@ public class GetInfoOnPDF {
ArrayNode multimediaArray = objectMapper.createArrayNode(); ArrayNode multimediaArray = objectMapper.createArrayNode();
for (PdfAnnotation annotation : annotations) {
if (PdfName.RichMedia.equals(annotation.getSubtype())) { for (PDAnnotation annotation : annotations) {
if ("RichMedia".equals(annotation.getSubtype())) {
ObjectNode multimediaNode = objectMapper.createObjectNode(); ObjectNode multimediaNode = objectMapper.createObjectNode();
// Extract details from the dictionary as needed // Extract details from the annotation as needed
multimediaArray.add(multimediaNode); multimediaArray.add(multimediaNode);
} }
} }
pageInfo.set("Multimedia", multimediaArray); pageInfo.set("Multimedia", multimediaArray);
@ -636,17 +646,21 @@ public class GetInfoOnPDF {
return null; return null;
} }
private static void addOutlinesToArray(PdfOutline outline, ArrayNode arrayNode) { private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
if (outline == null) return; if (outline == null) return;
ObjectNode outlineNode = objectMapper.createObjectNode(); ObjectNode outlineNode = objectMapper.createObjectNode();
outlineNode.put("Title", outline.getTitle()); outlineNode.put("Title", outline.getTitle());
// You can add other properties if needed // You can add other properties if needed
arrayNode.add(outlineNode); arrayNode.add(outlineNode);
for (PdfOutline child : outline.getAllChildren()) { PDOutlineItem child = outline.getFirstChild();
while (child != null) {
addOutlinesToArray(child, arrayNode); addOutlinesToArray(child, arrayNode);
child = child.getNextSibling();
} }
} }
public String getPageOrientation(double width, double height) { public String getPageOrientation(double width, double height) {
if (width > height) { if (width > height) {
return "Landscape"; return "Landscape";
@ -678,45 +692,33 @@ public class GetInfoOnPDF {
return Math.abs(pageAspectRatio - aspectRatio) <= 0.05; return Math.abs(pageAspectRatio - aspectRatio) <= 0.05;
} }
public boolean checkForStandard(PdfDocument document, String standardKeyword) {
// Check Output Intents
boolean foundInOutputIntents = checkOutputIntent(document, standardKeyword);
if (foundInOutputIntents) return true;
// Check XMP Metadata (rudimentary)
try {
byte[] metadataBytes = document.getXmpMetadata();
if (metadataBytes != null) {
XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(metadataBytes);
String xmpString = xmpMeta.dumpObject();
if (xmpString.contains(standardKeyword)) {
return true;
}
}
} catch (XMPException e) {
e.printStackTrace();
}
return false;
}
public boolean checkOutputIntent(PdfDocument document, String standard) { public static boolean checkForStandard(PDDocument document, String standardKeyword) {
PdfArray outputIntents = document.getCatalog().getPdfObject().getAsArray(PdfName.OutputIntents); // Check XMP Metadata
if (outputIntents != null && !outputIntents.isEmpty()) { try {
for (int i = 0; i < outputIntents.size(); i++) { PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
PdfDictionary outputIntentDict = outputIntents.getAsDictionary(i); if (pdMetadata != null) {
if (outputIntentDict != null) { COSInputStream metaStream = pdMetadata.createInputStream();
PdfString s = outputIntentDict.getAsString(PdfName.S); DomXmpParser domXmpParser = new DomXmpParser();
if (s != null && s.toString().contains(standard)) { XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
return true;
} ByteArrayOutputStream baos = new ByteArrayOutputStream();
} new XmpSerializer().serialize(xmpMeta, baos, true);
String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8);
if (xmpString.contains(standardKeyword)) {
return true;
} }
} }
return false; } catch (Exception e) { // Catching general exception for brevity, ideally you'd catch specific exceptions.
e.printStackTrace();
} }
return false;
}
public ArrayNode exploreStructureTree(List<Object> nodes) { public ArrayNode exploreStructureTree(List<Object> nodes) {
ArrayNode elementsArray = objectMapper.createArrayNode(); ArrayNode elementsArray = objectMapper.createArrayNode();
if (nodes != null) { if (nodes != null) {
@ -771,7 +773,7 @@ public class GetInfoOnPDF {
} }
} }
private String getPageModeDescription(PdfName pageMode) { private String getPageModeDescription(String pageMode) {
return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown"; return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown";
} }
} }

View file

@ -104,7 +104,6 @@ public class ApplicationProperties {
} }
public static class Security { public static class Security {
private Boolean enableLogin; private Boolean enableLogin;
private InitialLogin initialLogin;
private Boolean csrfDisabled; private Boolean csrfDisabled;
public Boolean getEnableLogin() { public Boolean getEnableLogin() {
@ -115,14 +114,6 @@ public class ApplicationProperties {
this.enableLogin = enableLogin; this.enableLogin = enableLogin;
} }
public InitialLogin getInitialLogin() {
return initialLogin != null ? initialLogin : new InitialLogin();
}
public void setInitialLogin(InitialLogin initialLogin) {
this.initialLogin = initialLogin;
}
public Boolean getCsrfDisabled() { public Boolean getCsrfDisabled() {
return csrfDisabled; return csrfDisabled;
} }
@ -134,40 +125,9 @@ public class ApplicationProperties {
@Override @Override
public String toString() { public String toString() {
return "Security [enableLogin=" + enableLogin + ", initialLogin=" + initialLogin + ", csrfDisabled=" return "Security [enableLogin=" + enableLogin + ", csrfDisabled="
+ csrfDisabled + "]"; + csrfDisabled + "]";
} }
public static class InitialLogin {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "InitialLogin [username=" + username + ", password=" + (password != null && !password.isEmpty() ? "MASKED" : "NULL") + "]";
}
}
} }
public static class System { public static class System {

View file

@ -65,7 +65,8 @@ public class GeneralUtils {
} else if (sizeStr.endsWith("B")) { } else if (sizeStr.endsWith("B")) {
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
} else { } else {
// Input string does not have a valid format, handle this case // Assume MB if no unit is specified
return (long) (Double.parseDouble(sizeStr) * 1024 * 1024);
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// The numeric part of the input string cannot be parsed, handle this case // The numeric part of the input string cannot be parsed, handle this case

View file

@ -4,15 +4,11 @@
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
initialLogin:
username: 'username' # Specify the initial username for first boot (e.g. 'admin')
password: 'password' # Specify the initial password for first boot (e.g. 'password123')
csrfDisabled: true csrfDisabled: true
system: system:
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
googlevisibility: false # 'true' to allow Google visibility, 'false' to disallow googlevisibility: false # 'true' to allow Google visibility, 'false' to disallow
rootURIPath: / # Set the application's root URI (e.g. /pdf-app)
customStaticFilePath: '/customFiles/static/' # Directory path for custom static files customStaticFilePath: '/customFiles/static/' # Directory path for custom static files
#ui: #ui: