mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2024-11-16 20:30:11 +01:00
compress finalise, cert test, locale test
This commit is contained in:
parent
9d80458250
commit
3cad43006a
@ -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'
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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()))) {
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
@ -314,4 +314,5 @@ public class PdfUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user