compress finalise, cert test, locale test
This commit is contained in:
parent
9d80458250
commit
3cad43006a
7 changed files with 195 additions and 28 deletions
|
@ -24,8 +24,9 @@ dependencies {
|
|||
|
||||
//general PDF
|
||||
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 'io.micrometer:micrometer-core'
|
||||
|
||||
|
|
|
@ -2,8 +2,10 @@ package stirling.software.SPDF.config;
|
|||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
@ -50,4 +52,15 @@ public class Beans implements WebMvcConfigurer {
|
|||
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;
|
||||
if (expectedOutputSizeString != null) {
|
||||
boolean autoMode = false;
|
||||
if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1 ) {
|
||||
expectedOutputSize = PdfUtils.convertSizeToBytes(expectedOutputSizeString);
|
||||
autoMode = true;
|
||||
}
|
||||
|
||||
// Save the uploaded file to a temporary location
|
||||
|
@ -69,8 +71,8 @@ public class CompressController {
|
|||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Determine initial optimization level based on expected size reduction, only if optimizeLevel is not provided
|
||||
if(optimizeLevel == null) {
|
||||
// Determine initial optimization level based on expected size reduction, only if in autoMode
|
||||
if(autoMode) {
|
||||
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
|
||||
if (sizeReductionRatio > 0.7) {
|
||||
optimizeLevel = 1;
|
||||
|
@ -79,12 +81,12 @@ public class CompressController {
|
|||
} else if (sizeReductionRatio > 0.35) {
|
||||
optimizeLevel = 3;
|
||||
} else {
|
||||
optimizeLevel = 4;
|
||||
optimizeLevel = 3;
|
||||
}
|
||||
}
|
||||
|
||||
boolean sizeMet = expectedOutputSize == 0L;
|
||||
while (!sizeMet && optimizeLevel <= 5) {
|
||||
boolean sizeMet = false;
|
||||
while (!sizeMet && optimizeLevel <= 4) {
|
||||
// Prepare the Ghostscript command
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("gs");
|
||||
|
@ -99,12 +101,9 @@ public class CompressController {
|
|||
command.add("-dPDFSETTINGS=/printer");
|
||||
break;
|
||||
case 3:
|
||||
command.add("-dPDFSETTINGS=/default");
|
||||
break;
|
||||
case 4:
|
||||
command.add("-dPDFSETTINGS=/ebook");
|
||||
break;
|
||||
case 5:
|
||||
case 4:
|
||||
command.add("-dPDFSETTINGS=/screen");
|
||||
break;
|
||||
default:
|
||||
|
@ -119,20 +118,27 @@ public class CompressController {
|
|||
|
||||
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);
|
||||
if (outputFileSize <= expectedOutputSize) {
|
||||
if (outputFileSize <= expectedOutputSize || !autoMode) {
|
||||
sizeMet = true;
|
||||
} else {
|
||||
// Increase optimization level for next iteration
|
||||
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);
|
||||
if (outputFileSize > expectedOutputSize) {
|
||||
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();
|
||||
try {
|
||||
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")) {
|
||||
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")) {
|
||||
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")) {
|
||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
||||
} else {
|
||||
|
@ -314,4 +314,5 @@ public class PdfUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -223,8 +223,11 @@ fileToPDF.submit=Convert to PDF
|
|||
compress.title=Compress
|
||||
compress.header=Compress PDF
|
||||
compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation.
|
||||
compress.selectText.1=Optimization level:
|
||||
compress.selectText.2=Expected PDF Size (e.g. 100MB, 25KB, 500B)
|
||||
compress.selectText.1=Manual Mode - From 1 to 4
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">R
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<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 class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h4>Manual Mode - From 1 to 5</h4>
|
||||
<label for="optimizeLevel" th:text="#{compress.selectText.1}"></label>
|
||||
<h4 th:text="#{compress.selectText.1}"></h4>
|
||||
<label for="optimizeLevel" th:text="#{compress.selectText.2}"></label>
|
||||
<select name="optimizeLevel" id="optimizeLevel" class="form-control">
|
||||
<option value="1">1</option>
|
||||
<option value="2" selected>2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="4" th:text="#{compress.selectText.3}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h4>Auto mode - Auto adjusts quality to get PDF to exact size</h4>
|
||||
<label for="expectedOutputSize" th:text="#{compress.selectText.2}"></label>
|
||||
<h4 th:text="#{compress.selectText.4}"></h4>
|
||||
<label for="expectedOutputSize" th:text="#{compress.selectText.5}"></label>
|
||||
<input type="text" name="expectedOutputSize" id="expectedOutputSize" min="1" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue