compress finalise, cert test, locale test

This commit is contained in:
Anthony Stirling 2023-05-20 22:48:59 +01:00
parent 9d80458250
commit 3cad43006a
7 changed files with 195 additions and 28 deletions

View file

@ -24,8 +24,9 @@ dependencies {
//general PDF //general PDF
implementation 'org.apache.pdfbox:pdfbox:2.0.28' implementation 'org.apache.pdfbox:pdfbox:2.0.28'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
implementation 'com.itextpdf:itext7-core:7.2.5'
implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-core' implementation 'io.micrometer:micrometer-core'

View file

@ -2,8 +2,10 @@ package stirling.software.SPDF.config;
import java.util.Locale; import java.util.Locale;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -50,4 +52,15 @@ public class Beans implements WebMvcConfigurer {
return slr; return slr;
} }
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
//messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultLocale(Locale.UK); // setting default locale
return messageSource;
}
} }

View file

@ -56,8 +56,10 @@ public class CompressController {
} }
Long expectedOutputSize = 0L; Long expectedOutputSize = 0L;
if (expectedOutputSizeString != null) { boolean autoMode = false;
if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1 ) {
expectedOutputSize = PdfUtils.convertSizeToBytes(expectedOutputSizeString); expectedOutputSize = PdfUtils.convertSizeToBytes(expectedOutputSizeString);
autoMode = true;
} }
// Save the uploaded file to a temporary location // Save the uploaded file to a temporary location
@ -69,8 +71,8 @@ public class CompressController {
// Prepare the output file path // Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf"); Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Determine initial optimization level based on expected size reduction, only if optimizeLevel is not provided // Determine initial optimization level based on expected size reduction, only if in autoMode
if(optimizeLevel == null) { if(autoMode) {
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize; double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
if (sizeReductionRatio > 0.7) { if (sizeReductionRatio > 0.7) {
optimizeLevel = 1; optimizeLevel = 1;
@ -79,12 +81,12 @@ public class CompressController {
} else if (sizeReductionRatio > 0.35) { } else if (sizeReductionRatio > 0.35) {
optimizeLevel = 3; optimizeLevel = 3;
} else { } else {
optimizeLevel = 4; optimizeLevel = 3;
} }
} }
boolean sizeMet = expectedOutputSize == 0L; boolean sizeMet = false;
while (!sizeMet && optimizeLevel <= 5) { while (!sizeMet && optimizeLevel <= 4) {
// Prepare the Ghostscript command // Prepare the Ghostscript command
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
command.add("gs"); command.add("gs");
@ -99,12 +101,9 @@ public class CompressController {
command.add("-dPDFSETTINGS=/printer"); command.add("-dPDFSETTINGS=/printer");
break; break;
case 3: case 3:
command.add("-dPDFSETTINGS=/default");
break;
case 4:
command.add("-dPDFSETTINGS=/ebook"); command.add("-dPDFSETTINGS=/ebook");
break; break;
case 5: case 4:
command.add("-dPDFSETTINGS=/screen"); command.add("-dPDFSETTINGS=/screen");
break; break;
default: default:
@ -119,20 +118,27 @@ public class CompressController {
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command);
// Check if file size is within expected size // Check if file size is within expected size or not auto mode so instantly finish
long outputFileSize = Files.size(tempOutputFile); long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize <= expectedOutputSize) { if (outputFileSize <= expectedOutputSize || !autoMode) {
sizeMet = true; sizeMet = true;
} else { } else {
// Increase optimization level for next iteration // Increase optimization level for next iteration
optimizeLevel++; optimizeLevel++;
System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel); if(autoMode && optimizeLevel > 3) {
System.out.println("Skipping level 4 due to bad results in auto mode");
sizeMet = true;
} else if(optimizeLevel == 5) {
} else {
System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel);
}
} }
} }
if (expectedOutputSize != null) { if (expectedOutputSize != null && autoMode) {
long outputFileSize = Files.size(tempOutputFile); long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize > expectedOutputSize) { if (outputFileSize > expectedOutputSize) {
try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) { try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) {

View file

@ -0,0 +1,144 @@
package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner;
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.HttpStatus;
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 com.itextpdf.kernel.pdf.*;
import com.itextpdf.signatures.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@RestController
public class CertSignController {
private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
static {
Security.addProvider(new BouncyCastleProvider());
}
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
public ResponseEntity<String> signPDF(
@RequestParam("pdf") MultipartFile pdf,
@RequestParam(value = "key", required = false) MultipartFile privateKeyFile,
@RequestParam(value = "cert", required = false) MultipartFile certFile,
@RequestParam(value = "p12", required = false) MultipartFile p12File,
@RequestParam(value = "password", required = false) String password) throws Exception {
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
PrivateKey privateKey = null;
X509Certificate cert = null;
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);
} else {
// 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()));
}
}
// Set up the PDF reader and stamper
PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.getBytes()));
ByteArrayOutputStream signedPdf = new ByteArrayOutputStream();
PdfSigner signer = new PdfSigner(reader, signedPdf, new StampingProperties());
// Set up the signing appearance
PdfSignatureAppearance appearance = signer.getSignatureAppearance()
.setReason("Test")
.setLocation("TestLocation");
// Set up the signer
PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
IExternalDigest digest = new BouncyCastleDigest();
// Call iTex7 to sign the PDF
signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
// This is just an example, you might want to save this signed PDF into your system or send it back in the response.
// For simplicity, we will just print out the size of the signed PDF.
System.out.println("Signed PDF size: " + signedPdf.size());
return ResponseEntity.ok("Signed PDF successfully");
}
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

@ -297,11 +297,11 @@ public class PdfUtils {
sizeStr = sizeStr.trim().toUpperCase(); sizeStr = sizeStr.trim().toUpperCase();
try { try {
if (sizeStr.endsWith("KB")) { if (sizeStr.endsWith("KB")) {
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024; return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
} else if (sizeStr.endsWith("MB")) { } else if (sizeStr.endsWith("MB")) {
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024; return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024);
} else if (sizeStr.endsWith("GB")) { } else if (sizeStr.endsWith("GB")) {
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024; return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024);
} 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 {
@ -313,5 +313,6 @@ public class PdfUtils {
return null; return null;
} }
} }

View file

@ -223,8 +223,11 @@ fileToPDF.submit=Convert to PDF
compress.title=Compress compress.title=Compress
compress.header=Compress PDF compress.header=Compress PDF
compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation. compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation.
compress.selectText.1=Optimization level: compress.selectText.1=Manual Mode - From 1 to 4
compress.selectText.2=Expected PDF Size (e.g. 100MB, 25KB, 500B) compress.selectText.2=Optimization level:
compress.selectText.3=4 (Terrible for text images)
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB)
compress.submit=Compress compress.submit=Compress

View file

@ -11,7 +11,7 @@
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br> <br>
<div class="container">R <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{compress.header}"></h2> <h2 th:text="#{compress.header}"></h2>
@ -19,22 +19,21 @@
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<h4>Manual Mode - From 1 to 5</h4> <h4 th:text="#{compress.selectText.1}"></h4>
<label for="optimizeLevel" th:text="#{compress.selectText.1}"></label> <label for="optimizeLevel" th:text="#{compress.selectText.2}"></label>
<select name="optimizeLevel" id="optimizeLevel" class="form-control"> <select name="optimizeLevel" id="optimizeLevel" class="form-control">
<option value="1">1</option> <option value="1">1</option>
<option value="2" selected>2</option> <option value="2" selected>2</option>
<option value="3">3</option> <option value="3">3</option>
<option value="4">4</option> <option value="4" th:text="#{compress.selectText.3}"></option>
<option value="5">5</option>
</select> </select>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<h4>Auto mode - Auto adjusts quality to get PDF to exact size</h4> <h4 th:text="#{compress.selectText.4}"></h4>
<label for="expectedOutputSize" th:text="#{compress.selectText.2}"></label> <label for="expectedOutputSize" th:text="#{compress.selectText.5}"></label>
<input type="text" name="expectedOutputSize" id="expectedOutputSize" min="1" class="form-control"> <input type="text" name="expectedOutputSize" id="expectedOutputSize" min="1" class="form-control">
</div> </div>
</div> </div>