mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
Cleanup work + stream endpoints to reduce memory usage (#6106)
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
@@ -10,6 +12,7 @@ import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -143,6 +146,24 @@ public class FileStorage {
|
||||
return new StoredFile(fileId, size);
|
||||
}
|
||||
|
||||
public String storeFromStreamingBody(StreamingResponseBody body, String originalName)
|
||||
throws IOException {
|
||||
String fileId = generateFileId();
|
||||
Path filePath = getFilePath(fileId);
|
||||
Files.createDirectories(filePath.getParent());
|
||||
boolean success = false;
|
||||
try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(filePath))) {
|
||||
body.writeTo(os);
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
Files.deleteIfExists(filePath);
|
||||
}
|
||||
}
|
||||
log.debug("Stored StreamingResponseBody with ID: {}", fileId);
|
||||
return fileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file by its ID
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -305,33 +306,21 @@ public class JobExecutorService {
|
||||
Object body = response.getBody();
|
||||
|
||||
if (body instanceof byte[]) {
|
||||
// Extract filename from content-disposition header if available
|
||||
String filename = "result.pdf";
|
||||
String contentType = MediaType.APPLICATION_PDF_VALUE;
|
||||
String filename = extractResponseFilename(response);
|
||||
String contentType = extractResponseContentType(response);
|
||||
|
||||
if (response.getHeaders().getContentDisposition() != null) {
|
||||
String disposition =
|
||||
response.getHeaders().getContentDisposition().toString();
|
||||
if (disposition.contains("filename=")) {
|
||||
filename =
|
||||
disposition.substring(
|
||||
disposition.indexOf("filename=") + 9,
|
||||
disposition.lastIndexOf('"'));
|
||||
}
|
||||
}
|
||||
|
||||
MediaType mediaType = response.getHeaders().getContentType();
|
||||
|
||||
if (mediaType != null) {
|
||||
contentType = mediaType.toString();
|
||||
}
|
||||
|
||||
// Store byte array directly to disk
|
||||
String fileId = fileStorage.storeBytes((byte[]) body, filename);
|
||||
taskManager.setFileResult(jobId, fileId, filename, contentType);
|
||||
log.debug("Stored ResponseEntity<byte[]> result with fileId: {}", fileId);
|
||||
} else if (body instanceof StreamingResponseBody streamingBody) {
|
||||
String filename = extractResponseFilename(response);
|
||||
String contentType = extractResponseContentType(response);
|
||||
|
||||
// Let the GC handle the memory naturally
|
||||
String fileId = fileStorage.storeFromStreamingBody(streamingBody, filename);
|
||||
taskManager.setFileResult(jobId, fileId, filename, contentType);
|
||||
log.debug(
|
||||
"Stored ResponseEntity<StreamingResponseBody> result with fileId: {}",
|
||||
fileId);
|
||||
} else {
|
||||
// Check if the response body contains a fileId
|
||||
if (body != null && body.toString().contains("fileId")) {
|
||||
@@ -481,6 +470,21 @@ public class JobExecutorService {
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractResponseFilename(ResponseEntity<?> response) {
|
||||
if (response.getHeaders().getContentDisposition() != null) {
|
||||
String filename = response.getHeaders().getContentDisposition().getFilename();
|
||||
if (filename != null && !filename.isEmpty()) {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
return "result.pdf";
|
||||
}
|
||||
|
||||
private static String extractResponseContentType(ResponseEntity<?> response) {
|
||||
MediaType mediaType = response.getHeaders().getContentType();
|
||||
return mediaType != null ? mediaType.toString() : MediaType.APPLICATION_PDF_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse session timeout string (e.g., "30m", "1h") to milliseconds
|
||||
*
|
||||
|
||||
@@ -401,7 +401,7 @@ public class JobQueue implements SmartLifecycle {
|
||||
* @throws Exception If there is an execution error
|
||||
*/
|
||||
private <T> T executeWithTimeout(Supplier<T> supplier, long timeoutMs) throws Exception {
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier);
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier, jobExecutor);
|
||||
|
||||
try {
|
||||
if (timeoutMs <= 0) {
|
||||
|
||||
@@ -7,11 +7,8 @@ import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -94,21 +91,12 @@ public class PostHogService {
|
||||
metrics.put("os_name", System.getProperty("os.name"));
|
||||
metrics.put("os_version", System.getProperty("os.version"));
|
||||
metrics.put("java_version", System.getProperty("java.version"));
|
||||
metrics.put("user_name", System.getProperty("user.name"));
|
||||
metrics.put("user_home", System.getProperty("user.home"));
|
||||
metrics.put("user_dir", System.getProperty("user.dir"));
|
||||
|
||||
// CPU and Memory
|
||||
metrics.put("cpu_cores", Runtime.getRuntime().availableProcessors());
|
||||
metrics.put("total_memory", Runtime.getRuntime().totalMemory());
|
||||
metrics.put("free_memory", Runtime.getRuntime().freeMemory());
|
||||
|
||||
// Network and Server Identity
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
metrics.put("ip_address", localHost.getHostAddress());
|
||||
metrics.put("hostname", localHost.getHostName());
|
||||
metrics.put("mac_address", getMacAddress());
|
||||
|
||||
// JVM info
|
||||
metrics.put("jvm_vendor", System.getProperty("java.vendor"));
|
||||
metrics.put("jvm_version", System.getProperty("java.vm.version"));
|
||||
@@ -153,9 +141,6 @@ public class PostHogService {
|
||||
metrics.put("gc_" + gcBean.getName() + "_time", gcBean.getCollectionTime());
|
||||
}
|
||||
|
||||
// Network interfaces
|
||||
metrics.put("network_interfaces", getNetworkInterfacesInfo());
|
||||
|
||||
// Docker detection and stats
|
||||
boolean isDocker = isRunningInDocker();
|
||||
if (isDocker) {
|
||||
@@ -353,30 +338,6 @@ public class PostHogService {
|
||||
.getProFeatures()
|
||||
.getCustomMetadata()
|
||||
.isAutoUpdateMetadata());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_customMetadata_author",
|
||||
applicationProperties
|
||||
.getPremium()
|
||||
.getProFeatures()
|
||||
.getCustomMetadata()
|
||||
.getAuthor());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_customMetadata_creator",
|
||||
applicationProperties
|
||||
.getPremium()
|
||||
.getProFeatures()
|
||||
.getCustomMetadata()
|
||||
.getCreator());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_customMetadata_producer",
|
||||
applicationProperties
|
||||
.getPremium()
|
||||
.getProFeatures()
|
||||
.getCustomMetadata()
|
||||
.getProducer());
|
||||
}
|
||||
// Capture AutoPipeline properties
|
||||
addIfNotEmpty(
|
||||
@@ -386,39 +347,4 @@ public class PostHogService {
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private String getMacAddress() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> networkInterfaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
while (networkInterfaces.hasMoreElements()) {
|
||||
NetworkInterface ni = networkInterfaces.nextElement();
|
||||
byte[] hardwareAddress = ni.getHardwareAddress();
|
||||
if (hardwareAddress != null) {
|
||||
String[] hexadecimal = new String[hardwareAddress.length];
|
||||
for (int i = 0; i < hardwareAddress.length; i++) {
|
||||
hexadecimal[i] = String.format("%02X", hardwareAddress[i]);
|
||||
}
|
||||
return String.join("-", hexadecimal);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Handle exception
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private Map<String, String> getNetworkInterfacesInfo() {
|
||||
Map<String, String> interfacesInfo = new HashMap<>();
|
||||
try {
|
||||
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||
while (nets.hasMoreElements()) {
|
||||
NetworkInterface netint = nets.nextElement();
|
||||
interfacesInfo.put(netint.getName(), netint.getDisplayName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
interfacesInfo.put("error", e.getMessage());
|
||||
}
|
||||
return interfacesInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
@@ -66,16 +65,7 @@ public class FileToPdf {
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile.getPath());
|
||||
try {
|
||||
return pdfBytes;
|
||||
} catch (Exception e) {
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile.getPath());
|
||||
if (pdfBytes.length < 1) {
|
||||
throw e;
|
||||
}
|
||||
return pdfBytes;
|
||||
}
|
||||
return Files.readAllBytes(tempOutputFile.getPath());
|
||||
} // tempInputFile auto-closed
|
||||
} // tempOutputFile auto-closed
|
||||
}
|
||||
@@ -92,8 +82,7 @@ public class FileToPdf {
|
||||
throws IOException {
|
||||
try (TempDirectory tempUnzippedDir = new TempDirectory(tempFileManager)) {
|
||||
try (ZipInputStream zipIn =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) {
|
||||
ZipSecurity.createHardenedInputStream(Files.newInputStream(zipFilePath))) {
|
||||
ZipEntry entry = zipIn.getNextEntry();
|
||||
while (entry != null) {
|
||||
Path filePath =
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -20,6 +20,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
|
||||
import com.vladsch.flexmark.util.data.MutableDataSet;
|
||||
@@ -48,7 +49,7 @@ public class PDFToFile {
|
||||
this.runtimePathConfig = runtimePathConfig;
|
||||
}
|
||||
|
||||
public ResponseEntity<byte[]> processPdfToMarkdown(MultipartFile inputFile)
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToMarkdown(MultipartFile inputFile)
|
||||
throws IOException, InterruptedException {
|
||||
if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) {
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
@@ -85,78 +86,77 @@ public class PDFToFile {
|
||||
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
byte[] fileBytes;
|
||||
String fileName;
|
||||
String fileName = pdfBaseName + "ToMarkdown.zip";
|
||||
TempFile finalOut = tempFileManager.createManagedTempFile(".zip");
|
||||
try {
|
||||
try (TempFile tempInputFile = new TempFile(tempFileManager, ".pdf");
|
||||
TempDirectory tempOutputDir = new TempDirectory(tempFileManager)) {
|
||||
inputFile.transferTo(tempInputFile.getFile());
|
||||
|
||||
try (TempFile tempInputFile = new TempFile(tempFileManager, ".pdf");
|
||||
TempDirectory tempOutputDir = new TempDirectory(tempFileManager)) {
|
||||
inputFile.transferTo(tempInputFile.getFile());
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"pdftohtml",
|
||||
"-s",
|
||||
"-noframes",
|
||||
"-c",
|
||||
tempInputFile.getAbsolutePath(),
|
||||
pdfBaseName));
|
||||
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"pdftohtml",
|
||||
"-s",
|
||||
"-noframes",
|
||||
"-c",
|
||||
tempInputFile.getAbsolutePath(),
|
||||
pdfBaseName));
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
|
||||
.runCommandWithOutputHandling(
|
||||
command, tempOutputDir.getPath().toFile());
|
||||
// Process HTML files to Markdown
|
||||
File[] outputFiles =
|
||||
Objects.requireNonNull(tempOutputDir.getPath().toFile().listFiles());
|
||||
List<File> markdownFiles = new ArrayList<>();
|
||||
List<File> imageFiles = new ArrayList<>();
|
||||
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
|
||||
.runCommandWithOutputHandling(
|
||||
command, tempOutputDir.getPath().toFile());
|
||||
// Process HTML files to Markdown
|
||||
File[] outputFiles =
|
||||
Objects.requireNonNull(tempOutputDir.getPath().toFile().listFiles());
|
||||
List<File> markdownFiles = new ArrayList<>();
|
||||
List<File> imageFiles = new ArrayList<>();
|
||||
// Convert HTML files to Markdown and collect image files
|
||||
for (File outputFile : outputFiles) {
|
||||
if (outputFile.getName().endsWith(".html")) {
|
||||
String html = Files.readString(outputFile.toPath());
|
||||
String markdown = htmlToMarkdownConverter.convert(html);
|
||||
|
||||
// Convert HTML files to Markdown and collect image files
|
||||
for (File outputFile : outputFiles) {
|
||||
if (outputFile.getName().endsWith(".html")) {
|
||||
String html = Files.readString(outputFile.toPath());
|
||||
String markdown = htmlToMarkdownConverter.convert(html);
|
||||
// Update image references to point to images/ folder
|
||||
markdown = updateImageReferences(markdown);
|
||||
|
||||
// Update image references to point to images/ folder
|
||||
markdown = updateImageReferences(markdown);
|
||||
String mdFileName = outputFile.getName().replace(".html", ".md");
|
||||
File mdFile = new File(tempOutputDir.getPath().toFile(), mdFileName);
|
||||
Files.writeString(mdFile.toPath(), markdown);
|
||||
markdownFiles.add(mdFile);
|
||||
} else if (!outputFile.getName().endsWith(".md")) {
|
||||
// Collect non-HTML, non-MD files as images/assets
|
||||
imageFiles.add(outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
String mdFileName = outputFile.getName().replace(".html", ".md");
|
||||
File mdFile = new File(tempOutputDir.getPath().toFile(), mdFileName);
|
||||
Files.writeString(mdFile.toPath(), markdown);
|
||||
markdownFiles.add(mdFile);
|
||||
} else if (!outputFile.getName().endsWith(".md")) {
|
||||
// Collect non-HTML, non-MD files as images/assets
|
||||
imageFiles.add(outputFile);
|
||||
try (OutputStream fos = Files.newOutputStream(finalOut.getPath());
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(fos)) {
|
||||
// Add markdown files to root of ZIP
|
||||
for (File mdFile : markdownFiles) {
|
||||
ZipEntry mdEntry = new ZipEntry(mdFile.getName());
|
||||
zipOutputStream.putNextEntry(mdEntry);
|
||||
Files.copy(mdFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
// Add images and other assets to images/ folder
|
||||
for (File imageFile : imageFiles) {
|
||||
ZipEntry assetEntry = new ZipEntry("images/" + imageFile.getName());
|
||||
zipOutputStream.putNextEntry(assetEntry);
|
||||
Files.copy(imageFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always create a ZIP file
|
||||
fileName = pdfBaseName + "ToMarkdown.zip";
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
||||
// Add markdown files to root of ZIP
|
||||
for (File mdFile : markdownFiles) {
|
||||
ZipEntry mdEntry = new ZipEntry(mdFile.getName());
|
||||
zipOutputStream.putNextEntry(mdEntry);
|
||||
Files.copy(mdFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
// Add images and other assets to images/ folder
|
||||
for (File imageFile : imageFiles) {
|
||||
ZipEntry assetEntry = new ZipEntry("images/" + imageFile.getName());
|
||||
zipOutputStream.putNextEntry(assetEntry);
|
||||
Files.copy(imageFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
fileBytes = byteArrayOutputStream.toByteArray();
|
||||
} catch (Exception e) {
|
||||
finalOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.fileToWebResponse(
|
||||
finalOut, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +169,7 @@ public class PDFToFile {
|
||||
return PATTERN.matcher(markdown).replaceAll("$1(images/$2)");
|
||||
}
|
||||
|
||||
public ResponseEntity<byte[]> processPdfToHtml(MultipartFile inputFile)
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToHtml(MultipartFile inputFile)
|
||||
throws IOException, InterruptedException {
|
||||
if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) {
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
@@ -182,56 +182,57 @@ public class PDFToFile {
|
||||
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
byte[] fileBytes;
|
||||
String fileName;
|
||||
String fileName = pdfBaseName + "ToHtml.zip";
|
||||
TempFile finalOut = tempFileManager.createManagedTempFile(".zip");
|
||||
try {
|
||||
try (TempFile inputFileTemp = new TempFile(tempFileManager, ".pdf");
|
||||
TempDirectory outputDirTemp = new TempDirectory(tempFileManager)) {
|
||||
|
||||
try (TempFile inputFileTemp = new TempFile(tempFileManager, ".pdf");
|
||||
TempDirectory outputDirTemp = new TempDirectory(tempFileManager)) {
|
||||
Path tempInputFile = inputFileTemp.getPath();
|
||||
Path tempOutputDir = outputDirTemp.getPath();
|
||||
|
||||
Path tempInputFile = inputFileTemp.getPath();
|
||||
Path tempOutputDir = outputDirTemp.getPath();
|
||||
// Save the uploaded file to a temporary location
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Save the uploaded file to a temporary location
|
||||
inputFile.transferTo(tempInputFile);
|
||||
// Run the pdftohtml command with complex output
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"pdftohtml", "-c", tempInputFile.toString(), pdfBaseName));
|
||||
|
||||
// Run the pdftohtml command with complex output
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"pdftohtml", "-c", tempInputFile.toString(), pdfBaseName));
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
|
||||
.runCommandWithOutputHandling(command, tempOutputDir.toFile());
|
||||
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
|
||||
.runCommandWithOutputHandling(command, tempOutputDir.toFile());
|
||||
// Get output files
|
||||
File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());
|
||||
|
||||
// Get output files
|
||||
File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());
|
||||
|
||||
// Return output files in a ZIP archive
|
||||
fileName = pdfBaseName + "ToHtml.zip";
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
||||
for (File outputFile : outputFiles) {
|
||||
ZipEntry entry = new ZipEntry(outputFile.getName());
|
||||
zipOutputStream.putNextEntry(entry);
|
||||
try (FileInputStream fis = new FileInputStream(outputFile)) {
|
||||
IOUtils.copy(fis, zipOutputStream);
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip entry", e);
|
||||
try (OutputStream fos = Files.newOutputStream(finalOut.getPath());
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(fos)) {
|
||||
for (File outputFile : outputFiles) {
|
||||
ZipEntry entry = new ZipEntry(outputFile.getName());
|
||||
zipOutputStream.putNextEntry(entry);
|
||||
try (FileInputStream fis = new FileInputStream(outputFile)) {
|
||||
IOUtils.copy(fis, zipOutputStream);
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip entry", e);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip", e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip", e);
|
||||
}
|
||||
fileBytes = byteArrayOutputStream.toByteArray();
|
||||
} catch (Exception e) {
|
||||
finalOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.fileToWebResponse(
|
||||
finalOut, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
public ResponseEntity<byte[]> processPdfToOfficeFormat(
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToOfficeFormat(
|
||||
MultipartFile inputFile, String outputFormat, String libreOfficeFilter)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
@@ -257,109 +258,115 @@ public class PDFToFile {
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
byte[] fileBytes;
|
||||
String fileName;
|
||||
|
||||
TempFile finalOut =
|
||||
tempFileManager.createManagedTempFile("." + resolvePrimaryExtension(outputFormat));
|
||||
Path libreOfficeProfile = null;
|
||||
try (TempFile inputFileTemp = new TempFile(tempFileManager, ".pdf");
|
||||
TempDirectory outputDirTemp = new TempDirectory(tempFileManager)) {
|
||||
try {
|
||||
try (TempFile inputFileTemp = new TempFile(tempFileManager, ".pdf");
|
||||
TempDirectory outputDirTemp = new TempDirectory(tempFileManager)) {
|
||||
|
||||
Path tempInputFile = inputFileTemp.getPath();
|
||||
Path tempOutputDir = outputDirTemp.getPath();
|
||||
Path unoOutputFile =
|
||||
tempOutputDir.resolve(
|
||||
pdfBaseName + "." + resolvePrimaryExtension(outputFormat));
|
||||
Path tempInputFile = inputFileTemp.getPath();
|
||||
Path tempOutputDir = outputDirTemp.getPath();
|
||||
Path unoOutputFile =
|
||||
tempOutputDir.resolve(
|
||||
pdfBaseName + "." + resolvePrimaryExtension(outputFormat));
|
||||
|
||||
// Save the uploaded file to a temporary location
|
||||
inputFile.transferTo(tempInputFile);
|
||||
// Save the uploaded file to a temporary location
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Run the LibreOffice command
|
||||
ProcessExecutorResult returnCode = null;
|
||||
IOException unoconvertException = null;
|
||||
// Run the LibreOffice command
|
||||
ProcessExecutorResult returnCode = null;
|
||||
IOException unoconvertException = null;
|
||||
|
||||
if (isUnoConvertEnabled()) {
|
||||
try {
|
||||
List<String> unoCommand =
|
||||
buildUnoConvertCommand(
|
||||
tempInputFile, unoOutputFile, outputFormat, libreOfficeFilter);
|
||||
returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(unoCommand);
|
||||
} catch (IOException e) {
|
||||
unoconvertException = e;
|
||||
log.warn(
|
||||
"Unoconvert command failed ({}). Falling back to soffice command.",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (returnCode == null) {
|
||||
// Run the LibreOffice command as a fallback
|
||||
libreOfficeProfile = Files.createTempDirectory("libreoffice_profile_");
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(runtimePathConfig.getSOfficePath());
|
||||
command.add("-env:UserInstallation=" + libreOfficeProfile.toUri().toString());
|
||||
command.add("--headless");
|
||||
command.add("--nologo");
|
||||
command.add("--infilter=" + libreOfficeFilter);
|
||||
command.add("--convert-to");
|
||||
command.add(outputFormat);
|
||||
command.add("--outdir");
|
||||
command.add(tempOutputDir.toString());
|
||||
command.add(tempInputFile.toString());
|
||||
|
||||
try {
|
||||
returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
} catch (IOException e) {
|
||||
if (unoconvertException != null) {
|
||||
e.addSuppressed(unoconvertException);
|
||||
if (isUnoConvertEnabled()) {
|
||||
try {
|
||||
List<String> unoCommand =
|
||||
buildUnoConvertCommand(
|
||||
tempInputFile,
|
||||
unoOutputFile,
|
||||
outputFormat,
|
||||
libreOfficeFilter);
|
||||
returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(unoCommand);
|
||||
} catch (IOException e) {
|
||||
unoconvertException = e;
|
||||
log.warn(
|
||||
"Unoconvert command failed ({}). Falling back to soffice command.",
|
||||
e.getMessage());
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Get output files
|
||||
List<File> outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());
|
||||
if (returnCode == null) {
|
||||
// Run the LibreOffice command as a fallback
|
||||
libreOfficeProfile = Files.createTempDirectory("libreoffice_profile_");
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(runtimePathConfig.getSOfficePath());
|
||||
command.add("-env:UserInstallation=" + libreOfficeProfile.toUri().toString());
|
||||
command.add("--headless");
|
||||
command.add("--nologo");
|
||||
command.add("--infilter=" + libreOfficeFilter);
|
||||
command.add("--convert-to");
|
||||
command.add(outputFormat);
|
||||
command.add("--outdir");
|
||||
command.add(tempOutputDir.toString());
|
||||
command.add(tempInputFile.toString());
|
||||
|
||||
if (outputFiles.size() == 1) {
|
||||
// Return single output file
|
||||
File outputFile = outputFiles.get(0);
|
||||
if ("txt:Text".equals(outputFormat)) {
|
||||
outputFormat = "txt";
|
||||
}
|
||||
fileName = pdfBaseName + "." + outputFormat;
|
||||
fileBytes = FileUtils.readFileToByteArray(outputFile);
|
||||
} else {
|
||||
// Return output files in a ZIP archive
|
||||
fileName = pdfBaseName + "To" + outputFormat + ".zip";
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
||||
for (File outputFile : outputFiles) {
|
||||
ZipEntry entry = new ZipEntry(outputFile.getName());
|
||||
zipOutputStream.putNextEntry(entry);
|
||||
try (FileInputStream fis = new FileInputStream(outputFile)) {
|
||||
IOUtils.copy(fis, zipOutputStream);
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip entry", e);
|
||||
try {
|
||||
returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
} catch (IOException e) {
|
||||
if (unoconvertException != null) {
|
||||
e.addSuppressed(unoconvertException);
|
||||
}
|
||||
|
||||
zipOutputStream.closeEntry();
|
||||
throw e;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip", e);
|
||||
}
|
||||
|
||||
fileBytes = byteArrayOutputStream.toByteArray();
|
||||
// Get output files
|
||||
List<File> outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());
|
||||
|
||||
if (outputFiles.size() == 1) {
|
||||
// Return single output file
|
||||
File outputFile = outputFiles.get(0);
|
||||
if ("txt:Text".equals(outputFormat)) {
|
||||
outputFormat = "txt";
|
||||
}
|
||||
fileName = pdfBaseName + "." + outputFormat;
|
||||
FileUtils.copyFile(outputFile, finalOut.getFile());
|
||||
} else {
|
||||
// Return output files in a ZIP archive
|
||||
fileName = pdfBaseName + "To" + outputFormat + ".zip";
|
||||
try (OutputStream fos = Files.newOutputStream(finalOut.getPath());
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(fos)) {
|
||||
for (File outputFile : outputFiles) {
|
||||
ZipEntry entry = new ZipEntry(outputFile.getName());
|
||||
zipOutputStream.putNextEntry(entry);
|
||||
try (FileInputStream fis = new FileInputStream(outputFile)) {
|
||||
IOUtils.copy(fis, zipOutputStream);
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip entry", e);
|
||||
}
|
||||
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Exception writing zip", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
finalOut.close();
|
||||
throw e;
|
||||
} finally {
|
||||
if (libreOfficeProfile != null) {
|
||||
FileUtils.deleteQuietly(libreOfficeProfile.toFile());
|
||||
}
|
||||
}
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.fileToWebResponse(
|
||||
finalOut, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
private boolean isUnoConvertEnabled() {
|
||||
|
||||
@@ -73,6 +73,19 @@ public class WebResponseUtils {
|
||||
return baosToWebResponse(baos, docName);
|
||||
}
|
||||
|
||||
public static ResponseEntity<StreamingResponseBody> pdfDocToWebResponse(
|
||||
PDDocument document, String docName, TempFileManager tempFileManager)
|
||||
throws IOException {
|
||||
TempFile tempFile = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
document.save(tempFile.getFile());
|
||||
} catch (IOException e) {
|
||||
tempFile.close();
|
||||
throw e;
|
||||
}
|
||||
return pdfFileToWebResponse(tempFile, docName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a File to a web response (PDF default).
|
||||
*
|
||||
@@ -108,23 +121,37 @@ public class WebResponseUtils {
|
||||
public static ResponseEntity<StreamingResponseBody> fileToWebResponse(
|
||||
TempFile outputTempFile, String docName, MediaType mediaType) throws IOException {
|
||||
|
||||
Path path = outputTempFile.getFile().toPath().normalize();
|
||||
long len = Files.size(path);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(mediaType);
|
||||
headers.setContentLength(len);
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + docName + "\"");
|
||||
try {
|
||||
Path path = outputTempFile.getFile().toPath().normalize();
|
||||
long len = Files.size(path);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(mediaType);
|
||||
headers.setContentLength(len);
|
||||
String encodedDocName =
|
||||
RegexPatternUtils.getInstance()
|
||||
.getPlusSignPattern()
|
||||
.matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8))
|
||||
.replaceAll("%20");
|
||||
headers.setContentDispositionFormData("attachment", encodedDocName);
|
||||
|
||||
StreamingResponseBody body =
|
||||
os -> {
|
||||
try (os) {
|
||||
Files.copy(path, os);
|
||||
os.flush();
|
||||
} finally {
|
||||
outputTempFile.close();
|
||||
}
|
||||
};
|
||||
StreamingResponseBody body =
|
||||
os -> {
|
||||
try (os) {
|
||||
Files.copy(path, os);
|
||||
os.flush();
|
||||
} finally {
|
||||
outputTempFile.close();
|
||||
}
|
||||
};
|
||||
|
||||
return new ResponseEntity<>(body, headers, HttpStatus.OK);
|
||||
return new ResponseEntity<>(body, headers, HttpStatus.OK);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
try {
|
||||
outputTempFile.close();
|
||||
} catch (Exception closeEx) {
|
||||
e.addSuppressed(closeEx);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.ZipSecurity;
|
||||
|
||||
@@ -59,6 +61,19 @@ class PDFToFileTest {
|
||||
.thenAnswer(
|
||||
invocation ->
|
||||
Files.createTempFile("test", invocation.getArgument(0)).toFile());
|
||||
lenient()
|
||||
.when(mockTempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
File f =
|
||||
Files.createTempFile("test", invocation.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = org.mockito.Mockito.mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
lenient().when(tf.getAbsolutePath()).thenReturn(f.getAbsolutePath());
|
||||
return tf;
|
||||
});
|
||||
lenient()
|
||||
.when(mockTempFileManager.createTempDirectory())
|
||||
.thenAnswer(invocation -> Files.createTempDirectory("test"));
|
||||
@@ -68,6 +83,12 @@ class PDFToFileTest {
|
||||
pdfToFile = new PDFToFile(mockTempFileManager, mockRuntimePathConfig);
|
||||
}
|
||||
|
||||
private static byte[] drain(ResponseEntity<StreamingResponseBody> response) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToMarkdown_InvalidContentType() throws IOException, InterruptedException {
|
||||
// Prepare
|
||||
@@ -79,7 +100,7 @@ class PDFToFileTest {
|
||||
"This is not a PDF".getBytes());
|
||||
|
||||
// Execute
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToMarkdown(nonPdfFile);
|
||||
ResponseEntity<StreamingResponseBody> response = pdfToFile.processPdfToMarkdown(nonPdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
@@ -96,7 +117,7 @@ class PDFToFileTest {
|
||||
"This is not a PDF".getBytes());
|
||||
|
||||
// Execute
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToHtml(nonPdfFile);
|
||||
ResponseEntity<StreamingResponseBody> response = pdfToFile.processPdfToHtml(nonPdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
@@ -114,7 +135,7 @@ class PDFToFileTest {
|
||||
"This is not a PDF".getBytes());
|
||||
|
||||
// Execute
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToOfficeFormat(nonPdfFile, "docx", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
@@ -133,7 +154,7 @@ class PDFToFileTest {
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
// Execute with invalid format
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "invalid_format", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
@@ -184,12 +205,14 @@ class PDFToFileTest {
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToMarkdown(pdfFile);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToMarkdown(pdfFile);
|
||||
|
||||
// Verify - should now return a ZIP file instead of plain markdown
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
|
||||
// Verify content disposition indicates a ZIP file
|
||||
assertTrue(
|
||||
@@ -201,7 +224,7 @@ class PDFToFileTest {
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
new java.io.ByteArrayInputStream(bodyBytes))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMdFile = false;
|
||||
boolean foundImageInFolder = false;
|
||||
@@ -275,12 +298,14 @@ class PDFToFileTest {
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToMarkdown(pdfFile);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToMarkdown(pdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
|
||||
// Verify content disposition indicates a zip file
|
||||
assertTrue(
|
||||
@@ -292,7 +317,7 @@ class PDFToFileTest {
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
new java.io.ByteArrayInputStream(bodyBytes))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMdFiles = false;
|
||||
boolean foundImage = false;
|
||||
@@ -352,12 +377,13 @@ class PDFToFileTest {
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToHtml(pdfFile);
|
||||
ResponseEntity<StreamingResponseBody> response = pdfToFile.processPdfToHtml(pdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
|
||||
// Verify content disposition indicates a zip file
|
||||
assertTrue(
|
||||
@@ -369,7 +395,7 @@ class PDFToFileTest {
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
new java.io.ByteArrayInputStream(bodyBytes))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMainHtml = false;
|
||||
boolean foundIndexHtml = false;
|
||||
@@ -437,13 +463,14 @@ class PDFToFileTest {
|
||||
});
|
||||
|
||||
// Execute the method with docx format
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
|
||||
// Verify content disposition has correct filename
|
||||
assertTrue(
|
||||
@@ -508,13 +535,14 @@ class PDFToFileTest {
|
||||
});
|
||||
|
||||
// Execute the method with ODP format
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "odp", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
|
||||
// Verify content disposition for zip file
|
||||
assertTrue(
|
||||
@@ -526,7 +554,7 @@ class PDFToFileTest {
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
new java.io.ByteArrayInputStream(bodyBytes))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMainFile = false;
|
||||
boolean foundMediaFiles = false;
|
||||
@@ -592,13 +620,14 @@ class PDFToFileTest {
|
||||
});
|
||||
|
||||
// Execute the method with text format
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "txt:Text", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
|
||||
// Verify content disposition has txt extension
|
||||
assertTrue(
|
||||
@@ -650,13 +679,14 @@ class PDFToFileTest {
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
|
||||
// Verify content disposition contains output.docx
|
||||
assertTrue(
|
||||
@@ -696,12 +726,13 @@ class PDFToFileTest {
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import");
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
@@ -759,12 +790,13 @@ class PDFToFileTest {
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
ResponseEntity<byte[]> response =
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import");
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
byte[] bodyBytes = drain(response);
|
||||
assertNotNull(bodyBytes);
|
||||
assertTrue(bodyBytes.length > 0);
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
|
||||
@@ -47,7 +47,7 @@ public class OpenApiConfig {
|
||||
.version(version)
|
||||
.license(
|
||||
new License()
|
||||
.name("MIT")
|
||||
.name("Open-Core - MIT Licensed")
|
||||
.url(
|
||||
"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE"))
|
||||
.termsOfService("https://www.stirlingpdf.com/terms")
|
||||
|
||||
@@ -28,7 +28,17 @@ public class SpringDocConfig {
|
||||
"/api/v1/proprietary/ui-data/**",
|
||||
"/api/v1/info/**",
|
||||
"/api/v1/general/job/**",
|
||||
"/api/v1/general/files/**")
|
||||
"/api/v1/general/files/**",
|
||||
"/api/v1/general/signatures/**",
|
||||
"/api/v1/database/**",
|
||||
"/api/v1/storage/**",
|
||||
"/api/v1/proprietary/signatures/**",
|
||||
"/api/v1/workflow/participant/**",
|
||||
"/api/v1/security/cert-sign/sessions",
|
||||
"/api/v1/security/cert-sign/sessions/**",
|
||||
"/api/v1/security/cert-sign/sign-requests",
|
||||
"/api/v1/security/cert-sign/sign-requests/**",
|
||||
"/api/v1/security/cert-sign/validate-certificate")
|
||||
.addOpenApiCustomizer(pdfFileOneOfCustomizer)
|
||||
.addOpenApiCustomizer(
|
||||
openApi -> {
|
||||
@@ -53,7 +63,16 @@ public class SpringDocConfig {
|
||||
"/api/v1/team/**",
|
||||
"/api/v1/auth/**",
|
||||
"/api/v1/invite/**",
|
||||
"/api/v1/audit/**")
|
||||
"/api/v1/audit/**",
|
||||
"/api/v1/database/**",
|
||||
"/api/v1/storage/**",
|
||||
"/api/v1/proprietary/signatures/**",
|
||||
"/api/v1/workflow/participant/**",
|
||||
"/api/v1/security/cert-sign/sessions",
|
||||
"/api/v1/security/cert-sign/sessions/**",
|
||||
"/api/v1/security/cert-sign/sign-requests",
|
||||
"/api/v1/security/cert-sign/sign-requests/**",
|
||||
"/api/v1/security/cert-sign/validate-certificate")
|
||||
.addOpenApiCustomizer(
|
||||
openApi -> {
|
||||
openApi.info(
|
||||
@@ -75,7 +94,8 @@ public class SpringDocConfig {
|
||||
"/api/v1/proprietary/ui-data/**",
|
||||
"/api/v1/info/**",
|
||||
"/api/v1/general/job/**",
|
||||
"/api/v1/general/files/**")
|
||||
"/api/v1/general/files/**",
|
||||
"/api/v1/general/signatures/**")
|
||||
.addOpenApiCustomizer(
|
||||
openApi -> {
|
||||
openApi.info(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -106,25 +105,27 @@ public class TauriProcessMonitor {
|
||||
logger.info("Orphaned Java backend detected. Shutting down gracefully...");
|
||||
|
||||
// Shutdown asynchronously to avoid blocking the monitor thread
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
try {
|
||||
// Give a small delay to ensure logging completes
|
||||
Thread.sleep(1000);
|
||||
Thread.ofVirtual()
|
||||
.name("tauri-graceful-shutdown")
|
||||
.start(
|
||||
() -> {
|
||||
try {
|
||||
// Give a small delay to ensure logging completes
|
||||
Thread.sleep(1000);
|
||||
|
||||
if (applicationContext instanceof ConfigurableApplicationContext) {
|
||||
((ConfigurableApplicationContext) applicationContext).close();
|
||||
} else {
|
||||
// Fallback to system exit
|
||||
logger.warn(
|
||||
"Unable to shutdown Spring context gracefully, using System.exit");
|
||||
System.exit(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during graceful shutdown", e);
|
||||
System.exit(1);
|
||||
}
|
||||
});
|
||||
if (applicationContext instanceof ConfigurableApplicationContext) {
|
||||
((ConfigurableApplicationContext) applicationContext).close();
|
||||
} else {
|
||||
// Fallback to system exit
|
||||
logger.warn(
|
||||
"Unable to shutdown Spring context gracefully, using System.exit");
|
||||
System.exit(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during graceful shutdown", e);
|
||||
System.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
|
||||
+8
-9
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -19,6 +18,7 @@ import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -30,6 +30,7 @@ import stirling.software.SPDF.model.api.general.BookletImpositionRequest;
|
||||
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -39,6 +40,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class BookletImpositionController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(
|
||||
value = "/booklet-imposition",
|
||||
@@ -49,7 +51,7 @@ public class BookletImpositionController {
|
||||
"This operation combines page reordering for booklet printing with multi-page layout. "
|
||||
+ "It rearranges pages in the correct order for booklet printing and places multiple pages "
|
||||
+ "on each sheet for proper folding and binding. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> createBookletImposition(
|
||||
public ResponseEntity<StreamingResponseBody> createBookletImposition(
|
||||
@ModelAttribute BookletImpositionRequest request) throws IOException {
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
@@ -85,15 +87,12 @@ public class BookletImpositionController {
|
||||
duplexPass,
|
||||
flipOnShortEdge)) {
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
newDocument.save(baos);
|
||||
|
||||
byte[] result = baos.toByteArray();
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
newDocument,
|
||||
GeneralUtils.generateFilename(
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename()),
|
||||
"_booklet.pdf"));
|
||||
"_booklet.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||
@@ -18,6 +15,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -32,6 +30,8 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -46,6 +46,7 @@ public class CropController {
|
||||
private static final String PDF_EXTENSION = ".pdf";
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private static int[] detectContentBounds(BufferedImage image) {
|
||||
int width = image.getWidth();
|
||||
@@ -131,7 +132,8 @@ public class CropController {
|
||||
description =
|
||||
"This operation takes an input PDF file and crops it according to the given"
|
||||
+ " coordinates. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm request) throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> cropPdf(@ModelAttribute CropPdfForm request)
|
||||
throws IOException {
|
||||
if (request.isAutoCrop()) {
|
||||
return cropWithAutomaticDetection(request);
|
||||
}
|
||||
@@ -151,8 +153,8 @@ public class CropController {
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> cropWithAutomaticDetection(@ModelAttribute CropPdfForm request)
|
||||
throws IOException {
|
||||
private ResponseEntity<StreamingResponseBody> cropWithAutomaticDetection(
|
||||
@ModelAttribute CropPdfForm request) throws IOException {
|
||||
try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) {
|
||||
|
||||
try (PDDocument newDocument =
|
||||
@@ -196,20 +198,17 @@ public class CropController {
|
||||
cropBounds.height));
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
newDocument.save(baos);
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
newDocument,
|
||||
GeneralUtils.generateFilename(
|
||||
request.getFileInput().getOriginalFilename(), "_cropped.pdf"));
|
||||
request.getFileInput().getOriginalFilename(), "_cropped.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> cropWithPDFBox(@ModelAttribute CropPdfForm request)
|
||||
throws IOException {
|
||||
private ResponseEntity<StreamingResponseBody> cropWithPDFBox(
|
||||
@ModelAttribute CropPdfForm request) throws IOException {
|
||||
try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) {
|
||||
|
||||
try (PDDocument newDocument =
|
||||
@@ -255,22 +254,19 @@ public class CropController {
|
||||
request.getHeight()));
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
newDocument.save(baos);
|
||||
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
newDocument,
|
||||
GeneralUtils.generateFilename(
|
||||
request.getFileInput().getOriginalFilename(), "_cropped.pdf"));
|
||||
request.getFileInput().getOriginalFilename(), "_cropped.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> cropWithGhostscript(@ModelAttribute CropPdfForm request)
|
||||
throws IOException {
|
||||
Path tempInputFile = null;
|
||||
Path tempOutputFile = null;
|
||||
private ResponseEntity<StreamingResponseBody> cropWithGhostscript(
|
||||
@ModelAttribute CropPdfForm request) throws IOException {
|
||||
TempFile tempInputFile = null;
|
||||
TempFile tempOutputFile = null;
|
||||
|
||||
try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) {
|
||||
for (int i = 0; i < sourceDocument.getNumberOfPages(); i++) {
|
||||
@@ -284,11 +280,11 @@ public class CropController {
|
||||
page.setCropBox(cropBox);
|
||||
}
|
||||
|
||||
tempInputFile = Files.createTempFile(TEMP_INPUT_PREFIX, PDF_EXTENSION);
|
||||
tempOutputFile = Files.createTempFile(TEMP_OUTPUT_PREFIX, PDF_EXTENSION);
|
||||
tempInputFile = tempFileManager.createManagedTempFile(PDF_EXTENSION);
|
||||
tempOutputFile = tempFileManager.createManagedTempFile(PDF_EXTENSION);
|
||||
|
||||
// Save the source document with crop boxes
|
||||
sourceDocument.save(tempInputFile.toFile());
|
||||
sourceDocument.save(tempInputFile.getFile());
|
||||
|
||||
// Execute Ghostscript to process the crop boxes
|
||||
ProcessExecutor processExecutor =
|
||||
@@ -299,15 +295,15 @@ public class CropController {
|
||||
"-sDEVICE=pdfwrite",
|
||||
"-dUseCropBox",
|
||||
"-o",
|
||||
tempOutputFile.toString(),
|
||||
tempInputFile.toString());
|
||||
tempOutputFile.getAbsolutePath(),
|
||||
tempInputFile.getAbsolutePath());
|
||||
|
||||
processExecutor.runCommandWithOutputHandling(command);
|
||||
|
||||
byte[] pdfContent = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
TempFile out = tempOutputFile;
|
||||
tempOutputFile = null; // ownership transferred to StreamingResponseBody
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
out,
|
||||
GeneralUtils.generateFilename(
|
||||
request.getFileInput().getOriginalFilename(), "_cropped.pdf"));
|
||||
|
||||
@@ -316,10 +312,10 @@ public class CropController {
|
||||
throw ExceptionUtils.createProcessingInterruptedException("Ghostscript", e);
|
||||
} finally {
|
||||
if (tempInputFile != null) {
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
tempInputFile.close();
|
||||
}
|
||||
if (tempOutputFile != null) {
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
tempOutputFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-10
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -14,6 +13,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -27,6 +27,7 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.GeneralApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
@@ -39,6 +40,7 @@ public class EditTableOfContentsController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(
|
||||
value = "/extract-bookmarks",
|
||||
@@ -149,12 +151,11 @@ public class EditTableOfContentsController {
|
||||
@Operation(
|
||||
summary = "Edit Table of Contents",
|
||||
description = "Add or edit bookmarks/table of contents in a PDF document.")
|
||||
public ResponseEntity<byte[]> editTableOfContents(
|
||||
public ResponseEntity<StreamingResponseBody> editTableOfContents(
|
||||
@ModelAttribute EditTableOfContentsRequest request) throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
try (PDDocument document = pdfDocumentFactory.load(file);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
try (PDDocument document = pdfDocumentFactory.load(file)) {
|
||||
|
||||
// Parse the bookmark data from JSON
|
||||
List<BookmarkItem> bookmarks =
|
||||
@@ -168,13 +169,10 @@ public class EditTableOfContentsController {
|
||||
// Add bookmarks to the outline
|
||||
addBookmarksToOutline(document, outline, bookmarks);
|
||||
|
||||
// Save the document to a byte array
|
||||
document.save(baos);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(file.getOriginalFilename(), "_with_toc.pdf"),
|
||||
MediaType.APPLICATION_PDF);
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
@@ -29,6 +28,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -279,7 +279,7 @@ public class MergeController {
|
||||
"This endpoint merges multiple PDF files into a single PDF file. The merged"
|
||||
+ " file will contain all pages from the input files in the order they were"
|
||||
+ " provided. Input:PDF Output:PDF Type:MISO")
|
||||
public ResponseEntity<byte[]> mergePdfs(
|
||||
public ResponseEntity<StreamingResponseBody> mergePdfs(
|
||||
@ModelAttribute MergePdfsRequest request,
|
||||
@RequestParam(value = "fileOrder", required = false) String fileOrder)
|
||||
throws IOException {
|
||||
@@ -399,12 +399,6 @@ public class MergeController {
|
||||
String mergedFileName =
|
||||
GeneralUtils.generateFilename(firstFilename, "_merged_unsigned.pdf");
|
||||
|
||||
byte[] pdfBytes;
|
||||
try {
|
||||
pdfBytes = Files.readAllBytes(outputTempFile.getPath());
|
||||
} finally {
|
||||
outputTempFile.close();
|
||||
}
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, mergedFileName);
|
||||
return WebResponseUtils.pdfFileToWebResponse(outputTempFile, mergedFileName);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||
@@ -15,6 +14,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -28,6 +28,7 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralFormCopyUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -36,6 +37,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class MultiPageLayoutController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(
|
||||
value = "/multi-page-layout",
|
||||
@@ -45,7 +47,7 @@ public class MultiPageLayoutController {
|
||||
description =
|
||||
"This operation takes an input PDF file and the number of pages to merge into a"
|
||||
+ " single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
||||
public ResponseEntity<StreamingResponseBody> mergeMultiplePagesIntoOne(
|
||||
@ModelAttribute MergeMultiplePagesRequest request) throws IOException {
|
||||
|
||||
int MAX_PAGES = 100000;
|
||||
@@ -338,13 +340,11 @@ public class MultiPageLayoutController {
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
newDocument.save(baos);
|
||||
byte[] result = baos.toByteArray();
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
newDocument,
|
||||
GeneralUtils.generateFilename(
|
||||
file.getOriginalFilename(), "_multi_page_layout.pdf"));
|
||||
file.getOriginalFilename(), "_multi_page_layout.pdf"),
|
||||
tempFileManager);
|
||||
} // newDocument is closed here
|
||||
} // sourceDocument is closed here
|
||||
}
|
||||
|
||||
+18
-9
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -16,6 +15,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -28,6 +28,8 @@ import stirling.software.common.annotations.api.GeneralApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -35,6 +37,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class PdfOverlayController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(value = "/overlay-pdfs", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@StandardPdfResponse
|
||||
@@ -43,8 +46,8 @@ public class PdfOverlayController {
|
||||
description =
|
||||
"Overlay PDF files onto a base PDF with different modes: Sequential,"
|
||||
+ " Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO")
|
||||
public ResponseEntity<byte[]> overlayPdfs(@ModelAttribute OverlayPdfsRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> overlayPdfs(
|
||||
@ModelAttribute OverlayPdfsRequest request) throws IOException {
|
||||
MultipartFile baseFile = request.getFileInput();
|
||||
int overlayPos = request.getOverlayPosition();
|
||||
|
||||
@@ -52,6 +55,7 @@ public class PdfOverlayController {
|
||||
File[] overlayPdfFiles = new File[overlayFiles.length];
|
||||
List<File> tempFiles = new ArrayList<>(); // List to keep track of temporary files
|
||||
|
||||
TempFile tempOut = null;
|
||||
try {
|
||||
for (int i = 0; i < overlayFiles.length; i++) {
|
||||
overlayPdfFiles[i] = GeneralUtils.multipartToFile(overlayFiles[i]);
|
||||
@@ -62,8 +66,7 @@ public class PdfOverlayController {
|
||||
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
|
||||
|
||||
try (PDDocument basePdf = pdfDocumentFactory.load(baseFile);
|
||||
Overlay overlay = new Overlay();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
Overlay overlay = new Overlay()) {
|
||||
Map<Integer, String> overlayGuide =
|
||||
prepareOverlayGuide(
|
||||
basePdf.getNumberOfPages(),
|
||||
@@ -79,15 +82,21 @@ public class PdfOverlayController {
|
||||
overlay.setOverlayPosition(Overlay.Position.BACKGROUND);
|
||||
}
|
||||
|
||||
overlay.overlay(overlayGuide).save(outputStream);
|
||||
byte[] data = outputStream.toByteArray();
|
||||
tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
overlay.overlay(overlayGuide).save(tempOut.getFile());
|
||||
String outputFilename =
|
||||
GeneralUtils.generateFilename(
|
||||
baseFile.getOriginalFilename(), "_overlayed.pdf");
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, outputFilename, MediaType.APPLICATION_PDF);
|
||||
TempFile out = tempOut;
|
||||
tempOut = null; // ownership transferred to StreamingResponseBody
|
||||
return WebResponseUtils.pdfFileToWebResponse(out, outputFilename);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (tempOut != null) {
|
||||
tempOut.close();
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
for (File overlayPdfFile : overlayPdfFiles) {
|
||||
if (overlayPdfFile != null) {
|
||||
|
||||
+11
-6
@@ -12,6 +12,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -27,6 +28,7 @@ import stirling.software.common.annotations.api.GeneralApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -35,6 +37,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class RearrangePagesPDFController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/remove-pages")
|
||||
@StandardPdfResponse
|
||||
@@ -44,8 +47,8 @@ public class RearrangePagesPDFController {
|
||||
"This endpoint removes specified pages from a given PDF file. Users can provide"
|
||||
+ " a comma-separated list of page numbers or ranges to delete. Input:PDF"
|
||||
+ " Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> deletePages(@ModelAttribute PDFWithPageNums request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> deletePages(
|
||||
@ModelAttribute PDFWithPageNums request) throws IOException {
|
||||
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String pagesToDelete = request.getPageNumbers();
|
||||
@@ -67,7 +70,8 @@ public class RearrangePagesPDFController {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
pdfFile.getOriginalFilename(), "_removed_pages.pdf"));
|
||||
pdfFile.getOriginalFilename(), "_removed_pages.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,8 +228,8 @@ public class RearrangePagesPDFController {
|
||||
+ " order or custom mode. Users can provide a page order as a"
|
||||
+ " comma-separated list of page numbers or page ranges, or a custom mode."
|
||||
+ " Input:PDF Output:PDF")
|
||||
public ResponseEntity<byte[]> rearrangePages(@ModelAttribute RearrangePagesRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> rearrangePages(
|
||||
@ModelAttribute RearrangePagesRequest request) throws IOException {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String pageOrder = request.getPageNumbers();
|
||||
String sortType = request.getCustomMode();
|
||||
@@ -264,7 +268,8 @@ public class RearrangePagesPDFController {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
rearrangedDocument,
|
||||
GeneralUtils.generateFilename(
|
||||
pdfFile.getOriginalFilename(), "_rearranged.pdf"));
|
||||
pdfFile.getOriginalFilename(), "_rearranged.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
+6
-2
@@ -9,6 +9,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -21,6 +22,7 @@ import stirling.software.common.annotations.api.GeneralApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -28,6 +30,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class RotationController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/rotate-pdf")
|
||||
@StandardPdfResponse
|
||||
@@ -36,7 +39,7 @@ public class RotationController {
|
||||
description =
|
||||
"This endpoint rotates a given PDF file by a specified angle. The angle must be"
|
||||
+ " a multiple of 90. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> rotatePDF(@ModelAttribute RotatePDFRequest request)
|
||||
public ResponseEntity<StreamingResponseBody> rotatePDF(@ModelAttribute RotatePDFRequest request)
|
||||
throws IOException {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
Integer angle = request.getAngle();
|
||||
@@ -60,7 +63,8 @@ public class RotationController {
|
||||
// Return the rotated PDF as a response
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_rotated.pdf"));
|
||||
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_rotated.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -16,6 +15,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -28,6 +28,7 @@ import stirling.software.common.annotations.api.GeneralApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -36,6 +37,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class ScalePagesController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private static PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
||||
if ("KEEP".equals(targetPDRectangle)) {
|
||||
@@ -118,16 +120,15 @@ public class ScalePagesController {
|
||||
description =
|
||||
"This operation takes an input PDF file and the size to scale the pages to in"
|
||||
+ " the output PDF file. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> scalePages(@ModelAttribute ScalePagesRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> scalePages(
|
||||
@ModelAttribute ScalePagesRequest request) throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String targetPDRectangle = request.getPageSize();
|
||||
float scaleFactor = request.getScaleFactor();
|
||||
|
||||
try (PDDocument sourceDocument = pdfDocumentFactory.load(file);
|
||||
PDDocument outputDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument)) {
|
||||
|
||||
PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument);
|
||||
|
||||
@@ -168,11 +169,10 @@ public class ScalePagesController {
|
||||
}
|
||||
}
|
||||
|
||||
outputDocument.save(baos);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
GeneralUtils.generateFilename(file.getOriginalFilename(), "_scaled.pdf"));
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
outputDocument,
|
||||
GeneralUtils.generateFilename(file.getOriginalFilename(), "_scaled.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
@@ -20,6 +19,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -59,8 +59,8 @@ public class SplitPdfBySectionsController {
|
||||
+ " which page to split, and how to split"
|
||||
+ " ( halves, thirds, quarters, etc.), both vertically and horizontally."
|
||||
+ " Input:PDF Output:ZIP-PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> splitPdf(@Valid @ModelAttribute SplitPdfBySectionsRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> splitPdf(
|
||||
@Valid @ModelAttribute SplitPdfBySectionsRequest request) throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String pageNumbers = request.getPageNumbers();
|
||||
SplitTypes splitMode =
|
||||
@@ -80,9 +80,7 @@ public class SplitPdfBySectionsController {
|
||||
|
||||
if (merge) {
|
||||
try (PDDocument mergedDoc =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(
|
||||
sourceDocument);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument)) {
|
||||
LayerUtility layerUtility = new LayerUtility(mergedDoc);
|
||||
for (int pageIndex = 0;
|
||||
pageIndex < sourceDocument.getNumberOfPages();
|
||||
@@ -99,11 +97,12 @@ public class SplitPdfBySectionsController {
|
||||
addPageToTarget(sourceDocument, pageIndex, mergedDoc, layerUtility);
|
||||
}
|
||||
}
|
||||
mergedDoc.save(baos);
|
||||
return WebResponseUtils.baosToWebResponse(baos, filename + ".pdf");
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
mergedDoc, filename + ".pdf", tempFileManager);
|
||||
}
|
||||
} else {
|
||||
try (TempFile zipTempFile = new TempFile(tempFileManager, ".zip")) {
|
||||
TempFile zipTempFile = tempFileManager.createManagedTempFile(".zip");
|
||||
try {
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(Files.newOutputStream(zipTempFile.getPath()))) {
|
||||
for (int pageIndex = 0;
|
||||
@@ -161,9 +160,10 @@ public class SplitPdfBySectionsController {
|
||||
log.error("Error creating ZIP file with split PDF sections", e);
|
||||
throw e;
|
||||
}
|
||||
byte[] zipBytes = Files.readAllBytes(zipTempFile.getPath());
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.zipFileToWebResponse(zipTempFile, filename + ".zip");
|
||||
} catch (Exception ex) {
|
||||
zipTempFile.close();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
+8
-9
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||
@@ -12,6 +11,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -23,6 +23,7 @@ import stirling.software.common.annotations.api.GeneralApi;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -30,6 +31,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class ToSinglePageController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
@@ -42,7 +44,7 @@ public class ToSinglePageController {
|
||||
+ " document. The width of the single page will be same as the input's"
|
||||
+ " width, but the height will be the sum of all the pages' heights."
|
||||
+ " Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> pdfToSinglePage(@ModelAttribute PDFFile request)
|
||||
public ResponseEntity<StreamingResponseBody> pdfToSinglePage(@ModelAttribute PDFFile request)
|
||||
throws IOException {
|
||||
|
||||
// Load the source document
|
||||
@@ -85,14 +87,11 @@ public class ToSinglePageController {
|
||||
pageIndex++;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
newDocument.save(baos);
|
||||
|
||||
byte[] result = baos.toByteArray();
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
newDocument,
|
||||
GeneralUtils.generateFilename(
|
||||
request.getFileInput().getOriginalFilename(), "_singlePage.pdf"));
|
||||
request.getFileInput().getOriginalFilename(), "_singlePage.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +95,8 @@ public class UIDataController {
|
||||
Resource resource = new ClassPathResource("static/3rdPartyLicenses.json");
|
||||
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||
Map<String, List<Dependency>> licenseData =
|
||||
objectMapper.readValue(json, new TypeReference<>() {});
|
||||
objectMapper.readValue(is, new TypeReference<>() {});
|
||||
data.setDependencies(licenseData.get("dependencies"));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load licenses data", e);
|
||||
|
||||
+19
-6
@@ -16,6 +16,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -31,6 +32,7 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@@ -60,7 +62,7 @@ public class ConvertEbookToPDFController {
|
||||
description =
|
||||
"This endpoint converts common eBook formats (EPUB, MOBI, AZW3, FB2, TXT, DOCX)"
|
||||
+ " to PDF using Calibre. Input:BOOK Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> convertEbookToPdf(
|
||||
public ResponseEntity<StreamingResponseBody> convertEbookToPdf(
|
||||
@ModelAttribute ConvertEbookToPdfRequest request) throws Exception {
|
||||
if (!isCalibreEnabled()) {
|
||||
throw new IllegalStateException("Calibre support is disabled");
|
||||
@@ -140,24 +142,35 @@ public class ConvertEbookToPDFController {
|
||||
String outputFilename =
|
||||
GeneralUtils.generateFilename(originalFilename, "_convertedToPDF.pdf");
|
||||
|
||||
TempFile tempOut = null;
|
||||
try {
|
||||
tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
if (optimizeForEbook) {
|
||||
byte[] pdfBytes = Files.readAllBytes(outputPath);
|
||||
try {
|
||||
byte[] optimizedPdf = GeneralUtils.optimizePdfWithGhostscript(pdfBytes);
|
||||
return WebResponseUtils.bytesToWebResponse(optimizedPdf, outputFilename);
|
||||
Files.write(tempOut.getPath(), optimizedPdf);
|
||||
} catch (IOException e) {
|
||||
log.warn(
|
||||
"Ghostscript optimization failed for ebook conversion, returning"
|
||||
+ " original PDF",
|
||||
e);
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
Files.write(tempOut.getPath(), pdfBytes);
|
||||
}
|
||||
} else {
|
||||
try (PDDocument document = pdfDocumentFactory.load(outputPath.toFile())) {
|
||||
document.save(tempOut.getFile());
|
||||
}
|
||||
}
|
||||
|
||||
try (PDDocument document = pdfDocumentFactory.load(outputPath.toFile())) {
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, outputFilename);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
tempOut = null;
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
if (tempOut != null) {
|
||||
tempOut.close();
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
cleanupTempFiles(workingDirectory, inputPath, outputPath);
|
||||
}
|
||||
|
||||
+46
-29
@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -10,6 +11,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
@@ -26,6 +28,7 @@ import stirling.software.common.model.api.converters.EmlToPdfRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.CustomHtmlSanitizer;
|
||||
import stirling.software.common.util.EmlToPdf;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@@ -48,7 +51,8 @@ public class ConvertEmlToPDF {
|
||||
+ " with extensive customization options. Features include font settings,"
|
||||
+ " image constraints, display modes, attachment handling, and HTML debug"
|
||||
+ " output. Input: EML or MSG file, Output: PDF or HTML file. Type: SISO")
|
||||
public ResponseEntity<byte[]> convertEmlToPdf(@ModelAttribute EmlToPdfRequest request) {
|
||||
public ResponseEntity<StreamingResponseBody> convertEmlToPdf(
|
||||
@ModelAttribute EmlToPdfRequest request) {
|
||||
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String originalFilename = inputFile.getOriginalFilename();
|
||||
@@ -56,22 +60,19 @@ public class ConvertEmlToPDF {
|
||||
// Validate input
|
||||
if (inputFile.isEmpty()) {
|
||||
log.error("No file provided for EML/MSG to PDF conversion.");
|
||||
return ResponseEntity.badRequest()
|
||||
.body("No file provided".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.BAD_REQUEST, "No file provided");
|
||||
}
|
||||
|
||||
if (originalFilename == null || originalFilename.trim().isEmpty()) {
|
||||
log.error("Filename is null or empty.");
|
||||
return ResponseEntity.badRequest()
|
||||
.body("Please provide a valid filename".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.BAD_REQUEST, "Please provide a valid filename");
|
||||
}
|
||||
|
||||
// Validate file type - support EML and MSG (Outlook) files
|
||||
String lowerFilename = originalFilename.toLowerCase(Locale.ROOT);
|
||||
if (!lowerFilename.endsWith(".eml") && !lowerFilename.endsWith(".msg")) {
|
||||
log.error("Invalid file type for EML/MSG to PDF: {}", originalFilename);
|
||||
return ResponseEntity.badRequest()
|
||||
.body("Please upload a valid EML or MSG file".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.BAD_REQUEST, "Please upload a valid EML or MSG file");
|
||||
}
|
||||
|
||||
String baseFilename = Filenames.toSimpleFileName(originalFilename); // Use Filenames utility
|
||||
@@ -84,16 +85,20 @@ public class ConvertEmlToPDF {
|
||||
String htmlContent =
|
||||
EmlToPdf.convertEmlToHtml(fileBytes, request, customHtmlSanitizer);
|
||||
log.info("Successfully converted email to HTML: {}", originalFilename);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
htmlContent.getBytes(StandardCharsets.UTF_8),
|
||||
baseFilename + ".html",
|
||||
MediaType.TEXT_HTML);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".html");
|
||||
try {
|
||||
Files.writeString(tempOut.getPath(), htmlContent, StandardCharsets.UTF_8);
|
||||
} catch (Exception ex) {
|
||||
tempOut.close();
|
||||
throw ex;
|
||||
}
|
||||
return WebResponseUtils.fileToWebResponse(
|
||||
tempOut, baseFilename + ".html", MediaType.TEXT_HTML);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
log.error("HTML conversion failed for {}", originalFilename, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(
|
||||
("HTML conversion failed: " + e.getMessage())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"HTML conversion failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,20 +116,25 @@ public class ConvertEmlToPDF {
|
||||
|
||||
if (pdfBytes == null || pdfBytes.length == 0) {
|
||||
log.error("PDF conversion failed - empty output for {}", originalFilename);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(
|
||||
"PDF conversion failed - empty output"
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"PDF conversion failed - empty output");
|
||||
}
|
||||
log.info("Successfully converted email to PDF: {}", originalFilename);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfBytes, baseFilename + ".pdf", MediaType.APPLICATION_PDF);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), pdfBytes);
|
||||
} catch (Exception ex) {
|
||||
tempOut.close();
|
||||
throw ex;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, baseFilename + ".pdf");
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("Email to PDF conversion was interrupted for {}", originalFilename, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Conversion was interrupted".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "Conversion was interrupted");
|
||||
} catch (IllegalArgumentException e) {
|
||||
String errorMessage = buildErrorMessage(e, originalFilename);
|
||||
log.error(
|
||||
@@ -132,8 +142,7 @@ public class ConvertEmlToPDF {
|
||||
originalFilename,
|
||||
errorMessage,
|
||||
e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(errorMessage.getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, errorMessage);
|
||||
} catch (RuntimeException e) {
|
||||
String errorMessage = buildErrorMessage(e, originalFilename);
|
||||
log.error(
|
||||
@@ -141,17 +150,25 @@ public class ConvertEmlToPDF {
|
||||
originalFilename,
|
||||
errorMessage,
|
||||
e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(errorMessage.getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, errorMessage);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("File processing error for email to PDF: {}", originalFilename, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("File processing error".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, "File processing error");
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseEntity<StreamingResponseBody> errorResponse(HttpStatus status, String message) {
|
||||
byte[] body = message.getBytes(StandardCharsets.UTF_8);
|
||||
StreamingResponseBody streaming =
|
||||
os -> {
|
||||
os.write(body);
|
||||
os.flush();
|
||||
};
|
||||
return ResponseEntity.status(status).body(streaming);
|
||||
}
|
||||
|
||||
private static @NotNull String buildErrorMessage(Exception e, String originalFilename) {
|
||||
String safeFilename = HtmlUtils.htmlEscape(originalFilename);
|
||||
String exceptionMessage = e.getMessage();
|
||||
|
||||
+12
-2
@@ -1,9 +1,12 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -37,7 +40,7 @@ public class ConvertHtmlToPDF {
|
||||
description =
|
||||
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format."
|
||||
+ " Input:HTML Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
|
||||
public ResponseEntity<StreamingResponseBody> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
|
||||
throws Exception {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
|
||||
@@ -65,6 +68,13 @@ public class ConvertHtmlToPDF {
|
||||
|
||||
String outputFilename = GeneralUtils.generateFilename(originalFilename, ".pdf");
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), pdfBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-6
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -14,6 +15,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -46,8 +48,8 @@ public class ConvertMarkdownToPdf {
|
||||
description =
|
||||
"This endpoint takes a Markdown file or ZIP (containing Markdown + images) input, converts it to HTML, and then to"
|
||||
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile generalFile)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> markdownToPdf(
|
||||
@ModelAttribute GeneralFile generalFile) throws Exception {
|
||||
MultipartFile fileInput = generalFile.getFileInput();
|
||||
|
||||
if (fileInput == null) {
|
||||
@@ -79,7 +81,7 @@ public class ConvertMarkdownToPdf {
|
||||
java.nio.file.Path tempDirPath = tempDir.getPath();
|
||||
try (java.util.zip.ZipInputStream zipIn =
|
||||
io.github.pixee.security.ZipSecurity.createHardenedInputStream(
|
||||
new java.io.ByteArrayInputStream(fileInput.getBytes()))) {
|
||||
fileInput.getInputStream())) {
|
||||
java.util.zip.ZipEntry entry;
|
||||
while ((entry = zipIn.getNextEntry()) != null) {
|
||||
if (!entry.isDirectory()) {
|
||||
@@ -141,7 +143,7 @@ public class ConvertMarkdownToPdf {
|
||||
List<Extension> extensions = List.of(TablesExtension.create());
|
||||
Parser parser = Parser.builder().extensions(extensions).build();
|
||||
|
||||
Node document = parser.parse(new String(fileInput.getBytes()));
|
||||
Node document = parser.parse(new String(fileInput.getBytes(), StandardCharsets.UTF_8));
|
||||
HtmlRenderer renderer =
|
||||
HtmlRenderer.builder()
|
||||
.attributeProviderFactory(context -> new TableAttributeProvider())
|
||||
@@ -154,7 +156,7 @@ public class ConvertMarkdownToPdf {
|
||||
FileToPdf.convertHtmlToPdf(
|
||||
runtimePathConfig.getWeasyPrintPath(),
|
||||
null,
|
||||
htmlContent.getBytes(),
|
||||
htmlContent.getBytes(StandardCharsets.UTF_8),
|
||||
"converted.html",
|
||||
tempFileManager,
|
||||
customHtmlSanitizer);
|
||||
@@ -163,7 +165,15 @@ public class ConvertMarkdownToPdf {
|
||||
}
|
||||
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
java.nio.file.Files.write(tempOut.getPath(), pdfBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+21
-6
@@ -17,6 +17,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -36,6 +37,8 @@ import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ConvertApi
|
||||
@@ -47,6 +50,7 @@ public class ConvertOfficeController {
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final CustomHtmlSanitizer customHtmlSanitizer;
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private boolean isUnoconvertAvailable() {
|
||||
return endpointConfiguration.isGroupEnabled("Unoconvert")
|
||||
@@ -202,21 +206,32 @@ public class ConvertOfficeController {
|
||||
description =
|
||||
"This endpoint converts a given file to a PDF using LibreOffice API Input:ANY"
|
||||
+ " Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile generalFile)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> processFileToPDF(
|
||||
@ModelAttribute GeneralFile generalFile) throws Exception {
|
||||
MultipartFile inputFile = generalFile.getFileInput();
|
||||
// unused but can start server instance if startup time is to long
|
||||
// LibreOfficeListener.getInstance().start();
|
||||
File file = null;
|
||||
TempFile tempOut = null;
|
||||
try {
|
||||
file = convertToPdf(inputFile);
|
||||
|
||||
tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try (PDDocument doc = pdfDocumentFactory.load(file)) {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
doc,
|
||||
GeneralUtils.generateFilename(
|
||||
inputFile.getOriginalFilename(), "_convertedToPDF.pdf"));
|
||||
doc.save(tempOut.getFile());
|
||||
}
|
||||
String filename =
|
||||
GeneralUtils.generateFilename(
|
||||
inputFile.getOriginalFilename(), "_convertedToPDF.pdf");
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
WebResponseUtils.pdfFileToWebResponse(tempOut, filename);
|
||||
tempOut = null;
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
if (tempOut != null) {
|
||||
tempOut.close();
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
if (file != null && file.getParent() != null) {
|
||||
FileUtils.deleteDirectory(file.getParentFile());
|
||||
|
||||
+13
-4
@@ -13,6 +13,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -29,6 +30,7 @@ import stirling.software.common.annotations.api.ConvertApi;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@@ -85,8 +87,8 @@ public class ConvertPDFToEpubController {
|
||||
description =
|
||||
"Convert a PDF file to a high-quality EPUB or AZW3 ebook using Calibre. Input:PDF"
|
||||
+ " Output:EPUB/AZW3 Type:SISO")
|
||||
public ResponseEntity<byte[]> convertPdfToEpub(@ModelAttribute ConvertPdfToEpubRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> convertPdfToEpub(
|
||||
@ModelAttribute ConvertPdfToEpubRequest request) throws Exception {
|
||||
|
||||
if (!endpointConfiguration.isGroupEnabled(CALIBRE_GROUP)) {
|
||||
throw new IllegalStateException(
|
||||
@@ -170,9 +172,16 @@ public class ConvertPDFToEpubController {
|
||||
+ "."
|
||||
+ outputFormat.getExtension());
|
||||
|
||||
byte[] outputBytes = Files.readAllBytes(outputPath);
|
||||
MediaType mediaType = MediaType.valueOf(outputFormat.getMediaType());
|
||||
return WebResponseUtils.bytesToWebResponse(outputBytes, outputFilename, mediaType);
|
||||
TempFile tempOut =
|
||||
tempFileManager.createManagedTempFile("." + outputFormat.getExtension());
|
||||
try {
|
||||
Files.copy(outputPath, tempOut.getPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.fileToWebResponse(tempOut, outputFilename, mediaType);
|
||||
} finally {
|
||||
cleanupTempFiles(workingDirectory, inputPath, outputPath);
|
||||
}
|
||||
|
||||
+21
-15
@@ -1,6 +1,7 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -11,11 +12,10 @@ import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.WorkbookUtil;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -27,6 +27,9 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.ConvertApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import technology.tabula.ObjectExtractor;
|
||||
import technology.tabula.Page;
|
||||
@@ -40,6 +43,7 @@ import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
|
||||
public class ConvertPDFToExcelController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(value = "/pdf/xlsx", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@Operation(
|
||||
@@ -47,11 +51,12 @@ public class ConvertPDFToExcelController {
|
||||
description =
|
||||
"Extracts tabular data from each page of a PDF and writes it into an Excel"
|
||||
+ " workbook, one sheet per table. Input:PDF Output:XLSX Type:SISO")
|
||||
public ResponseEntity<byte[]> pdfToExcel(@ModelAttribute PDFWithPageNums request)
|
||||
public ResponseEntity<StreamingResponseBody> pdfToExcel(@ModelAttribute PDFWithPageNums request)
|
||||
throws Exception {
|
||||
String baseName =
|
||||
GeneralUtils.removeExtension(request.getFileInput().getOriginalFilename());
|
||||
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".xlsx");
|
||||
try (PDDocument document = pdfDocumentFactory.load(request);
|
||||
XSSFWorkbook workbook = new XSSFWorkbook();
|
||||
ObjectExtractor extractor = new ObjectExtractor(document)) {
|
||||
@@ -89,21 +94,22 @@ public class ConvertPDFToExcelController {
|
||||
}
|
||||
|
||||
if (sheetCount == 0) {
|
||||
tempOut.close();
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
workbook.write(baos);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentDisposition(
|
||||
ContentDisposition.builder("attachment").filename(baseName + ".xlsx").build());
|
||||
headers.setContentType(
|
||||
MediaType.parseMediaType(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
|
||||
|
||||
return ResponseEntity.ok().headers(headers).body(baos.toByteArray());
|
||||
try (OutputStream os = Files.newOutputStream(tempOut.getPath())) {
|
||||
workbook.write(os);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
MediaType mediaType =
|
||||
MediaType.parseMediaType(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
return WebResponseUtils.fileToWebResponse(tempOut, baseName + ".xlsx", mediaType);
|
||||
}
|
||||
|
||||
private String getUniqueSheetName(Workbook workbook, String baseName) {
|
||||
|
||||
+3
-1
@@ -4,6 +4,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -28,7 +29,8 @@ public class ConvertPDFToHtml {
|
||||
summary = "Convert PDF to HTML",
|
||||
description =
|
||||
"This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToHTML(@ModelAttribute PDFFile file) throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToHTML(@ModelAttribute PDFFile file)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = file.getFileInput();
|
||||
PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig);
|
||||
return pdfToFile.processPdfToHtml(inputFile);
|
||||
|
||||
+18
-9
@@ -1,6 +1,8 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
@@ -8,6 +10,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -23,6 +26,7 @@ import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.PDFToFile;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@@ -40,7 +44,7 @@ public class ConvertPDFToOffice {
|
||||
description =
|
||||
"This endpoint converts a given PDF file to a Presentation format. Input:PDF"
|
||||
+ " Output:PPT Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToPresentation(
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToPresentation(
|
||||
@ModelAttribute PdfToPresentationRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
@@ -55,20 +59,24 @@ public class ConvertPDFToOffice {
|
||||
description =
|
||||
"This endpoint converts a given PDF file to Text or RTF format. Input:PDF"
|
||||
+ " Output:TXT Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToRTForTXT(
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToRTForTXT(
|
||||
@ModelAttribute PdfToTextOrRTFRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String outputFormat = request.getOutputFormat();
|
||||
if ("txt".equals(request.getOutputFormat())) {
|
||||
String fileName =
|
||||
GeneralUtils.generateFilename(inputFile.getOriginalFilename(), ".txt");
|
||||
TempFile finalOut = tempFileManager.createManagedTempFile(".txt");
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
|
||||
PDFTextStripper stripper = new PDFTextStripper();
|
||||
String text = stripper.getText(document);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
text.getBytes(),
|
||||
GeneralUtils.generateFilename(inputFile.getOriginalFilename(), ".txt"),
|
||||
MediaType.TEXT_PLAIN);
|
||||
Files.writeString(finalOut.getPath(), text, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
finalOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.fileToWebResponse(finalOut, fileName, MediaType.TEXT_PLAIN);
|
||||
} else {
|
||||
PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig);
|
||||
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
||||
@@ -81,8 +89,8 @@ public class ConvertPDFToOffice {
|
||||
description =
|
||||
"This endpoint converts a given PDF file to a Word document format. Input:PDF"
|
||||
+ " Output:WORD Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToWord(@ModelAttribute PdfToWordRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToWord(
|
||||
@ModelAttribute PdfToWordRequest request) throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String outputFormat = request.getOutputFormat();
|
||||
PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig);
|
||||
@@ -95,7 +103,8 @@ public class ConvertPDFToOffice {
|
||||
description =
|
||||
"This endpoint converts a PDF file to an XML file. Input:PDF Output:XML"
|
||||
+ " Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToXML(@ModelAttribute PDFFile file) throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToXML(@ModelAttribute PDFFile file)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = file.getFileInput();
|
||||
|
||||
PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig);
|
||||
|
||||
+31
-9
@@ -77,6 +77,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -92,6 +93,8 @@ import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ConvertApi
|
||||
@@ -102,6 +105,7 @@ public class ConvertPDFToPDFA {
|
||||
private static final Pattern NON_PRINTABLE_ASCII = Pattern.compile("[^\\x20-\\x7E]");
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final stirling.software.SPDF.service.VeraPDFService veraPDFService;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private static final String ICC_RESOURCE_PATH = "/icc/sRGB2014.icc";
|
||||
private static final int PDFA_COMPATIBILITY_POLICY = 1;
|
||||
@@ -573,7 +577,7 @@ public class ConvertPDFToPDFA {
|
||||
summary = "Convert a PDF to a PDF/A or PDF/X",
|
||||
description =
|
||||
"This endpoint converts a PDF file to a PDF/A or PDF/X file using Ghostscript (preferred) or PDFBox/LibreOffice (fallback). PDF/A is a format designed for long-term archiving, while PDF/X is optimized for print production. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
|
||||
public ResponseEntity<StreamingResponseBody> pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String outputFormat = request.getOutputFormat();
|
||||
@@ -609,7 +613,7 @@ public class ConvertPDFToPDFA {
|
||||
return missing;
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> handlePdfXConversion(
|
||||
private ResponseEntity<StreamingResponseBody> handlePdfXConversion(
|
||||
MultipartFile inputFile, String outputFormat) throws Exception {
|
||||
PdfXProfile profile = PdfXProfile.fromRequest(outputFormat);
|
||||
|
||||
@@ -640,8 +644,14 @@ public class ConvertPDFToPDFA {
|
||||
|
||||
log.info("PDF/X conversion completed successfully to {}", profile.getDisplayName());
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
converted, outputFilename, MediaType.APPLICATION_PDF);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), converted);
|
||||
} catch (Exception ex) {
|
||||
tempOut.close();
|
||||
throw ex;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("PDF/X conversion failed", e);
|
||||
@@ -1796,7 +1806,7 @@ public class ConvertPDFToPDFA {
|
||||
return Files.readAllBytes(outputPdf);
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> handlePdfAConversion(
|
||||
private ResponseEntity<StreamingResponseBody> handlePdfAConversion(
|
||||
MultipartFile inputFile, String outputFormat, boolean strict) throws Exception {
|
||||
PdfaProfile profile = PdfaProfile.fromRequest(outputFormat);
|
||||
|
||||
@@ -1830,8 +1840,14 @@ public class ConvertPDFToPDFA {
|
||||
verifyStrictCompliance(converted);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
converted, outputFilename, MediaType.APPLICATION_PDF);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), converted);
|
||||
} catch (Exception ex) {
|
||||
tempOut.close();
|
||||
throw ex;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.warn(
|
||||
"Ghostscript conversion failed, falling back to PDFBox/LibreOffice method",
|
||||
@@ -1851,8 +1867,14 @@ public class ConvertPDFToPDFA {
|
||||
verifyStrictCompliance(converted);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
converted, outputFilename, MediaType.APPLICATION_PDF);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), converted);
|
||||
} catch (Exception ex) {
|
||||
tempOut.close();
|
||||
throw ex;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
} finally {
|
||||
deleteQuietly(workingDir);
|
||||
}
|
||||
|
||||
+71
-43
@@ -1,6 +1,7 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -31,6 +33,8 @@ import stirling.software.common.model.api.GeneralFile;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.JobOwnershipService;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@Slf4j
|
||||
@@ -42,6 +46,7 @@ public class ConvertPdfJsonController {
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("[\\r\\n\\t]+");
|
||||
private static final Pattern NON_PRINTABLE_PATTERN = Pattern.compile("[^\\x20-\\x7E]");
|
||||
private final PdfJsonConversionService pdfJsonConversionService;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@Autowired(required = false)
|
||||
private JobOwnershipService jobOwnershipService;
|
||||
@@ -51,7 +56,7 @@ public class ConvertPdfJsonController {
|
||||
summary = "Convert PDF to Text Editor Format",
|
||||
description =
|
||||
"Extracts PDF text, fonts, and metadata into an editable JSON structure for the text editor tool. Input:PDF Output:JSON Type:SISO")
|
||||
public ResponseEntity<byte[]> convertPdfToJson(
|
||||
public ResponseEntity<StreamingResponseBody> convertPdfToJson(
|
||||
@ModelAttribute PDFFile request,
|
||||
@RequestParam(value = "lightweight", defaultValue = "false") boolean lightweight)
|
||||
throws Exception {
|
||||
@@ -60,6 +65,8 @@ public class ConvertPdfJsonController {
|
||||
throw ExceptionUtils.createNullArgumentException("fileInput");
|
||||
}
|
||||
|
||||
// TODO: Refactor PdfJsonConversionService to write directly to an OutputStream
|
||||
// instead of returning byte[], avoiding the intermediate heap allocation + temp file write
|
||||
byte[] jsonBytes = pdfJsonConversionService.convertPdfToJson(inputFile, lightweight);
|
||||
logJsonResponse("pdf/text-editor", jsonBytes);
|
||||
String originalName = inputFile.getOriginalFilename();
|
||||
@@ -70,7 +77,14 @@ public class ConvertPdfJsonController {
|
||||
.replaceFirst("")
|
||||
: "document";
|
||||
String docName = baseName + ".json";
|
||||
return WebResponseUtils.bytesToWebResponse(jsonBytes, docName, MediaType.APPLICATION_JSON);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".json");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), jsonBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/text-editor/pdf")
|
||||
@@ -79,8 +93,8 @@ public class ConvertPdfJsonController {
|
||||
summary = "Convert Text Editor Format to PDF",
|
||||
description =
|
||||
"Rebuilds a PDF from the editable JSON structure generated by the text editor tool. Input:JSON Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> convertJsonToPdf(@ModelAttribute GeneralFile request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> convertJsonToPdf(
|
||||
@ModelAttribute GeneralFile request) throws Exception {
|
||||
MultipartFile jsonFile = request.getFileInput();
|
||||
if (jsonFile == null) {
|
||||
throw ExceptionUtils.createNullArgumentException("fileInput");
|
||||
@@ -95,7 +109,14 @@ public class ConvertPdfJsonController {
|
||||
.replaceFirst("")
|
||||
: "document";
|
||||
String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, docName);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), pdfBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, docName);
|
||||
}
|
||||
|
||||
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/text-editor/metadata")
|
||||
@@ -105,17 +126,15 @@ public class ConvertPdfJsonController {
|
||||
"Extracts document metadata, fonts, and page dimensions for the text editor tool. Caches the document for"
|
||||
+ " subsequent page requests. Returns a server-generated jobId scoped to the"
|
||||
+ " authenticated user. Input:PDF Output:JSON Type:SISO")
|
||||
public ResponseEntity<byte[]> extractPdfMetadata(@ModelAttribute PDFFile request)
|
||||
public ResponseEntity<StreamingResponseBody> extractPdfMetadata(@ModelAttribute PDFFile request)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
if (inputFile == null) {
|
||||
throw ExceptionUtils.createNullArgumentException("fileInput");
|
||||
}
|
||||
|
||||
// Generate server-side UUID for job
|
||||
String baseJobId = UUID.randomUUID().toString();
|
||||
|
||||
// Scope job to authenticated user if security is enabled
|
||||
String scopedJobKey = getScopedJobKey(baseJobId);
|
||||
|
||||
log.debug("Extracting metadata for PDF, assigned jobId: {}", scopedJobKey);
|
||||
@@ -123,20 +142,27 @@ public class ConvertPdfJsonController {
|
||||
byte[] jsonBytes =
|
||||
pdfJsonConversionService.extractDocumentMetadata(inputFile, scopedJobKey);
|
||||
logJsonResponse("pdf/text-editor/metadata", jsonBytes);
|
||||
String originalName = inputFile.getOriginalFilename();
|
||||
String baseName =
|
||||
(originalName != null && !originalName.isBlank())
|
||||
? FILE_EXTENSION_PATTERN
|
||||
.matcher(Filenames.toSimpleFileName(originalName))
|
||||
.replaceFirst("")
|
||||
: "document";
|
||||
String docName = baseName + "_metadata.json";
|
||||
|
||||
// Return jobId in response header for client
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".json");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), jsonBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.header("X-Job-Id", scopedJobKey)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(jsonBytes);
|
||||
.contentLength(java.nio.file.Files.size(tempOut.getPath()))
|
||||
.body(
|
||||
os -> {
|
||||
try (os) {
|
||||
Files.copy(tempOut.getPath(), os);
|
||||
os.flush();
|
||||
} finally {
|
||||
tempOut.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AutoJobPostMapping(
|
||||
@@ -149,7 +175,7 @@ public class ConvertPdfJsonController {
|
||||
"Applies edits for the specified pages of a cached PDF and returns an updated PDF."
|
||||
+ " Requires the PDF to have been previously cached via the text editor metadata endpoint."
|
||||
+ " The jobId must be obtained from the metadata extraction endpoint.")
|
||||
public ResponseEntity<byte[]> exportPartialPdf(
|
||||
public ResponseEntity<StreamingResponseBody> exportPartialPdf(
|
||||
@PathVariable String jobId,
|
||||
@RequestBody PdfJsonDocument document,
|
||||
@RequestParam(value = "filename", required = false) String filename)
|
||||
@@ -158,7 +184,6 @@ public class ConvertPdfJsonController {
|
||||
throw ExceptionUtils.createNullArgumentException("document");
|
||||
}
|
||||
|
||||
// Validate job ownership
|
||||
validateJobAccess(jobId);
|
||||
|
||||
byte[] pdfBytes = pdfJsonConversionService.exportUpdatedPages(jobId, document);
|
||||
@@ -173,7 +198,14 @@ public class ConvertPdfJsonController {
|
||||
.filter(title -> title != null && !title.isBlank())
|
||||
.orElse("document");
|
||||
String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, docName);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), pdfBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, docName);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/pdf/text-editor/page/{jobId}/{pageNumber}")
|
||||
@@ -183,16 +215,22 @@ public class ConvertPdfJsonController {
|
||||
"Retrieves a single page's content from a previously cached PDF document for the text editor tool."
|
||||
+ " Requires prior call to /pdf/text-editor/metadata. The jobId must belong to the"
|
||||
+ " authenticated user. Output:JSON")
|
||||
public ResponseEntity<byte[]> extractSinglePage(
|
||||
public ResponseEntity<StreamingResponseBody> extractSinglePage(
|
||||
@PathVariable String jobId, @PathVariable int pageNumber) throws Exception {
|
||||
|
||||
// Validate job ownership
|
||||
validateJobAccess(jobId);
|
||||
|
||||
byte[] jsonBytes = pdfJsonConversionService.extractSinglePage(jobId, pageNumber);
|
||||
logJsonResponse("pdf/text-editor/page", jsonBytes);
|
||||
String docName = "page_" + pageNumber + ".json";
|
||||
return WebResponseUtils.bytesToWebResponse(jsonBytes, docName, MediaType.APPLICATION_JSON);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".json");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), jsonBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/pdf/text-editor/fonts/{jobId}/{pageNumber}")
|
||||
@@ -202,16 +240,22 @@ public class ConvertPdfJsonController {
|
||||
"Retrieves the font payloads used by a single page from a previously cached PDF document."
|
||||
+ " Requires prior call to /pdf/text-editor/metadata. The jobId must belong to the"
|
||||
+ " authenticated user. Output:JSON")
|
||||
public ResponseEntity<byte[]> extractPageFonts(
|
||||
public ResponseEntity<StreamingResponseBody> extractPageFonts(
|
||||
@PathVariable String jobId, @PathVariable int pageNumber) throws Exception {
|
||||
|
||||
// Validate job ownership
|
||||
validateJobAccess(jobId);
|
||||
|
||||
byte[] jsonBytes = pdfJsonConversionService.extractPageFonts(jobId, pageNumber);
|
||||
logJsonResponse("pdf/text-editor/fonts/page", jsonBytes);
|
||||
String docName = "page_fonts_" + pageNumber + ".json";
|
||||
return WebResponseUtils.bytesToWebResponse(jsonBytes, docName, MediaType.APPLICATION_JSON);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".json");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), jsonBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
@AutoJobPostMapping(
|
||||
@@ -225,24 +269,16 @@ public class ConvertPdfJsonController {
|
||||
+ " authenticated user.")
|
||||
public ResponseEntity<Void> clearCache(@PathVariable String jobId) {
|
||||
|
||||
// Validate job ownership
|
||||
validateJobAccess(jobId);
|
||||
|
||||
pdfJsonConversionService.clearCachedDocument(jobId);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scoped job key that includes user ownership when security is enabled.
|
||||
*
|
||||
* @param baseJobId the base job identifier
|
||||
* @return scoped job key, or just baseJobId if no ownership service available
|
||||
*/
|
||||
private String getScopedJobKey(String baseJobId) {
|
||||
if (jobOwnershipService != null) {
|
||||
return jobOwnershipService.createScopedJobKey(baseJobId);
|
||||
}
|
||||
// Security disabled, return unsecured job key
|
||||
return baseJobId;
|
||||
}
|
||||
|
||||
@@ -252,7 +288,6 @@ public class ConvertPdfJsonController {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only perform expensive tail extraction if debug logging is enabled
|
||||
if (log.isDebugEnabled()) {
|
||||
int length = jsonBytes.length;
|
||||
boolean endsWithJson =
|
||||
@@ -431,16 +466,9 @@ public class ConvertPdfJsonController {
|
||||
return WHITESPACE_PATTERN.matcher(value.substring(0, max)).replaceAll(" ") + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the current user has access to the given job.
|
||||
*
|
||||
* @param jobId the job identifier to validate
|
||||
* @throws SecurityException if current user does not own the job
|
||||
*/
|
||||
private void validateJobAccess(String jobId) {
|
||||
if (jobOwnershipService != null) {
|
||||
jobOwnershipService.validateJobAccess(jobId);
|
||||
}
|
||||
// If jobOwnershipService is null (security disabled), allow all access
|
||||
}
|
||||
}
|
||||
|
||||
+53
-38
@@ -14,6 +14,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -53,7 +54,8 @@ public class ConvertSvgToPDF {
|
||||
+ "SVG dimensions (width/height) determine the PDF page size; defaults to A4 if not specified. "
|
||||
+ "SVG content is sanitized to prevent XSS attacks. "
|
||||
+ "Input: SVG file(s), Output: PDF file(s) or ZIP. Type: MIMO")
|
||||
public ResponseEntity<byte[]> convertSvgToPdf(@ModelAttribute SvgToPdfRequest request) {
|
||||
public ResponseEntity<StreamingResponseBody> convertSvgToPdf(
|
||||
@ModelAttribute SvgToPdfRequest request) {
|
||||
|
||||
MultipartFile[] inputFiles = request.getFileInput();
|
||||
boolean combineIntoSinglePdf = Boolean.TRUE.equals(request.getCombineIntoSinglePdf());
|
||||
@@ -61,8 +63,7 @@ public class ConvertSvgToPDF {
|
||||
// Validate input
|
||||
if (inputFiles == null || inputFiles.length == 0) {
|
||||
log.error("No files provided for SVG to PDF conversion.");
|
||||
return ResponseEntity.badRequest()
|
||||
.body("No files provided".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.BAD_REQUEST, "No files provided");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -103,8 +104,7 @@ public class ConvertSvgToPDF {
|
||||
|
||||
if (sanitizedSvgs.isEmpty()) {
|
||||
log.error("No valid SVG files were found");
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body("No valid SVG files were found".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.BAD_REQUEST, "No valid SVG files were found");
|
||||
}
|
||||
|
||||
if (combineIntoSinglePdf) {
|
||||
@@ -115,14 +115,23 @@ public class ConvertSvgToPDF {
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during SVG to PDF conversion", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(
|
||||
"An unexpected error occurred during conversion"
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"An unexpected error occurred during conversion");
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> handleCombinedConversion(
|
||||
private ResponseEntity<StreamingResponseBody> errorResponse(HttpStatus status, String message) {
|
||||
byte[] body = message.getBytes(StandardCharsets.UTF_8);
|
||||
StreamingResponseBody streaming =
|
||||
os -> {
|
||||
os.write(body);
|
||||
os.flush();
|
||||
};
|
||||
return ResponseEntity.status(status).body(streaming);
|
||||
}
|
||||
|
||||
private ResponseEntity<StreamingResponseBody> handleCombinedConversion(
|
||||
List<byte[]> sanitizedSvgs, List<String> filenames) {
|
||||
try {
|
||||
log.info("Combining {} SVG files into single PDF", sanitizedSvgs.size());
|
||||
@@ -131,10 +140,8 @@ public class ConvertSvgToPDF {
|
||||
|
||||
if (pdfBytes == null || pdfBytes.length == 0) {
|
||||
log.error("PDF conversion failed - empty output");
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(
|
||||
"PDF conversion failed - empty output"
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "PDF conversion failed - empty output");
|
||||
}
|
||||
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
@@ -146,19 +153,23 @@ public class ConvertSvgToPDF {
|
||||
|
||||
log.info("Successfully combined {} SVGs into single PDF", sanitizedSvgs.size());
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfBytes, outputFilename, MediaType.APPLICATION_PDF);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), pdfBytes);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Error combining SVGs into PDF", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(
|
||||
("Conversion failed: " + e.getMessage())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "Conversion failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> handleSeparateConversion(
|
||||
private ResponseEntity<StreamingResponseBody> handleSeparateConversion(
|
||||
List<byte[]> sanitizedSvgs, List<String> filenames) {
|
||||
List<ConvertedPdf> convertedPdfs = new ArrayList<>();
|
||||
|
||||
@@ -188,15 +199,21 @@ public class ConvertSvgToPDF {
|
||||
|
||||
if (convertedPdfs.isEmpty()) {
|
||||
log.error("No files were successfully converted");
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("No files were successfully converted".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "No files were successfully converted");
|
||||
}
|
||||
|
||||
try {
|
||||
if (convertedPdfs.size() == 1) {
|
||||
ConvertedPdf pdf = convertedPdfs.get(0);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdf.content, pdf.filename, MediaType.APPLICATION_PDF);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
Files.write(tempOut.getPath(), pdf.content);
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, pdf.filename);
|
||||
}
|
||||
|
||||
String zipFilename =
|
||||
@@ -204,22 +221,18 @@ public class ConvertSvgToPDF {
|
||||
? "converted_svgs.zip"
|
||||
: GeneralUtils.generateFilename(
|
||||
filenames.get(0), "_converted_svgs.zip");
|
||||
byte[] zipBytes = createZipFromPdfs(convertedPdfs);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
TempFile zipFile = createZipFromPdfs(convertedPdfs);
|
||||
return WebResponseUtils.zipFileToWebResponse(zipFile, zipFilename);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to create response", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Failed to create response".getBytes(StandardCharsets.UTF_8));
|
||||
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to create response");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] createZipFromPdfs(List<ConvertedPdf> pdfs) throws IOException {
|
||||
try (TempFile tempZipFile = new TempFile(tempFileManager, ".zip");
|
||||
ZipOutputStream zipOut =
|
||||
new ZipOutputStream(Files.newOutputStream(tempZipFile.getPath()))) {
|
||||
|
||||
private TempFile createZipFromPdfs(List<ConvertedPdf> pdfs) throws IOException {
|
||||
TempFile tempZipFile = tempFileManager.createManagedTempFile(".zip");
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(Files.newOutputStream(tempZipFile.getPath()))) {
|
||||
for (ConvertedPdf pdf : pdfs) {
|
||||
ZipEntry pdfEntry = new ZipEntry(pdf.filename);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
@@ -227,9 +240,11 @@ public class ConvertSvgToPDF {
|
||||
zipOut.closeEntry();
|
||||
log.debug("Added {} to ZIP", pdf.filename);
|
||||
}
|
||||
|
||||
return Files.readAllBytes(tempZipFile.getPath());
|
||||
} catch (IOException e) {
|
||||
tempZipFile.close();
|
||||
throw e;
|
||||
}
|
||||
return tempZipFile;
|
||||
}
|
||||
|
||||
private static class ConvertedPdf {
|
||||
|
||||
+11
-8
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
@@ -39,6 +38,8 @@ import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ConvertApi
|
||||
@@ -49,6 +50,7 @@ public class ConvertWebsiteToPDF {
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final ApplicationProperties applicationProperties;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private static final Pattern FILE_SCHEME_PATTERN =
|
||||
Pattern.compile("(?<![a-z0-9_])file\\s*:(?:/{1,3}|%2f|%5c|%3a|/|/)");
|
||||
@@ -136,14 +138,15 @@ public class ConvertWebsiteToPDF {
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Load the PDF using pdfDocumentFactory
|
||||
try (PDDocument doc = pdfDocumentFactory.load(tempOutputFile.toFile());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
// Convert URL to a safe filename
|
||||
String outputFilename = convertURLToFileName(URL);
|
||||
|
||||
doc.save(baos);
|
||||
return WebResponseUtils.baosToWebResponse(baos, outputFilename);
|
||||
String outputFilename = convertURLToFileName(URL);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try (PDDocument doc = pdfDocumentFactory.load(tempOutputFile.toFile())) {
|
||||
doc.save(tempOut.getFile());
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename);
|
||||
} finally {
|
||||
if (tempHtmlInput != null) {
|
||||
try {
|
||||
|
||||
+6
-10
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -32,6 +31,7 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.ConvertApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import technology.tabula.ObjectExtractor;
|
||||
import technology.tabula.Page;
|
||||
@@ -90,7 +90,7 @@ public class ExtractCSVController {
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> createZipResponse(List<CsvEntry> entries, String baseName)
|
||||
throws IOException {
|
||||
throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(baos)) {
|
||||
for (CsvEntry entry : entries) {
|
||||
@@ -101,14 +101,10 @@ public class ExtractCSVController {
|
||||
}
|
||||
}
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentDisposition(
|
||||
ContentDisposition.builder("attachment")
|
||||
.filename(baseName + "_extracted.zip")
|
||||
.build());
|
||||
headers.setContentType(MediaType.parseMediaType("application/zip"));
|
||||
|
||||
return ResponseEntity.ok().headers(headers).body(baos.toByteArray());
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
baseName + "_extracted.zip",
|
||||
MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
private ResponseEntity<String> createCsvResponse(CsvEntry entry, String baseName) {
|
||||
|
||||
+37
-33
@@ -13,6 +13,7 @@ import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -38,7 +39,6 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
@RequiredArgsConstructor
|
||||
public class PdfVectorExportController {
|
||||
|
||||
private static final MediaType PDF_MEDIA_TYPE = MediaType.APPLICATION_PDF;
|
||||
private static final Set<String> GHOSTSCRIPT_INPUTS =
|
||||
Set.of("ps", "eps", "epsf"); // PCL/PXL/XPS require GhostPDL (gpcl6/gxps)
|
||||
|
||||
@@ -51,7 +51,7 @@ public class PdfVectorExportController {
|
||||
description =
|
||||
"Converts PostScript vector inputs (PS, EPS, EPSF) to PDF using Ghostscript."
|
||||
+ " Input:PS/EPS Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> convertGhostscriptInputsToPdf(
|
||||
public ResponseEntity<StreamingResponseBody> convertGhostscriptInputsToPdf(
|
||||
@Valid @ModelAttribute PdfVectorExportRequest request) throws Exception {
|
||||
|
||||
String originalName =
|
||||
@@ -63,9 +63,9 @@ public class PdfVectorExportController {
|
||||
? FilenameUtils.getExtension(originalName).toLowerCase(Locale.ROOT)
|
||||
: "";
|
||||
|
||||
TempFile outputTemp = tempFileManager.createManagedTempFile(".pdf");
|
||||
try (TempFile inputTemp =
|
||||
new TempFile(tempFileManager, extension.isEmpty() ? "" : "." + extension);
|
||||
TempFile outputTemp = new TempFile(tempFileManager, ".pdf")) {
|
||||
new TempFile(tempFileManager, extension.isEmpty() ? "" : "." + extension)) {
|
||||
|
||||
request.getFileInput().transferTo(inputTemp.getFile());
|
||||
|
||||
@@ -83,11 +83,13 @@ public class PdfVectorExportController {
|
||||
"Unsupported Ghostscript input format {0}",
|
||||
extension);
|
||||
}
|
||||
|
||||
byte[] pdfBytes = Files.readAllBytes(outputTemp.getPath());
|
||||
String outputName = GeneralUtils.generateFilename(originalName, "_converted.pdf");
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputName, PDF_MEDIA_TYPE);
|
||||
} catch (Exception e) {
|
||||
outputTemp.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
String outputName = GeneralUtils.generateFilename(originalName, "_converted.pdf");
|
||||
return WebResponseUtils.pdfFileToWebResponse(outputTemp, outputName);
|
||||
}
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/pdf/vector")
|
||||
@@ -96,7 +98,7 @@ public class PdfVectorExportController {
|
||||
description =
|
||||
"Converts PDF to Ghostscript vector formats (EPS, PS, PCL, or XPS)."
|
||||
+ " Input:PDF Output:VECTOR Type:SISO")
|
||||
public ResponseEntity<byte[]> convertPdfToVector(
|
||||
public ResponseEntity<StreamingResponseBody> convertPdfToVector(
|
||||
@Valid @ModelAttribute PdfVectorExportRequest request) throws Exception {
|
||||
|
||||
String originalName =
|
||||
@@ -110,35 +112,37 @@ public class PdfVectorExportController {
|
||||
}
|
||||
outputFormat = outputFormat.toLowerCase(Locale.ROOT);
|
||||
|
||||
try (TempFile inputTemp = new TempFile(tempFileManager, ".pdf");
|
||||
TempFile outputTemp = new TempFile(tempFileManager, "." + outputFormat)) {
|
||||
TempFile outputTemp = tempFileManager.createManagedTempFile("." + outputFormat);
|
||||
try (TempFile inputTemp = new TempFile(tempFileManager, ".pdf")) {
|
||||
|
||||
request.getFileInput().transferTo(inputTemp.getFile());
|
||||
|
||||
runGhostscriptPdfToVector(inputTemp.getPath(), outputTemp.getPath(), outputFormat);
|
||||
|
||||
byte[] vectorBytes = Files.readAllBytes(outputTemp.getPath());
|
||||
String outputName =
|
||||
GeneralUtils.generateFilename(originalName, "_converted." + outputFormat);
|
||||
|
||||
MediaType mediaType;
|
||||
switch (outputFormat.toLowerCase(Locale.ROOT)) {
|
||||
case "eps":
|
||||
case "ps":
|
||||
mediaType = MediaType.parseMediaType("application/postscript");
|
||||
break;
|
||||
case "pcl":
|
||||
mediaType = MediaType.parseMediaType("application/vnd.hp-PCL");
|
||||
break;
|
||||
case "xps":
|
||||
mediaType = MediaType.parseMediaType("application/vnd.ms-xpsdocument");
|
||||
break;
|
||||
default:
|
||||
mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(vectorBytes, outputName, mediaType);
|
||||
} catch (Exception e) {
|
||||
outputTemp.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
String outputName =
|
||||
GeneralUtils.generateFilename(originalName, "_converted." + outputFormat);
|
||||
|
||||
MediaType mediaType;
|
||||
switch (outputFormat.toLowerCase(Locale.ROOT)) {
|
||||
case "eps":
|
||||
case "ps":
|
||||
mediaType = MediaType.parseMediaType("application/postscript");
|
||||
break;
|
||||
case "pcl":
|
||||
mediaType = MediaType.parseMediaType("application/vnd.hp-PCL");
|
||||
break;
|
||||
case "xps":
|
||||
mediaType = MediaType.parseMediaType("application/vnd.ms-xpsdocument");
|
||||
break;
|
||||
default:
|
||||
mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
return WebResponseUtils.fileToWebResponse(outputTemp, outputName, mediaType);
|
||||
}
|
||||
|
||||
private void runGhostscriptPdfToVector(Path inputPath, Path outputPath, String outputFormat)
|
||||
|
||||
+13
-6
@@ -9,6 +9,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -29,6 +30,7 @@ import stirling.software.common.annotations.api.FilterApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.PdfUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@FilterApi
|
||||
@@ -36,6 +38,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class FilterController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
@@ -53,8 +56,8 @@ public class FilterController {
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
public ResponseEntity<StreamingResponseBody> containsText(
|
||||
@ModelAttribute ContainsTextRequest request) throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String text = request.getText();
|
||||
String pageNumber = request.getPageNumbers();
|
||||
@@ -62,7 +65,9 @@ public class FilterController {
|
||||
try (PDDocument pdfDocument = pdfDocumentFactory.load(inputFile)) {
|
||||
if (PdfUtils.hasText(pdfDocument, pageNumber, text)) {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
|
||||
pdfDocument,
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
@@ -84,15 +89,17 @@ public class FilterController {
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
|
||||
throws IOException, InterruptedException {
|
||||
public ResponseEntity<StreamingResponseBody> containsImage(
|
||||
@ModelAttribute PDFWithPageNums request) throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String pageNumber = request.getPageNumbers();
|
||||
|
||||
try (PDDocument pdfDocument = pdfDocumentFactory.load(inputFile)) {
|
||||
if (PdfUtils.hasImages(pdfDocument, pageNumber)) {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
|
||||
pdfDocument,
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
|
||||
+9
-8
@@ -18,6 +18,7 @@ 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.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import com.opencsv.CSVWriter;
|
||||
|
||||
@@ -34,6 +35,7 @@ import stirling.software.common.model.FormFieldWithCoordinates;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.FormUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
@@ -59,12 +61,11 @@ public class FormFillController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private static ResponseEntity<byte[]> saveDocument(PDDocument document, String baseName)
|
||||
private ResponseEntity<StreamingResponseBody> saveDocument(PDDocument document, String baseName)
|
||||
throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), baseName + ".pdf");
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, baseName + ".pdf", tempFileManager);
|
||||
}
|
||||
|
||||
private static String buildBaseName(MultipartFile file, String suffix) {
|
||||
@@ -261,7 +262,7 @@ public class FormFillController {
|
||||
summary = "Modify existing form fields",
|
||||
description =
|
||||
"Updates existing fields in the provided PDF and returns the updated file")
|
||||
public ResponseEntity<byte[]> modifyFields(
|
||||
public ResponseEntity<StreamingResponseBody> modifyFields(
|
||||
@Parameter(
|
||||
description = "The input PDF file",
|
||||
required = true,
|
||||
@@ -292,7 +293,7 @@ public class FormFillController {
|
||||
@Operation(
|
||||
summary = "Delete form fields",
|
||||
description = "Removes the specified fields from the PDF and returns the updated file")
|
||||
public ResponseEntity<byte[]> deleteFields(
|
||||
public ResponseEntity<StreamingResponseBody> deleteFields(
|
||||
@Parameter(
|
||||
description = "The input PDF file",
|
||||
required = true,
|
||||
@@ -328,7 +329,7 @@ public class FormFillController {
|
||||
description =
|
||||
"Populates the supplied PDF form using values from the provided JSON payload"
|
||||
+ " and returns the filled PDF")
|
||||
public ResponseEntity<byte[]> fillForm(
|
||||
public ResponseEntity<StreamingResponseBody> fillForm(
|
||||
@Parameter(
|
||||
description = "The input PDF file",
|
||||
required = true,
|
||||
@@ -355,7 +356,7 @@ public class FormFillController {
|
||||
document -> FormUtils.applyFieldValues(document, values, flatten, true));
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> processSingleFile(
|
||||
private ResponseEntity<StreamingResponseBody> processSingleFile(
|
||||
MultipartFile file, String suffix, DocumentProcessor processor) throws IOException {
|
||||
requirePdf(file);
|
||||
|
||||
|
||||
+29
-19
@@ -1,7 +1,7 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -30,6 +31,8 @@ import stirling.software.common.annotations.api.MiscApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -43,14 +46,16 @@ public class AttachmentController {
|
||||
|
||||
private final ConvertPDFToPDFA convertPDFToPDFA;
|
||||
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/add-attachments")
|
||||
@StandardPdfResponse
|
||||
@Operation(
|
||||
summary = "Add attachments to PDF",
|
||||
description =
|
||||
"This endpoint adds attachments to a PDF. Input:PDF, Output:PDF Type:MISO")
|
||||
public ResponseEntity<byte[]> addAttachments(@ModelAttribute AddAttachmentRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> addAttachments(
|
||||
@ModelAttribute AddAttachmentRequest request) throws Exception {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
List<MultipartFile> attachments = request.getAttachments();
|
||||
boolean convertToPdfA3b = request.isConvertToPdfA3b();
|
||||
@@ -79,13 +84,9 @@ public class AttachmentController {
|
||||
|
||||
ConvertPDFToPDFA.fixType1FontCharSet(pdfaDocument);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
pdfaDocument.save(baos);
|
||||
byte[] resultBytes = baos.toByteArray();
|
||||
|
||||
String outputFilename = baseFileName + "_with_attachments_PDFA-3b.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
resultBytes, outputFilename, MediaType.APPLICATION_PDF);
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
pdfaDocument, outputFilename, tempFileManager);
|
||||
}
|
||||
} else {
|
||||
try (PDDocument document = pdfDocumentFactory.load(request, false)) {
|
||||
@@ -94,7 +95,8 @@ public class AttachmentController {
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename()),
|
||||
"_with_attachments.pdf"));
|
||||
"_with_attachments.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +143,7 @@ public class AttachmentController {
|
||||
description =
|
||||
"This endpoint extracts all embedded attachments from a PDF into a ZIP archive."
|
||||
+ " Input:PDF Output:ZIP Type:SISO")
|
||||
public ResponseEntity<byte[]> extractAttachments(
|
||||
public ResponseEntity<StreamingResponseBody> extractAttachments(
|
||||
@ModelAttribute ExtractAttachmentsRequest request) throws IOException {
|
||||
try (PDDocument document = pdfDocumentFactory.load(request, true)) {
|
||||
Optional<byte[]> extracted = pdfAttachmentService.extractAttachments(document);
|
||||
@@ -159,8 +161,14 @@ public class AttachmentController {
|
||||
Filenames.toSimpleFileName(
|
||||
GeneralUtils.generateFilename(sourceName, "_attachments.zip"));
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
extracted.get(), outputName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".zip");
|
||||
try {
|
||||
Files.write(tempOut.getFile().toPath(), extracted.get());
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.zipFileToWebResponse(tempOut, outputName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,8 +195,8 @@ public class AttachmentController {
|
||||
summary = "Rename attachment in PDF",
|
||||
description =
|
||||
"This endpoint renames an embedded attachment in a PDF. Input:PDF Output:PDF Type:MISO")
|
||||
public ResponseEntity<byte[]> renameAttachment(@ModelAttribute RenameAttachmentRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> renameAttachment(
|
||||
@ModelAttribute RenameAttachmentRequest request) throws Exception {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
String attachmentName = request.getAttachmentName();
|
||||
String newName = request.getNewName();
|
||||
@@ -209,7 +217,8 @@ public class AttachmentController {
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename()),
|
||||
"_attachment_renamed.pdf"));
|
||||
"_attachment_renamed.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +230,8 @@ public class AttachmentController {
|
||||
summary = "Delete attachment from PDF",
|
||||
description =
|
||||
"This endpoint deletes an embedded attachment from a PDF. Input:PDF Output:PDF Type:MISO")
|
||||
public ResponseEntity<byte[]> deleteAttachment(@ModelAttribute DeleteAttachmentRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> deleteAttachment(
|
||||
@ModelAttribute DeleteAttachmentRequest request) throws Exception {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
String attachmentName = request.getAttachmentName();
|
||||
|
||||
@@ -238,7 +247,8 @@ public class AttachmentController {
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename()),
|
||||
"_attachment_deleted.pdf"));
|
||||
"_attachment_deleted.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -12,6 +12,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -24,6 +25,7 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.MiscApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -35,6 +37,7 @@ public class AutoRenameController {
|
||||
private static final int LINE_LIMIT = 200;
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/auto-rename")
|
||||
@Operation(
|
||||
@@ -42,8 +45,8 @@ public class AutoRenameController {
|
||||
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(@ModelAttribute ExtractHeaderRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> extractHeader(
|
||||
@ModelAttribute ExtractHeaderRequest request) throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
boolean useFirstTextAsFallback = Boolean.TRUE.equals(request.getUseFirstTextAsFallback());
|
||||
|
||||
@@ -140,11 +143,14 @@ public class AutoRenameController {
|
||||
.matcher(header)
|
||||
.replaceAll("")
|
||||
.trim();
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document, header + ".pdf", tempFileManager);
|
||||
} else {
|
||||
log.info("File has no good title to be found");
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
||||
document,
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename()),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -22,6 +22,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import com.google.zxing.*;
|
||||
import com.google.zxing.common.GlobalHistogramBinarizer;
|
||||
@@ -275,8 +276,8 @@ public class AutoSplitPdfController {
|
||||
+ " splits the document at the QR code boundaries. The output is a zip"
|
||||
+ " file containing each separate PDF document. Input:PDF Output:ZIP-PDF"
|
||||
+ " Type:SISO")
|
||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> autoSplitPdf(
|
||||
@ModelAttribute AutoSplitPdfRequest request) throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
boolean duplexMode = Boolean.TRUE.equals(request.getDuplexMode());
|
||||
|
||||
@@ -287,8 +288,8 @@ public class AutoSplitPdfController {
|
||||
duplexMode);
|
||||
|
||||
List<PDDocument> splitDocuments = new ArrayList<>();
|
||||
try (TempFile outputTempFile = new TempFile(tempFileManager, ".zip");
|
||||
PDDocument document = pdfDocumentFactory.load(file.getInputStream())) {
|
||||
TempFile outputTempFile = new TempFile(tempFileManager, ".zip");
|
||||
try (PDDocument document = pdfDocumentFactory.load(file.getInputStream())) {
|
||||
int totalPages = document.getNumberOfPages();
|
||||
log.info("PDF loaded, totalPages={}", totalPages);
|
||||
|
||||
@@ -357,11 +358,10 @@ public class AutoSplitPdfController {
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data = Files.readAllBytes(outputTempFile.getPath());
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.zipFileToWebResponse(outputTempFile, filename + ".zip");
|
||||
|
||||
} catch (Exception e) {
|
||||
outputTempFile.close();
|
||||
log.error("Error in auto split", e);
|
||||
throw e;
|
||||
} finally {
|
||||
|
||||
+23
-16
@@ -1,8 +1,9 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -19,6 +20,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -35,6 +37,8 @@ import stirling.software.common.util.ApplicationContextProvider;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.PdfUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -43,6 +47,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class BlankPageController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
public static boolean isBlankImage(
|
||||
BufferedImage image, int threshold, double whitePercent, int blurSize) {
|
||||
@@ -83,7 +88,8 @@ public class BlankPageController {
|
||||
"This endpoint removes blank pages from a given PDF file. Users can specify the"
|
||||
+ " threshold and white percentage to tune the detection of blank pages."
|
||||
+ " Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request)
|
||||
public ResponseEntity<StreamingResponseBody> removeBlankPages(
|
||||
@ModelAttribute RemoveBlankPagesRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
int threshold = request.getThreshold();
|
||||
@@ -149,28 +155,29 @@ public class BlankPageController {
|
||||
pageIndex++;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ZipOutputStream zos = new ZipOutputStream(baos);
|
||||
|
||||
String filename =
|
||||
GeneralUtils.removeExtension(
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
|
||||
|
||||
if (!nonBlankPages.isEmpty()) {
|
||||
createZipEntry(zos, nonBlankPages, filename + "_nonBlankPages.pdf");
|
||||
} else {
|
||||
createZipEntry(zos, blankPages, filename + "_allBlankPages.pdf");
|
||||
}
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".zip");
|
||||
try (OutputStream fos = Files.newOutputStream(tempOut.getFile().toPath());
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
if (!nonBlankPages.isEmpty()) {
|
||||
createZipEntry(zos, nonBlankPages, filename + "_nonBlankPages.pdf");
|
||||
} else {
|
||||
createZipEntry(zos, blankPages, filename + "_allBlankPages.pdf");
|
||||
}
|
||||
|
||||
if (!nonBlankPages.isEmpty() && !blankPages.isEmpty()) {
|
||||
createZipEntry(zos, blankPages, filename + "_blankPages.pdf");
|
||||
if (!nonBlankPages.isEmpty() && !blankPages.isEmpty()) {
|
||||
createZipEntry(zos, blankPages, filename + "_blankPages.pdf");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
zos.close();
|
||||
|
||||
log.info("Returning ZIP file: {}", filename + "_processed.zip");
|
||||
return WebResponseUtils.baosToWebResponse(
|
||||
baos, filename + "_processed.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.zipFileToWebResponse(tempOut, filename + "_processed.zip");
|
||||
|
||||
} catch (ExceptionUtils.OutOfMemoryDpiException e) {
|
||||
throw e;
|
||||
|
||||
+11
-6
@@ -38,6 +38,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -328,7 +329,8 @@ public class CompressController {
|
||||
+ "_"
|
||||
+ image.getBitsPerComponent();
|
||||
|
||||
return bytesToHexString(generateMD5(enhancedData.getBytes()));
|
||||
return bytesToHexString(
|
||||
generateMD5(enhancedData.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
return "empty-stream";
|
||||
}
|
||||
@@ -727,7 +729,8 @@ public class CompressController {
|
||||
params.append("_").append(image.getDecode().toString());
|
||||
}
|
||||
|
||||
return bytesToHexString(generateMD5(params.toString().getBytes()));
|
||||
return bytesToHexString(
|
||||
generateMD5(params.toString().getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
return "fallback-decode-" + System.identityHashCode(image);
|
||||
}
|
||||
@@ -798,7 +801,8 @@ public class CompressController {
|
||||
metadata.append("_softmask");
|
||||
}
|
||||
|
||||
return bytesToHexString(generateMD5(metadata.toString().getBytes()));
|
||||
return bytesToHexString(
|
||||
generateMD5(metadata.toString().getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
return "fallback-meta-" + System.identityHashCode(image);
|
||||
}
|
||||
@@ -924,8 +928,8 @@ public class CompressController {
|
||||
description =
|
||||
"This endpoint accepts a PDF file and optimizes it based on the provided"
|
||||
+ " parameters. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> optimizePdf(@ModelAttribute OptimizePdfRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> optimizePdf(
|
||||
@ModelAttribute OptimizePdfRequest request) throws Exception {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
|
||||
// Validate input file
|
||||
@@ -1097,7 +1101,8 @@ public class CompressController {
|
||||
|
||||
try {
|
||||
try (PDDocument document = pdfDocumentFactory.load(currentFile.toFile())) {
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, outputFilename);
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document, outputFilename, tempFileManager);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw ExceptionUtils.handlePdfException(e, "PDF optimization");
|
||||
|
||||
+16
-8
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
@@ -14,6 +13,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -26,6 +26,8 @@ import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -34,12 +36,13 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class DecompressPdfController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(value = "/decompress-pdf", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@Operation(
|
||||
summary = "Decompress PDF streams",
|
||||
description = "Fully decompresses all PDF streams including text content")
|
||||
public ResponseEntity<byte[]> decompressPdf(@ModelAttribute PDFFile request)
|
||||
public ResponseEntity<StreamingResponseBody> decompressPdf(@ModelAttribute PDFFile request)
|
||||
throws IOException {
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
@@ -48,13 +51,18 @@ public class DecompressPdfController {
|
||||
// Process all objects in document
|
||||
processAllObjects(document);
|
||||
|
||||
// Save with explicit no compression
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos, CompressParameters.NO_COMPRESSION);
|
||||
// Save with explicit no compression to a temp file
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
document.save(tempOut.getFile(), CompressParameters.NO_COMPRESSION);
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Return the PDF as a response
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
// Return the PDF as a streaming response
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
tempOut,
|
||||
GeneralUtils.generateFilename(file.getOriginalFilename(), "_decompressed.pdf"));
|
||||
}
|
||||
}
|
||||
|
||||
+41
-39
@@ -1,8 +1,8 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -21,6 +21,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -39,6 +40,8 @@ import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -49,6 +52,7 @@ public class ExtractImageScansController {
|
||||
private static final String REPLACEFIRST = "[.][^.]+$";
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
@@ -61,7 +65,7 @@ public class ExtractImageScansController {
|
||||
+ " parameters. Users can specify angle threshold, tolerance, minimum area,"
|
||||
+ " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP"
|
||||
+ " Type:SIMO")
|
||||
public ResponseEntity<byte[]> extractImageScans(
|
||||
public ResponseEntity<StreamingResponseBody> extractImageScans(
|
||||
@ModelAttribute ExtractImageScansRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
@@ -71,9 +75,8 @@ public class ExtractImageScansController {
|
||||
|
||||
List<String> images = new ArrayList<>();
|
||||
|
||||
List<Path> tempImageFiles = new ArrayList<>();
|
||||
Path tempInputFile;
|
||||
Path tempZipFile = null;
|
||||
List<TempFile> tempImageFiles = new ArrayList<>();
|
||||
TempFile tempInputFile = null;
|
||||
List<Path> tempDirs = new ArrayList<>();
|
||||
|
||||
if (!CheckProgramInstall.isPythonAvailable()) {
|
||||
@@ -83,6 +86,8 @@ public class ExtractImageScansController {
|
||||
|
||||
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
||||
Path splitPhotosScript = GeneralUtils.extractScript("split_photos.py");
|
||||
TempFile finalOutput = null;
|
||||
boolean finalOutputOwnershipTransferred = false;
|
||||
try {
|
||||
// Check if input file is a PDF
|
||||
if ("pdf".equalsIgnoreCase(extension)) {
|
||||
@@ -96,7 +101,8 @@ public class ExtractImageScansController {
|
||||
// Create images of all pages
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
// Create temp file to save the image
|
||||
Path tempFile = Files.createTempFile("image_", ".png");
|
||||
TempFile tempImage = tempFileManager.createManagedTempFile(".png");
|
||||
tempImageFiles.add(tempImage);
|
||||
|
||||
// Render image and save as temp file
|
||||
BufferedImage image;
|
||||
@@ -116,18 +122,17 @@ public class ExtractImageScansController {
|
||||
pageIndex + 1,
|
||||
dpi,
|
||||
() -> pdfRenderer.renderImageWithDPI(pageIndex, dpi));
|
||||
ImageIO.write(image, "png", tempFile.toFile());
|
||||
ImageIO.write(image, "png", tempImage.getFile());
|
||||
|
||||
// Add temp file path to images list
|
||||
images.add(tempFile.toString());
|
||||
tempImageFiles.add(tempFile);
|
||||
images.add(tempImage.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tempInputFile = Files.createTempFile("input_", "." + extension);
|
||||
inputFile.transferTo(tempInputFile);
|
||||
tempInputFile = tempFileManager.createManagedTempFile("." + extension);
|
||||
inputFile.transferTo(tempInputFile.getFile());
|
||||
// Add input file path to images list
|
||||
images.add(tempInputFile.toString());
|
||||
images.add(tempInputFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
List<byte[]> processedImageBytes = new ArrayList<>();
|
||||
@@ -177,10 +182,10 @@ public class ExtractImageScansController {
|
||||
if (processedImageBytes.size() > 1) {
|
||||
String outputZipFilename =
|
||||
GeneralUtils.generateFilename(fileName, "_processed.zip");
|
||||
tempZipFile = Files.createTempFile("output_", ".zip");
|
||||
finalOutput = tempFileManager.createManagedTempFile(".zip");
|
||||
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
|
||||
new ZipOutputStream(Files.newOutputStream(finalOutput.getPath()))) {
|
||||
// Add processed images to the zip
|
||||
for (int i = 0; i < processedImageBytes.size(); i++) {
|
||||
ZipEntry entry =
|
||||
@@ -193,13 +198,10 @@ public class ExtractImageScansController {
|
||||
}
|
||||
}
|
||||
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
WebResponseUtils.zipFileToWebResponse(finalOutput, outputZipFilename);
|
||||
finalOutputOwnershipTransferred = true;
|
||||
return response;
|
||||
}
|
||||
if (processedImageBytes.isEmpty()) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
@@ -208,28 +210,28 @@ public class ExtractImageScansController {
|
||||
|
||||
// Return the processed image as a response
|
||||
byte[] imageBytes = processedImageBytes.get(0);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
imageBytes,
|
||||
GeneralUtils.generateFilename(fileName, ".png"),
|
||||
MediaType.IMAGE_PNG);
|
||||
finalOutput = tempFileManager.createManagedTempFile(".png");
|
||||
try (OutputStream out = Files.newOutputStream(finalOutput.getPath())) {
|
||||
out.write(imageBytes);
|
||||
}
|
||||
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
finalOutput,
|
||||
GeneralUtils.generateFilename(fileName, ".png"),
|
||||
MediaType.IMAGE_PNG);
|
||||
finalOutputOwnershipTransferred = true;
|
||||
return response;
|
||||
}
|
||||
} finally {
|
||||
if (finalOutput != null && !finalOutputOwnershipTransferred) {
|
||||
finalOutput.close();
|
||||
}
|
||||
// Cleanup logic for all temporary files and directories
|
||||
tempImageFiles.forEach(
|
||||
path -> {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to delete temporary image file: {}", path, e);
|
||||
}
|
||||
});
|
||||
tempImageFiles.forEach(TempFile::close);
|
||||
|
||||
if (tempZipFile != null && Files.exists(tempZipFile)) {
|
||||
try {
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to delete temporary zip file: {}", tempZipFile, e);
|
||||
}
|
||||
if (tempInputFile != null) {
|
||||
tempInputFile.close();
|
||||
}
|
||||
|
||||
tempDirs.forEach(
|
||||
|
||||
+11
-3
@@ -15,6 +15,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -30,6 +31,7 @@ import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ApplicationContextProvider;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -38,6 +40,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class FlattenController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/flatten")
|
||||
@StandardPdfResponse
|
||||
@@ -46,7 +49,8 @@ public class FlattenController {
|
||||
description =
|
||||
"Flattening just PDF form fields or converting each page to images to make text"
|
||||
+ " unselectable. Input:PDF, Output:PDF. Type:SISO")
|
||||
public ResponseEntity<byte[]> flatten(@ModelAttribute FlattenRequest request) throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> flatten(@ModelAttribute FlattenRequest request)
|
||||
throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
try (PDDocument document = pdfDocumentFactory.load(file)) {
|
||||
@@ -58,7 +62,9 @@ public class FlattenController {
|
||||
acroForm.flatten();
|
||||
}
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
||||
document,
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename()),
|
||||
tempFileManager);
|
||||
} else {
|
||||
// flatten whole page aka convert each page to image and re-add it (making text
|
||||
// unselectable)
|
||||
@@ -143,7 +149,9 @@ public class FlattenController {
|
||||
}
|
||||
}
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
||||
newDocument,
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename()),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -13,6 +13,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -28,6 +29,7 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.service.PdfMetadataService;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor;
|
||||
|
||||
@@ -37,6 +39,7 @@ import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor;
|
||||
public class MetadataController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private String checkUndefined(String entry) {
|
||||
// Check if the string is "undefined"
|
||||
@@ -61,7 +64,7 @@ public class MetadataController {
|
||||
"This endpoint allows you to update the metadata of a given PDF file. You can"
|
||||
+ " add, modify, or delete standard and custom metadata fields. Input:PDF"
|
||||
+ " Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> metadata(@ModelAttribute MetadataRequest request)
|
||||
public ResponseEntity<StreamingResponseBody> metadata(@ModelAttribute MetadataRequest request)
|
||||
throws IOException {
|
||||
|
||||
// Extract PDF file from the request object
|
||||
@@ -179,7 +182,8 @@ public class MetadataController {
|
||||
document,
|
||||
GeneralUtils.removeExtension(
|
||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()))
|
||||
+ "_metadata.pdf");
|
||||
+ "_metadata.pdf",
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-18
@@ -25,6 +25,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -89,7 +90,7 @@ public class OCRController {
|
||||
+ " specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType,"
|
||||
+ " and removeImagesAfter options. Uses OCRmyPDF if available, falls back to"
|
||||
+ " Tesseract. Input:PDF Output:PDF Type:SI-Conditional")
|
||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
||||
public ResponseEntity<StreamingResponseBody> processPdfWithOCR(
|
||||
@ModelAttribute ProcessPdfWithOcrRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
@@ -121,9 +122,11 @@ public class OCRController {
|
||||
throw ExceptionUtils.createOcrInvalidLanguagesException();
|
||||
}
|
||||
|
||||
// Use try-with-resources for proper temp file management
|
||||
TempFile tempOutputFile = new TempFile(tempFileManager, ".pdf");
|
||||
TempFile tempZipFile = null;
|
||||
boolean pdfOwnershipTransferred = false;
|
||||
boolean zipOwnershipTransferred = false;
|
||||
try (TempFile tempInputFile = new TempFile(tempFileManager, ".pdf");
|
||||
TempFile tempOutputFile = new TempFile(tempFileManager, ".pdf");
|
||||
TempFile sidecarTextFile = sidecar ? new TempFile(tempFileManager, ".txt") : null) {
|
||||
|
||||
inputFile.transferTo(tempInputFile.getFile());
|
||||
@@ -156,9 +159,6 @@ public class OCRController {
|
||||
throw ExceptionUtils.createOcrToolsUnavailableException();
|
||||
}
|
||||
|
||||
// Read the processed PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile.getPath());
|
||||
|
||||
// Return the OCR processed PDF as a response
|
||||
String outputFilename =
|
||||
GeneralUtils.removeExtension(
|
||||
@@ -172,14 +172,14 @@ public class OCRController {
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
|
||||
+ "_OCR.zip";
|
||||
|
||||
try (TempFile tempZipFile = new TempFile(tempFileManager, ".zip");
|
||||
ZipOutputStream zipOut =
|
||||
new ZipOutputStream(Files.newOutputStream(tempZipFile.getPath()))) {
|
||||
tempZipFile = new TempFile(tempFileManager, ".zip");
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(Files.newOutputStream(tempZipFile.getPath()))) {
|
||||
|
||||
// Add PDF file to the zip
|
||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
zipOut.write(pdfBytes);
|
||||
Files.copy(tempOutputFile.getPath(), zipOut);
|
||||
zipOut.closeEntry();
|
||||
|
||||
// Add text file to the zip
|
||||
@@ -189,16 +189,28 @@ public class OCRController {
|
||||
zipOut.closeEntry();
|
||||
|
||||
zipOut.finish();
|
||||
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile.getPath());
|
||||
|
||||
// Return the zip file containing both the PDF and the text file
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
// The intermediate PDF temp file is no longer needed; only the zip is streamed.
|
||||
tempOutputFile.close();
|
||||
pdfOwnershipTransferred = true;
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
tempZipFile, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
zipOwnershipTransferred = true;
|
||||
return response;
|
||||
} else {
|
||||
// Return the OCR processed PDF as a response
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
WebResponseUtils.pdfFileToWebResponse(tempOutputFile, outputFilename);
|
||||
pdfOwnershipTransferred = true;
|
||||
return response;
|
||||
}
|
||||
} finally {
|
||||
if (!pdfOwnershipTransferred) {
|
||||
tempOutputFile.close();
|
||||
}
|
||||
if (tempZipFile != null && !zipOwnershipTransferred) {
|
||||
tempZipFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-8
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -12,6 +11,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -24,6 +24,8 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.MiscApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -32,6 +34,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class OverlayImageController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/add-image")
|
||||
@Operation(
|
||||
@@ -42,7 +45,8 @@ public class OverlayImageController {
|
||||
+ "SVG files are rendered as vector graphics for crisp output at any resolution. "
|
||||
+ "The image can be overlaid on every page of the PDF if specified. "
|
||||
+ "Input:PDF/IMAGE/SVG Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) {
|
||||
public ResponseEntity<StreamingResponseBody> overlayImage(
|
||||
@ModelAttribute OverlayImageRequest request) {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
MultipartFile imageFile = request.getImageFile();
|
||||
float x = request.getX();
|
||||
@@ -82,14 +86,17 @@ public class OverlayImageController {
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
|
||||
byte[] result = baos.toByteArray();
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
document.save(tempOut.getFile());
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
log.info("PDF with overlaid image successfully created");
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result,
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
tempOut,
|
||||
GeneralUtils.generateFilename(
|
||||
pdfFile.getOriginalFilename(), "_overlayed.pdf"));
|
||||
}
|
||||
|
||||
+15
-7
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -16,6 +15,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -28,6 +28,8 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.MiscApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -35,6 +37,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class PageNumbersController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(value = "/add-page-numbers", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@StandardPdfResponse
|
||||
@@ -43,8 +46,8 @@ public class PageNumbersController {
|
||||
description =
|
||||
"This operation takes an input PDF file and adds page numbers to it. Input:PDF"
|
||||
+ " Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> addPageNumbers(
|
||||
@ModelAttribute AddPageNumbersRequest request) throws IOException {
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
String customMargin = request.getCustomMargin();
|
||||
@@ -175,11 +178,16 @@ public class PageNumbersController {
|
||||
pageNumber++;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
document.save(tempOut.getFile());
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
tempOut,
|
||||
GeneralUtils.generateFilename(
|
||||
file.getOriginalFilename(), "_page_numbers_added.pdf"));
|
||||
}
|
||||
|
||||
+15
-7
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -17,6 +16,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -29,6 +29,8 @@ import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@GeneralApi
|
||||
@@ -37,6 +39,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class RemoveImagesController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/remove-image-pdf")
|
||||
@Operation(
|
||||
@@ -44,7 +47,8 @@ public class RemoveImagesController {
|
||||
description =
|
||||
"This endpoint removes all embedded images from a PDF file and returns the"
|
||||
+ " modified document. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile request) throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> removeImages(@ModelAttribute PDFFile request)
|
||||
throws IOException {
|
||||
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
|
||||
@@ -60,12 +64,16 @@ public class RemoveImagesController {
|
||||
|
||||
log.info("Removed {} images from PDF with {} pages", imagesRemoved, totalPages);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
pdfDoc.save(baos);
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
pdfDoc.save(tempOut.getFile());
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
tempOut,
|
||||
GeneralUtils.generateFilename(
|
||||
inputFile.getOriginalFilename(), "_images_removed.pdf"));
|
||||
|
||||
|
||||
+13
-10
@@ -8,6 +8,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -53,13 +54,12 @@ public class RepairController {
|
||||
"This endpoint repairs a given PDF file by running Ghostscript (primary), qpdf (fallback), or PDFBox (if no external tools available). The PDF is"
|
||||
+ " first saved to a temporary location, repaired, read back, and then"
|
||||
+ " returned as a response. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile file)
|
||||
public ResponseEntity<StreamingResponseBody> repairPdf(@ModelAttribute PDFFile file)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = file.getFileInput();
|
||||
|
||||
// Use TempFile with try-with-resources for automatic cleanup
|
||||
try (TempFile tempInputFile = new TempFile(tempFileManager, ".pdf");
|
||||
TempFile tempOutputFile = new TempFile(tempFileManager, ".pdf")) {
|
||||
TempFile tempOutputFile = new TempFile(tempFileManager, ".pdf");
|
||||
try (TempFile tempInputFile = new TempFile(tempFileManager, ".pdf")) {
|
||||
|
||||
// Save the uploaded file to the temporary location
|
||||
inputFile.transferTo(tempInputFile.getFile());
|
||||
@@ -121,14 +121,17 @@ public class RepairController {
|
||||
}
|
||||
}
|
||||
|
||||
// Read the repaired PDF file
|
||||
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.getFile());
|
||||
|
||||
// Return the repaired PDF as a response
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfBytes,
|
||||
// Return the repaired PDF as a streaming response
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
tempOutputFile,
|
||||
GeneralUtils.generateFilename(
|
||||
inputFile.getOriginalFilename(), "_repaired.pdf"));
|
||||
} catch (IOException | InterruptedException e) {
|
||||
tempOutputFile.close();
|
||||
throw e;
|
||||
} catch (RuntimeException e) {
|
||||
tempOutputFile.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-3
@@ -1,11 +1,15 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -16,6 +20,8 @@ import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
|
||||
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.MiscApi;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -23,6 +29,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class ReplaceAndInvertColorController {
|
||||
|
||||
private final ReplaceAndInvertColorService replaceAndInvertColorService;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
@@ -32,7 +39,7 @@ public class ReplaceAndInvertColorController {
|
||||
description =
|
||||
"This endpoint accepts a PDF file and provides options to invert all colors, replace"
|
||||
+ " text and background colors, or convert to CMYK color space for printing. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> replaceAndInvertColor(
|
||||
public ResponseEntity<StreamingResponseBody> replaceAndInvertColor(
|
||||
@ModelAttribute ReplaceAndInvertColorRequest request) throws IOException {
|
||||
|
||||
InputStreamResource resource =
|
||||
@@ -47,7 +54,15 @@ public class ReplaceAndInvertColorController {
|
||||
String filename =
|
||||
GeneralUtils.generateFilename(
|
||||
request.getFileInput().getOriginalFilename(), "_inverted.pdf");
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
resource.getContentAsByteArray(), filename, MediaType.APPLICATION_PDF);
|
||||
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try (InputStream in = resource.getInputStream()) {
|
||||
Files.copy(in, tempOut.getFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return WebResponseUtils.pdfFileToWebResponse(tempOut, filename);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -6,7 +6,6 @@ import java.awt.RenderingHints;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -35,6 +34,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -51,6 +51,7 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ApplicationContextProvider;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -59,6 +60,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class ScannerEffectController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
private static final int MAX_IMAGE_WIDTH = 8192;
|
||||
private static final int MAX_IMAGE_HEIGHT = 8192;
|
||||
private static final long MAX_IMAGE_PIXELS = 16_777_216; // 4096x4096
|
||||
@@ -562,8 +564,8 @@ public class ScannerEffectController {
|
||||
summary = "Apply scanner effect to PDF",
|
||||
description =
|
||||
"Applies various effects to simulate a scanned document, including rotation, noise, and edge softening. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> scannerEffect(@Valid @ModelAttribute ScannerEffectRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> scannerEffect(
|
||||
@Valid @ModelAttribute ScannerEffectRequest request) throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
List<Path> tempFiles = new ArrayList<>();
|
||||
@@ -624,8 +626,7 @@ public class ScannerEffectController {
|
||||
sharedPdfBytes != null
|
||||
? pdfDocumentFactory.load(sharedPdfBytes)
|
||||
: pdfDocumentFactory.load(processingInput);
|
||||
PDDocument outputDocument = new PDDocument();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
PDDocument outputDocument = new PDDocument()) {
|
||||
|
||||
int totalPages = document.getNumberOfPages();
|
||||
if (totalPages == 0) {
|
||||
@@ -708,12 +709,11 @@ public class ScannerEffectController {
|
||||
|
||||
writeProcessedPagesToDocument(processedPages, outputDocument);
|
||||
|
||||
outputDocument.save(outputStream);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
outputStream.toByteArray(),
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
outputDocument,
|
||||
GeneralUtils.generateFilename(
|
||||
file.getOriginalFilename(), "_scanner_effect.pdf"));
|
||||
file.getOriginalFilename(), "_scanner_effect.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
+18
-3
@@ -1,6 +1,7 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -10,6 +11,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -21,6 +23,8 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.MiscApi;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@@ -28,13 +32,15 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class ShowJavascript {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/show-javascript")
|
||||
@JavaScriptResponse
|
||||
@Operation(
|
||||
summary = "Grabs all JS from a PDF and returns a single JS file with all code",
|
||||
description = "desc. Input:PDF Output:JS Type:SISO")
|
||||
public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile file) throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> extractHeader(@ModelAttribute PDFFile file)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = file.getFileInput();
|
||||
StringBuilder script = new StringBuilder();
|
||||
boolean foundScript = false;
|
||||
@@ -77,8 +83,17 @@ public class ShowJavascript {
|
||||
.append("' does not contain Javascript");
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
script.toString().getBytes(StandardCharsets.UTF_8),
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".js");
|
||||
try {
|
||||
Files.write(
|
||||
tempOut.getFile().toPath(),
|
||||
script.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
return WebResponseUtils.fileToWebResponse(
|
||||
tempOut,
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js",
|
||||
MediaType.TEXT_PLAIN);
|
||||
}
|
||||
|
||||
+4
-2
@@ -37,6 +37,7 @@ import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -92,7 +93,7 @@ public class StampController {
|
||||
"This endpoint adds a stamp to a given PDF file. Users can specify the stamp"
|
||||
+ " type (text or image), rotation, opacity, width spacer, and height"
|
||||
+ " spacer. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> addStamp(@ModelAttribute AddStampRequest request)
|
||||
public ResponseEntity<StreamingResponseBody> addStamp(@ModelAttribute AddStampRequest request)
|
||||
throws IOException, Exception {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String pdfFileName = pdfFile.getOriginalFilename();
|
||||
@@ -199,7 +200,8 @@ public class StampController {
|
||||
// Return the stamped PDF as a response
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_stamped.pdf"));
|
||||
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_stamped.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -26,15 +27,19 @@ import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@MiscApi
|
||||
@Slf4j
|
||||
public class UnlockPDFFormsController {
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
public UnlockPDFFormsController(CustomPDFDocumentFactory pdfDocumentFactory) {
|
||||
public UnlockPDFFormsController(
|
||||
CustomPDFDocumentFactory pdfDocumentFactory, TempFileManager tempFileManager) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.tempFileManager = tempFileManager;
|
||||
}
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/unlock-pdf-forms")
|
||||
@@ -44,7 +49,7 @@ public class UnlockPDFFormsController {
|
||||
description =
|
||||
"Removing read-only property from form fields making them fillable"
|
||||
+ "Input:PDF, Output:PDF. Type:SISO")
|
||||
public ResponseEntity<byte[]> unlockPDFForms(@ModelAttribute PDFFile file) {
|
||||
public ResponseEntity<StreamingResponseBody> unlockPDFForms(@ModelAttribute PDFFile file) {
|
||||
try (PDDocument document = pdfDocumentFactory.load(file)) {
|
||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||
|
||||
@@ -116,7 +121,7 @@ public class UnlockPDFFormsController {
|
||||
GeneralUtils.generateFilename(
|
||||
file.getFileInput().getOriginalFilename(), "_unlocked_forms.pdf");
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document, Filenames.toSimpleFileName(mergedFileName));
|
||||
document, Filenames.toSimpleFileName(mergedFileName), tempFileManager);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
+28
-19
@@ -64,6 +64,7 @@ import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.micrometer.common.util.StringUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -78,6 +79,8 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.service.ServerCertificateServiceInterface;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -104,13 +107,15 @@ public class CertSignController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final ServerCertificateServiceInterface serverCertificateService;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
public CertSignController(
|
||||
CustomPDFDocumentFactory pdfDocumentFactory,
|
||||
@Autowired(required = false)
|
||||
ServerCertificateServiceInterface serverCertificateService) {
|
||||
@Autowired(required = false) ServerCertificateServiceInterface serverCertificateService,
|
||||
TempFileManager tempFileManager) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.serverCertificateService = serverCertificateService;
|
||||
this.tempFileManager = tempFileManager;
|
||||
}
|
||||
|
||||
public static void sign(
|
||||
@@ -163,8 +168,8 @@ public class CertSignController {
|
||||
"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:SISO")
|
||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> signPDFWithCert(
|
||||
@ModelAttribute SignPDFWithCertRequest request) throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
String certType = request.getCertType();
|
||||
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
||||
@@ -246,22 +251,26 @@ public class CertSignController {
|
||||
}
|
||||
|
||||
CreateSignature createSignature = new CreateSignature(ks, keystorePassword.toCharArray());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
sign(
|
||||
pdfDocumentFactory,
|
||||
pdf,
|
||||
baos,
|
||||
createSignature,
|
||||
showSignature,
|
||||
pageNumber,
|
||||
name,
|
||||
location,
|
||||
reason,
|
||||
showLogo);
|
||||
TempFile signedOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try (OutputStream os = new FileOutputStream(signedOut.getFile())) {
|
||||
sign(
|
||||
pdfDocumentFactory,
|
||||
pdf,
|
||||
os,
|
||||
createSignature,
|
||||
showSignature,
|
||||
pageNumber,
|
||||
name,
|
||||
location,
|
||||
reason,
|
||||
showLogo);
|
||||
} catch (IOException e) {
|
||||
signedOut.close();
|
||||
throw e;
|
||||
}
|
||||
// Return the signed PDF
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_signed.pdf"));
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
signedOut, GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_signed.pdf"));
|
||||
}
|
||||
|
||||
private MultipartFile validateFilePresent(
|
||||
|
||||
+13
-7
@@ -9,6 +9,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -22,6 +23,7 @@ import stirling.software.common.annotations.api.SecurityApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@SecurityApi
|
||||
@@ -29,6 +31,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class PasswordController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/remove-password")
|
||||
@StandardPdfResponse
|
||||
@@ -37,8 +40,8 @@ public class PasswordController {
|
||||
description =
|
||||
"This endpoint removes the password from a protected PDF file. Users need to"
|
||||
+ " provide the existing password. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> removePassword(
|
||||
@ModelAttribute PDFPasswordRequest request) throws IOException {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
String password = request.getPassword();
|
||||
|
||||
@@ -47,7 +50,8 @@ public class PasswordController {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
fileInput.getOriginalFilename(), "_password_removed.pdf"));
|
||||
fileInput.getOriginalFilename(), "_password_removed.pdf"),
|
||||
tempFileManager);
|
||||
} catch (IOException e) {
|
||||
// Handle password errors specifically
|
||||
if (ExceptionUtils.isPasswordError(e)) {
|
||||
@@ -66,8 +70,8 @@ public class PasswordController {
|
||||
"This endpoint adds password protection to a PDF file. Users can specify a set"
|
||||
+ " of permissions that should be applied to the file. Input:PDF"
|
||||
+ " Output:PDF")
|
||||
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> addPassword(
|
||||
@ModelAttribute AddPasswordRequest request) throws IOException {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
String ownerPassword = request.getOwnerPassword();
|
||||
String password = request.getPassword();
|
||||
@@ -108,11 +112,13 @@ public class PasswordController {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
fileInput.getOriginalFilename(), "_permissions.pdf"));
|
||||
fileInput.getOriginalFilename(), "_permissions.pdf"),
|
||||
tempFileManager);
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
fileInput.getOriginalFilename(), "_passworded.pdf"));
|
||||
fileInput.getOriginalFilename(), "_passworded.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+45
-44
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -40,6 +39,7 @@ import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -64,6 +64,8 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.PdfUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
import stirling.software.common.util.propertyeditor.StringToArrayListPropertyEditor;
|
||||
|
||||
@@ -85,6 +87,7 @@ public class RedactController {
|
||||
private static final COSString EMPTY_COS_STRING = new COSString("");
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private String removeFileExtension(String filename) {
|
||||
return GeneralUtils.removeExtension(filename);
|
||||
@@ -105,8 +108,8 @@ public class RedactController {
|
||||
"This endpoint redacts content from a PDF file based on manually specified areas. "
|
||||
+ "Users can specify areas to redact and optionally convert the PDF to an image. "
|
||||
+ "Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> redactPDF(
|
||||
@ModelAttribute ManualRedactPdfRequest request) throws IOException {
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
List<RedactionArea> redactionAreas = request.getRedactions();
|
||||
@@ -120,30 +123,24 @@ public class RedactController {
|
||||
|
||||
if (Boolean.TRUE.equals(request.getConvertPDFToImage())) {
|
||||
try (PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document)) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
convertedPdf.save(baos);
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
convertedPdf,
|
||||
removeFileExtension(
|
||||
Objects.requireNonNull(
|
||||
Filenames.toSimpleFileName(
|
||||
file.getOriginalFilename())))
|
||||
+ "_redacted.pdf");
|
||||
+ "_redacted.pdf",
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
removeFileExtension(
|
||||
Objects.requireNonNull(
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())))
|
||||
+ "_redacted.pdf");
|
||||
+ "_redacted.pdf",
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +501,8 @@ public class RedactController {
|
||||
"This endpoint automatically redacts text from a PDF file based on specified patterns. "
|
||||
+ "Users can provide text patterns to redact, with options for regex and whole word matching. "
|
||||
+ "Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request) {
|
||||
public ResponseEntity<StreamingResponseBody> redactPdf(
|
||||
@ModelAttribute RedactPdfRequest request) {
|
||||
String[] listOfText = request.getListOfText().split("\n");
|
||||
boolean useRegex = Boolean.TRUE.equals(request.getUseRegex());
|
||||
boolean wholeWordSearchBool = Boolean.TRUE.equals(request.getWholeWordSearch());
|
||||
@@ -545,20 +543,15 @@ public class RedactController {
|
||||
|
||||
if (allFoundTextsByPage.isEmpty()) {
|
||||
log.info("No text found matching redaction patterns");
|
||||
byte[] originalContent;
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
document.save(baos);
|
||||
originalContent = baos.toByteArray();
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
originalContent,
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
removeFileExtension(
|
||||
Objects.requireNonNull(
|
||||
Filenames.toSimpleFileName(
|
||||
request.getFileInput()
|
||||
.getOriginalFilename())))
|
||||
+ "_redacted.pdf");
|
||||
+ "_redacted.pdf",
|
||||
tempFileManager);
|
||||
}
|
||||
|
||||
boolean fallbackToBoxOnlyMode;
|
||||
@@ -587,7 +580,7 @@ public class RedactController {
|
||||
findTextToRedact(
|
||||
fallbackDocument, listOfText, useRegex, wholeWordSearchBool);
|
||||
|
||||
byte[] pdfContent =
|
||||
TempFile finalized =
|
||||
finalizeRedaction(
|
||||
fallbackDocument,
|
||||
allFoundTextsByPage,
|
||||
@@ -596,8 +589,8 @@ public class RedactController {
|
||||
request.getConvertPDFToImage(),
|
||||
false); // Box-only mode, use original box sizes
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
finalized,
|
||||
removeFileExtension(
|
||||
Objects.requireNonNull(
|
||||
Filenames.toSimpleFileName(
|
||||
@@ -606,7 +599,7 @@ public class RedactController {
|
||||
+ "_redacted.pdf");
|
||||
}
|
||||
|
||||
byte[] pdfContent =
|
||||
TempFile finalized =
|
||||
finalizeRedaction(
|
||||
document,
|
||||
allFoundTextsByPage,
|
||||
@@ -615,8 +608,8 @@ public class RedactController {
|
||||
request.getConvertPDFToImage(),
|
||||
true); // Text removal mode, use reduced box sizes
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
finalized,
|
||||
removeFileExtension(
|
||||
Objects.requireNonNull(
|
||||
Filenames.toSimpleFileName(
|
||||
@@ -733,7 +726,7 @@ public class RedactController {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] finalizeRedaction(
|
||||
private TempFile finalizeRedaction(
|
||||
PDDocument document,
|
||||
Map<Integer, List<PDFText>> allFoundTextsByPage,
|
||||
String colorString,
|
||||
@@ -759,29 +752,37 @@ public class RedactController {
|
||||
try (PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document)) {
|
||||
cleanDocumentMetadata(convertedPdf);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
convertedPdf.save(baos);
|
||||
byte[] out = baos.toByteArray();
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
convertedPdf.save(tempOut.getFile());
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
log.info(
|
||||
"Redaction finalized (image mode): {} pages ➜ {} KB",
|
||||
convertedPdf.getNumberOfPages(),
|
||||
out.length / 1024);
|
||||
tempOut.getFile().length() / 1024);
|
||||
|
||||
return out;
|
||||
return tempOut;
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
byte[] out = baos.toByteArray();
|
||||
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
|
||||
try {
|
||||
document.save(tempOut.getFile());
|
||||
} catch (IOException e) {
|
||||
tempOut.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
log.info(
|
||||
"Redaction finalized: {} pages ➜ {} KB",
|
||||
document.getNumberOfPages(),
|
||||
out.length / 1024);
|
||||
tempOut.getFile().length() / 1024);
|
||||
|
||||
return out;
|
||||
return tempOut;
|
||||
}
|
||||
|
||||
private void cleanDocumentMetadata(PDDocument document) {
|
||||
|
||||
+6
-2
@@ -11,6 +11,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -22,6 +23,7 @@ import stirling.software.common.annotations.api.SecurityApi;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@SecurityApi
|
||||
@@ -29,6 +31,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class RemoveCertSignController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/remove-cert-sign")
|
||||
@StandardPdfResponse
|
||||
@@ -37,7 +40,7 @@ public class RemoveCertSignController {
|
||||
description =
|
||||
"This endpoint accepts a PDF file and returns the PDF file without the digital"
|
||||
+ " signature. Input:PDF, Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
|
||||
public ResponseEntity<StreamingResponseBody> removeCertSignPDF(@ModelAttribute PDFFile request)
|
||||
throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
|
||||
@@ -63,7 +66,8 @@ public class RemoveCertSignController {
|
||||
// Return the modified PDF as a response
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_unsigned.pdf"));
|
||||
GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_unsigned.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@@ -29,6 +28,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -41,6 +41,7 @@ import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.SecurityApi;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@Slf4j
|
||||
@@ -49,6 +50,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class SanitizeController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/sanitize-pdf")
|
||||
@StandardPdfResponse
|
||||
@@ -57,8 +59,8 @@ public class SanitizeController {
|
||||
description =
|
||||
"This endpoint processes a PDF file and removes specific elements based on the"
|
||||
+ " provided options. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<StreamingResponseBody> sanitizePDF(
|
||||
@ModelAttribute SanitizePdfRequest request) throws IOException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
boolean removeJavaScript = Boolean.TRUE.equals(request.getRemoveJavaScript());
|
||||
boolean removeEmbeddedFiles = Boolean.TRUE.equals(request.getRemoveEmbeddedFiles());
|
||||
@@ -92,14 +94,11 @@ public class SanitizeController {
|
||||
sanitizeFonts(document);
|
||||
}
|
||||
|
||||
// Save the sanitized document to output stream
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
document.save(outputStream);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
outputStream.toByteArray(),
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
inputFile.getOriginalFilename(), "_sanitized.pdf"));
|
||||
inputFile.getOriginalFilename(), "_sanitized.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-9
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -33,6 +32,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -46,6 +46,8 @@ import stirling.software.common.annotations.api.SecurityApi;
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@Slf4j
|
||||
@@ -74,6 +76,7 @@ public class TimestampController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final ApplicationProperties applicationProperties;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/timestamp-pdf")
|
||||
@StandardPdfResponse
|
||||
@@ -84,8 +87,8 @@ public class TimestampController {
|
||||
+ " document timestamp into the PDF. Only a SHA-256 hash of the"
|
||||
+ " document is sent to the TSA — the PDF itself never leaves the"
|
||||
+ " server. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> timestampPdf(@ModelAttribute TimestampPdfRequest request)
|
||||
throws Exception {
|
||||
public ResponseEntity<StreamingResponseBody> timestampPdf(
|
||||
@ModelAttribute TimestampPdfRequest request) throws Exception {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
ApplicationProperties.Security.Timestamp tsConfig =
|
||||
applicationProperties.getSecurity().getTimestamp();
|
||||
@@ -124,9 +127,10 @@ public class TimestampController {
|
||||
+ " via settings.yml (security.timestamp.customTsaUrls).");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
|
||||
TempFile tempOutputFile = tempFileManager.createManagedTempFile(".pdf");
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile);
|
||||
OutputStream outputStream =
|
||||
java.nio.file.Files.newOutputStream(tempOutputFile.getPath())) {
|
||||
PDSignature signature = new PDSignature();
|
||||
signature.setType(COSName.DOC_TIME_STAMP);
|
||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
||||
@@ -136,10 +140,13 @@ public class TimestampController {
|
||||
document.addSignature(signature, content -> requestTimestampToken(content, tsaUrl));
|
||||
|
||||
document.saveIncremental(outputStream);
|
||||
} catch (Exception e) {
|
||||
tempOutputFile.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
outputStream.toByteArray(),
|
||||
return WebResponseUtils.pdfFileToWebResponse(
|
||||
tempOutputFile,
|
||||
GeneralUtils.generateFilename(inputFile.getOriginalFilename(), "_timestamped.pdf"));
|
||||
}
|
||||
|
||||
@@ -163,7 +170,8 @@ public class TimestampController {
|
||||
TimeStampRequest tsaRequest = generator.generate(digestAlgorithm, hash, nonce);
|
||||
byte[] requestBytes = tsaRequest.getEncoded();
|
||||
|
||||
// Contact the TSA server (redirects disabled to prevent SSRF via redirect)
|
||||
// Contact the TSA server — tsaUrl is validated against an allowlist above,
|
||||
// and redirects are disabled below to prevent SSRF via redirect.
|
||||
connection = (HttpURLConnection) URI.create(tsaUrl).toURL().openConnection();
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
+9
-4
@@ -30,6 +30,7 @@ import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -45,6 +46,7 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.PdfUtils;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@SecurityApi
|
||||
@@ -52,6 +54,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
public class WatermarkController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
@@ -73,8 +76,8 @@ public class WatermarkController {
|
||||
"This endpoint adds a watermark to a given PDF file. Users can specify the"
|
||||
+ " watermark type (text or image), rotation, opacity, width spacer, and"
|
||||
+ " height spacer. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> addWatermark(@Valid @ModelAttribute AddWatermarkRequest request)
|
||||
throws IOException, Exception {
|
||||
public ResponseEntity<StreamingResponseBody> addWatermark(
|
||||
@Valid @ModelAttribute AddWatermarkRequest request) throws IOException, Exception {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String pdfFileName = pdfFile.getOriginalFilename();
|
||||
if (pdfFileName != null && (pdfFileName.contains("..") || pdfFileName.startsWith("/"))) {
|
||||
@@ -151,14 +154,16 @@ public class WatermarkController {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
convertedPdf,
|
||||
GeneralUtils.generateFilename(
|
||||
pdfFile.getOriginalFilename(), "_watermarked.pdf"));
|
||||
pdfFile.getOriginalFilename(), "_watermarked.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
} else {
|
||||
// Return the watermarked PDF as a response
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
GeneralUtils.generateFilename(
|
||||
pdfFile.getOriginalFilename(), "_watermarked.pdf"));
|
||||
pdfFile.getOriginalFilename(), "_watermarked.pdf"),
|
||||
tempFileManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -11,21 +11,18 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.service.SharedSignatureService;
|
||||
import stirling.software.common.service.PersonalSignatureServiceInterface;
|
||||
import stirling.software.common.service.UserServiceInterface;
|
||||
|
||||
/**
|
||||
* Unified signature image controller that works for both authenticated and unauthenticated users.
|
||||
* Uses composition pattern: - Core SharedSignatureService (always available): reads shared
|
||||
* signatures - PersonalSignatureService (proprietary, optional): reads personal signatures For
|
||||
* authenticated signature management (save/delete), see proprietary SignatureController.
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/general")
|
||||
@Tag(name = "Signature Assets", description = "Retrieve saved signature images")
|
||||
public class SignatureImageController {
|
||||
|
||||
private final SharedSignatureService sharedSignatureService;
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@@ -28,7 +29,7 @@ public class ConvertPDFToMarkdown {
|
||||
summary = "Convert PDF to Markdown",
|
||||
description =
|
||||
"This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToMarkdown(@ModelAttribute PDFFile file)
|
||||
public ResponseEntity<StreamingResponseBody> processPdfToMarkdown(@ModelAttribute PDFFile file)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = file.getFileInput();
|
||||
PDFToFile pdfToFile = new PDFToFile(tempFileManager);
|
||||
|
||||
@@ -25,6 +25,11 @@ server.http2.enabled=true
|
||||
# Enable virtual threads (Java 21+, pinning fix in Java 25)
|
||||
spring.threads.virtual.enabled=true
|
||||
|
||||
# Only run security filters on REQUEST and ERROR dispatches (not ASYNC).
|
||||
# StreamingResponseBody triggers an ASYNC dispatch on completion; without this,
|
||||
# Spring Security re-evaluates authorization after the response is already committed.
|
||||
spring.security.filter.dispatcher-types=REQUEST,ERROR
|
||||
|
||||
# Response compression
|
||||
server.compression.enabled=true
|
||||
server.compression.min-response-size=1024
|
||||
|
||||
+54
-12
@@ -1,8 +1,10 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -11,6 +13,7 @@ import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -21,17 +24,47 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.BookletImpositionRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BookletImpositionControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@InjectMocks private BookletImpositionController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private MockMultipartFile createRealPdf(int numPages) throws IOException {
|
||||
Path path = tempDir.resolve("test.pdf");
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
@@ -61,11 +94,12 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
try (PDDocument result = Loader.loadPDF(response.getBody())) {
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
try (PDDocument result = Loader.loadPDF(drainBody(response))) {
|
||||
assertThat(result.getNumberOfPages()).isGreaterThan(0);
|
||||
}
|
||||
}
|
||||
@@ -92,10 +126,11 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,7 +144,8 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -126,7 +162,8 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -143,7 +180,8 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -160,7 +198,8 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -177,7 +216,8 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -192,7 +232,8 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -220,7 +261,8 @@ class BookletImpositionControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.createBookletImposition(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.createBookletImposition(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
+39
-11
@@ -1,9 +1,11 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
@@ -29,21 +31,47 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("CropController Tests")
|
||||
class CropControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@InjectMocks private CropController cropController;
|
||||
private TestPdfFactory pdfFactory;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
pdfFactory = new TestPdfFactory();
|
||||
}
|
||||
|
||||
@@ -177,7 +205,7 @@ class CropControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
|
||||
.thenReturn(newDocument);
|
||||
|
||||
ResponseEntity<byte[]> response = cropController.cropPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request);
|
||||
|
||||
assertThat(response)
|
||||
.isNotNull()
|
||||
@@ -214,7 +242,7 @@ class CropControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
|
||||
.thenReturn(newDocument);
|
||||
|
||||
ResponseEntity<byte[]> response = cropController.cropPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request);
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
@@ -234,7 +262,7 @@ class CropControllerTest {
|
||||
private TestPdfFactory autoCropPdfFactory;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
void setUp() throws Exception {
|
||||
autoCropPdfFactory = new TestPdfFactory();
|
||||
}
|
||||
|
||||
@@ -254,13 +282,13 @@ class CropControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc))
|
||||
.thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = cropController.cropPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request);
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
|
||||
try (PDDocument result = Loader.loadPDF(response.getBody())) {
|
||||
try (PDDocument result = Loader.loadPDF(drainBody(response))) {
|
||||
assertThat(result.getNumberOfPages()).isEqualTo(1);
|
||||
|
||||
PDPage page = result.getPage(0);
|
||||
@@ -285,13 +313,13 @@ class CropControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc))
|
||||
.thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = cropController.cropPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request);
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
|
||||
Assertions.assertNotNull(response.getBody());
|
||||
try (PDDocument result = Loader.loadPDF(response.getBody())) {
|
||||
try (PDDocument result = Loader.loadPDF(drainBody(response))) {
|
||||
assertThat(result.getNumberOfPages()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -648,7 +676,7 @@ class CropControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
|
||||
.thenReturn(newDocument);
|
||||
|
||||
ResponseEntity<byte[]> response = cropController.cropPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request);
|
||||
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
@@ -676,7 +704,7 @@ class CropControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument))
|
||||
.thenReturn(newDocument);
|
||||
|
||||
ResponseEntity<byte[]> response = cropController.cropPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = cropController.cropPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(mockDocument, times(1)).close();
|
||||
|
||||
+44
-21
@@ -1,10 +1,12 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -26,10 +28,13 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.controller.api.EditTableOfContentsController.BookmarkItem;
|
||||
import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
@@ -40,6 +45,7 @@ class EditTableOfContentsControllerTest {
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Mock private ObjectMapper objectMapper;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private EditTableOfContentsController editTableOfContentsController;
|
||||
|
||||
@@ -53,7 +59,19 @@ class EditTableOfContentsControllerTest {
|
||||
private PDOutlineItem mockOutlineItem;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
mockFile =
|
||||
new MockMultipartFile(
|
||||
"file",
|
||||
@@ -226,18 +244,19 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockDocument.getNumberOfPages()).thenReturn(5);
|
||||
when(mockDocument.getPage(0)).thenReturn(mockPage1);
|
||||
|
||||
// Mock saving behavior
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
ByteArrayOutputStream baos = invocation.getArgument(0);
|
||||
baos.write("mocked pdf content".getBytes());
|
||||
lenient()
|
||||
.doAnswer(
|
||||
inv -> {
|
||||
File f = inv.getArgument(0);
|
||||
java.nio.file.Files.write(f.toPath(), "mock pdf".getBytes());
|
||||
return null;
|
||||
})
|
||||
.when(mockDocument)
|
||||
.save(any(ByteArrayOutputStream.class));
|
||||
.save(any(File.class));
|
||||
|
||||
// When
|
||||
ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request);
|
||||
ResponseEntity<StreamingResponseBody> result =
|
||||
editTableOfContentsController.editTableOfContents(request);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
@@ -289,17 +308,19 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockDocument.getPage(0)).thenReturn(mockPage1);
|
||||
when(mockDocument.getPage(1)).thenReturn(mockPage2);
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
ByteArrayOutputStream baos = invocation.getArgument(0);
|
||||
baos.write("mocked pdf content".getBytes());
|
||||
lenient()
|
||||
.doAnswer(
|
||||
inv -> {
|
||||
File f = inv.getArgument(0);
|
||||
java.nio.file.Files.write(f.toPath(), "mock pdf".getBytes());
|
||||
return null;
|
||||
})
|
||||
.when(mockDocument)
|
||||
.save(any(ByteArrayOutputStream.class));
|
||||
.save(any(File.class));
|
||||
|
||||
// When
|
||||
ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request);
|
||||
ResponseEntity<StreamingResponseBody> result =
|
||||
editTableOfContentsController.editTableOfContents(request);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
@@ -341,17 +362,19 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockDocument.getPage(0)).thenReturn(mockPage1); // For negative page number
|
||||
when(mockDocument.getPage(4)).thenReturn(mockPage2); // For page number exceeding bounds
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
ByteArrayOutputStream baos = invocation.getArgument(0);
|
||||
baos.write("mocked pdf content".getBytes());
|
||||
lenient()
|
||||
.doAnswer(
|
||||
inv -> {
|
||||
File f = inv.getArgument(0);
|
||||
java.nio.file.Files.write(f.toPath(), "mock pdf".getBytes());
|
||||
return null;
|
||||
})
|
||||
.when(mockDocument)
|
||||
.save(any(ByteArrayOutputStream.class));
|
||||
.save(any(File.class));
|
||||
|
||||
// When
|
||||
ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request);
|
||||
ResponseEntity<StreamingResponseBody> result =
|
||||
editTableOfContentsController.editTableOfContents(request);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
|
||||
+39
-6
@@ -1,5 +1,12 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@@ -15,14 +22,28 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MultiPageLayoutControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private MultiPageLayoutController controller;
|
||||
|
||||
@@ -30,7 +51,19 @@ class MultiPageLayoutControllerTest {
|
||||
private MockMultipartFile fileNoExt;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
void setup() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
fileWithExt =
|
||||
new MockMultipartFile(
|
||||
"fileInput", "test.pdf", "application/pdf", new byte[] {1, 2, 3});
|
||||
@@ -64,11 +97,11 @@ class MultiPageLayoutControllerTest {
|
||||
req.setAddBorder(Boolean.FALSE);
|
||||
req.setFileInput(fileWithExt);
|
||||
|
||||
ResponseEntity<byte[]> resp = controller.mergeMultiplePagesIntoOne(req);
|
||||
ResponseEntity<StreamingResponseBody> resp = controller.mergeMultiplePagesIntoOne(req);
|
||||
Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType());
|
||||
Assertions.assertNotNull(resp.getBody());
|
||||
Assertions.assertTrue(resp.getBody().length > 0);
|
||||
Assertions.assertTrue(drainBody(resp).length > 0);
|
||||
Assertions.assertEquals(
|
||||
"test_multi_page_layout.pdf",
|
||||
resp.getHeaders().getContentDisposition().getFilename());
|
||||
@@ -89,11 +122,11 @@ class MultiPageLayoutControllerTest {
|
||||
req.setAddBorder(Boolean.TRUE);
|
||||
req.setFileInput(fileWithExt);
|
||||
|
||||
ResponseEntity<byte[]> resp = controller.mergeMultiplePagesIntoOne(req);
|
||||
ResponseEntity<StreamingResponseBody> resp = controller.mergeMultiplePagesIntoOne(req);
|
||||
Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType());
|
||||
Assertions.assertNotNull(resp.getBody());
|
||||
Assertions.assertTrue(resp.getBody().length > 0);
|
||||
Assertions.assertTrue(drainBody(resp).length > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,7 +145,7 @@ class MultiPageLayoutControllerTest {
|
||||
req.setAddBorder(Boolean.TRUE);
|
||||
req.setFileInput(fileNoExt);
|
||||
|
||||
ResponseEntity<byte[]> resp = controller.mergeMultiplePagesIntoOne(req);
|
||||
ResponseEntity<StreamingResponseBody> resp = controller.mergeMultiplePagesIntoOne(req);
|
||||
Assertions.assertEquals(
|
||||
"name_multi_page_layout.pdf",
|
||||
resp.getHeaders().getContentDisposition().getFilename());
|
||||
|
||||
+43
-7
@@ -2,16 +2,22 @@ package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -24,17 +30,47 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PdfOverlayControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@InjectMocks private PdfOverlayController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private byte[] createPdf(int numPages) throws IOException {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
for (int i = 0; i < numPages; i++) {
|
||||
@@ -71,12 +107,12 @@ class PdfOverlayControllerTest {
|
||||
when(pdfDocumentFactory.load(any(MultipartFile.class)))
|
||||
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayPdfs(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
assertTrue(drainBody(response).length > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,7 +141,7 @@ class PdfOverlayControllerTest {
|
||||
when(pdfDocumentFactory.load(any(MultipartFile.class)))
|
||||
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayPdfs(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -137,7 +173,7 @@ class PdfOverlayControllerTest {
|
||||
when(pdfDocumentFactory.load(any(MultipartFile.class)))
|
||||
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayPdfs(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -168,7 +204,7 @@ class PdfOverlayControllerTest {
|
||||
when(pdfDocumentFactory.load(any(MultipartFile.class)))
|
||||
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayPdfs(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -258,7 +294,7 @@ class PdfOverlayControllerTest {
|
||||
when(pdfDocumentFactory.load(any(MultipartFile.class)))
|
||||
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayPdfs(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -291,7 +327,7 @@ class PdfOverlayControllerTest {
|
||||
when(pdfDocumentFactory.load(any(MultipartFile.class)))
|
||||
.thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes()));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayPdfs(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayPdfs(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
|
||||
+35
-11
@@ -1,12 +1,16 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -15,18 +19,38 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RearrangePagesPDFControllerTest {
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private RearrangePagesPDFController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private MockMultipartFile createMockPdf() {
|
||||
return new MockMultipartFile(
|
||||
"fileInput", "test.pdf", MediaType.APPLICATION_PDF_VALUE, new byte[] {1, 2, 3});
|
||||
@@ -43,7 +67,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(mockDoc);
|
||||
when(mockDoc.getNumberOfPages()).thenReturn(5);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.deletePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.deletePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -73,7 +97,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -103,7 +127,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
verify(mockNewDoc).addPage(page1);
|
||||
@@ -131,7 +155,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
verify(mockNewDoc).addPage(page0);
|
||||
@@ -157,7 +181,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -184,7 +208,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -208,7 +232,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -232,7 +256,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -260,7 +284,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -284,7 +308,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
// 2 pages * 3 duplicates = 6 addPage calls
|
||||
@@ -309,7 +333,7 @@ class RearrangePagesPDFControllerTest {
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc))
|
||||
.thenReturn(mockNewDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.rearrangePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.rearrangePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
|
||||
+26
-1
@@ -1,15 +1,20 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -18,17 +23,37 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RotationControllerTest {
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private RotationController rotationController;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRotatePDF() throws IOException {
|
||||
// Create a mock file
|
||||
@@ -50,7 +75,7 @@ public class RotationControllerTest {
|
||||
when(mockPage.getRotation()).thenReturn(0);
|
||||
|
||||
// Act
|
||||
ResponseEntity<byte[]> response = rotationController.rotatePDF(request);
|
||||
ResponseEntity<StreamingResponseBody> response = rotationController.rotatePDF(request);
|
||||
|
||||
// Assert
|
||||
verify(mockPage).setRotation(90);
|
||||
|
||||
+42
-9
@@ -2,8 +2,10 @@ package stirling.software.SPDF.controller.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -11,6 +13,7 @@ import java.nio.file.Path;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -21,17 +24,47 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ScalePagesControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@InjectMocks private ScalePagesController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private byte[] createRealPdf(PDRectangle pageSize, int numPages) throws IOException {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
for (int i = 0; i < numPages; i++) {
|
||||
@@ -68,12 +101,12 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
assertTrue(drainBody(response).length > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,7 +123,7 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -110,7 +143,7 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -130,7 +163,7 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -150,7 +183,7 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -187,7 +220,7 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -207,7 +240,7 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -248,7 +281,7 @@ class ScalePagesControllerTest {
|
||||
|
||||
setupFactory();
|
||||
|
||||
ResponseEntity<byte[]> response = controller.scalePages(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.scalePages(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
|
||||
+16
@@ -4,8 +4,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -31,6 +34,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -44,6 +48,18 @@ class SplitPdfBySectionsControllerTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
when(tempFileManager.createTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
|
||||
+39
-12
@@ -5,7 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
@@ -19,6 +22,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -29,6 +33,7 @@ import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.SPDF.model.api.converters.ConvertEbookToPdfRequest;
|
||||
@@ -37,11 +42,22 @@ import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.ProcessExecutor.Processes;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertEbookToPDFControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@@ -49,6 +65,22 @@ class ConvertEbookToPDFControllerTest {
|
||||
|
||||
@InjectMocks private ConvertEbookToPDFController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertEbookToPdf_buildsCalibreCommandAndCleansUp() throws Exception {
|
||||
when(endpointConfiguration.isGroupEnabled("Calibre")).thenReturn(true);
|
||||
@@ -113,16 +145,14 @@ class ConvertEbookToPDFControllerTest {
|
||||
return execResult;
|
||||
});
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("result".getBytes());
|
||||
wr.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfDocToWebResponse(
|
||||
mockDocument, "ebook_convertedToPDF.pdf"))
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("result".getBytes());
|
||||
wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
gu.when(() -> GeneralUtils.generateFilename("ebook.epub", "_convertedToPDF.pdf"))
|
||||
.thenReturn("ebook_convertedToPDF.pdf");
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEbookToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEbookToPdf(request);
|
||||
|
||||
assertSame(expectedResponse, response);
|
||||
|
||||
@@ -232,14 +262,11 @@ class ConvertEbookToPDFControllerTest {
|
||||
gu.when(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class)))
|
||||
.thenReturn(optimizedBytes);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(optimizedBytes);
|
||||
wr.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
optimizedBytes, "ebook_convertedToPDF.pdf"))
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(optimizedBytes);
|
||||
wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEbookToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEbookToPdf(request);
|
||||
|
||||
assertSame(expectedResponse, response);
|
||||
gu.verify(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class)));
|
||||
|
||||
+65
-34
@@ -4,12 +4,18 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -21,17 +27,29 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.model.api.converters.EmlToPdfRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.CustomHtmlSanitizer;
|
||||
import stirling.software.common.util.EmlToPdf;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertEmlToPDFTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private RuntimePathConfig runtimePathConfig;
|
||||
@@ -40,35 +58,51 @@ class ConvertEmlToPDFTest {
|
||||
|
||||
@InjectMocks private ConvertEmlToPDF controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertEmlToPdf_emptyFileReturnsBadRequest() {
|
||||
void convertEmlToPdf_emptyFileReturnsBadRequest() throws java.io.IOException {
|
||||
MockMultipartFile emptyFile =
|
||||
new MockMultipartFile("fileInput", "test.eml", "message/rfc822", new byte[0]);
|
||||
|
||||
EmlToPdfRequest request = new EmlToPdfRequest();
|
||||
request.setFileInput(emptyFile);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
assertTrue(
|
||||
new String(response.getBody(), StandardCharsets.UTF_8)
|
||||
new String(drainBody(response), StandardCharsets.UTF_8)
|
||||
.contains("No file provided"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertEmlToPdf_nullFilenameReturnsBadRequest() {
|
||||
void convertEmlToPdf_nullFilenameReturnsBadRequest() throws java.io.IOException {
|
||||
MockMultipartFile file =
|
||||
new MockMultipartFile("fileInput", null, "message/rfc822", "content".getBytes());
|
||||
|
||||
EmlToPdfRequest request = new EmlToPdfRequest();
|
||||
request.setFileInput(file);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
assertTrue(
|
||||
new String(response.getBody(), StandardCharsets.UTF_8).contains("valid filename"));
|
||||
new String(drainBody(response), StandardCharsets.UTF_8).contains("valid filename"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,24 +113,24 @@ class ConvertEmlToPDFTest {
|
||||
EmlToPdfRequest request = new EmlToPdfRequest();
|
||||
request.setFileInput(file);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertEmlToPdf_invalidFileTypeReturnsBadRequest() {
|
||||
void convertEmlToPdf_invalidFileTypeReturnsBadRequest() throws java.io.IOException {
|
||||
MockMultipartFile file =
|
||||
new MockMultipartFile("fileInput", "test.txt", "text/plain", "content".getBytes());
|
||||
|
||||
EmlToPdfRequest request = new EmlToPdfRequest();
|
||||
request.setFileInput(file);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
assertTrue(
|
||||
new String(response.getBody(), StandardCharsets.UTF_8)
|
||||
new String(drainBody(response), StandardCharsets.UTF_8)
|
||||
.contains("valid EML or MSG"));
|
||||
}
|
||||
|
||||
@@ -112,7 +146,7 @@ class ConvertEmlToPDFTest {
|
||||
|
||||
when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint");
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(pdfBytes);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(pdfBytes);
|
||||
|
||||
try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class);
|
||||
MockedStatic<WebResponseUtils> wrMock =
|
||||
@@ -132,14 +166,14 @@ class ConvertEmlToPDFTest {
|
||||
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
pdfBytes, "test.eml.pdf", MediaType.APPLICATION_PDF))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertArrayEquals(pdfBytes, response.getBody());
|
||||
assertArrayEquals(pdfBytes, drainBody(response));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,8 +188,8 @@ class ConvertEmlToPDFTest {
|
||||
request.setFileInput(file);
|
||||
request.setDownloadHtml(true);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse =
|
||||
ResponseEntity.ok(htmlContent.getBytes(StandardCharsets.UTF_8));
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk(htmlContent.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class);
|
||||
MockedStatic<WebResponseUtils> wrMock =
|
||||
@@ -171,13 +205,11 @@ class ConvertEmlToPDFTest {
|
||||
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
htmlContent.getBytes(StandardCharsets.UTF_8),
|
||||
"test.eml.html",
|
||||
MediaType.TEXT_HTML))
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class), anyString(), any(MediaType.class)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
@@ -203,11 +235,11 @@ class ConvertEmlToPDFTest {
|
||||
eq(customHtmlSanitizer)))
|
||||
.thenThrow(new IOException("Parse error"));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||
assertTrue(
|
||||
new String(response.getBody(), StandardCharsets.UTF_8)
|
||||
new String(drainBody(response), StandardCharsets.UTF_8)
|
||||
.contains("HTML conversion failed"));
|
||||
}
|
||||
}
|
||||
@@ -231,11 +263,11 @@ class ConvertEmlToPDFTest {
|
||||
any(), any(), any(), any(), any(), any(), any()))
|
||||
.thenReturn(null);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||
assertTrue(
|
||||
new String(response.getBody(), StandardCharsets.UTF_8)
|
||||
new String(drainBody(response), StandardCharsets.UTF_8)
|
||||
.contains("empty output"));
|
||||
}
|
||||
}
|
||||
@@ -255,7 +287,7 @@ class ConvertEmlToPDFTest {
|
||||
|
||||
when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint");
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(pdfBytes);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(pdfBytes);
|
||||
|
||||
try (MockedStatic<EmlToPdf> emlMock = Mockito.mockStatic(EmlToPdf.class);
|
||||
MockedStatic<WebResponseUtils> wrMock =
|
||||
@@ -269,13 +301,11 @@ class ConvertEmlToPDFTest {
|
||||
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
any(String.class),
|
||||
any(MediaType.class)))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
@@ -300,11 +330,12 @@ class ConvertEmlToPDFTest {
|
||||
any(), any(), any(), any(), any(), any(), any()))
|
||||
.thenThrow(new InterruptedException("interrupted"));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertEmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertEmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||
assertTrue(
|
||||
new String(response.getBody(), StandardCharsets.UTF_8).contains("interrupted"));
|
||||
new String(drainBody(response), StandardCharsets.UTF_8)
|
||||
.contains("interrupted"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
-6
@@ -3,9 +3,16 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -16,6 +23,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
|
||||
@@ -23,11 +31,22 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.CustomHtmlSanitizer;
|
||||
import stirling.software.common.util.FileToPdf;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertHtmlToPDFTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private RuntimePathConfig runtimePathConfig;
|
||||
@@ -36,6 +55,22 @@ class ConvertHtmlToPDFTest {
|
||||
|
||||
@InjectMocks private ConvertHtmlToPDF controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlToPdf_nullFileInputThrows() {
|
||||
HTMLToPdfRequest request = new HTMLToPdfRequest();
|
||||
@@ -69,7 +104,7 @@ class ConvertHtmlToPDFTest {
|
||||
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes))
|
||||
.thenReturn(processedPdf);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(processedPdf);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf);
|
||||
|
||||
try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class);
|
||||
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
|
||||
@@ -90,10 +125,13 @@ class ConvertHtmlToPDFTest {
|
||||
guMock.when(() -> GeneralUtils.generateFilename("test.html", ".pdf"))
|
||||
.thenReturn("test.pdf");
|
||||
|
||||
wrMock.when(() -> WebResponseUtils.bytesToWebResponse(processedPdf, "test.pdf"))
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.HtmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.HtmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
@@ -114,7 +152,7 @@ class ConvertHtmlToPDFTest {
|
||||
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes))
|
||||
.thenReturn(processedPdf);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(processedPdf);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf);
|
||||
|
||||
try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class);
|
||||
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
|
||||
@@ -135,10 +173,13 @@ class ConvertHtmlToPDFTest {
|
||||
guMock.when(() -> GeneralUtils.generateFilename("archive.zip", ".pdf"))
|
||||
.thenReturn("archive.pdf");
|
||||
|
||||
wrMock.when(() -> WebResponseUtils.bytesToWebResponse(processedPdf, "archive.pdf"))
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.HtmlToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.HtmlToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
|
||||
+11
@@ -14,6 +14,7 @@ import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
||||
@@ -25,6 +26,16 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertImgPDFControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
+41
-3
@@ -4,10 +4,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -18,6 +25,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.model.api.GeneralFile;
|
||||
@@ -25,11 +33,22 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.CustomHtmlSanitizer;
|
||||
import stirling.software.common.util.FileToPdf;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertMarkdownToPdfTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private RuntimePathConfig runtimePathConfig;
|
||||
@@ -38,6 +57,22 @@ class ConvertMarkdownToPdfTest {
|
||||
|
||||
@InjectMocks private ConvertMarkdownToPdf controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void markdownToPdf_nullFileInputThrows() {
|
||||
GeneralFile generalFile = new GeneralFile();
|
||||
@@ -71,7 +106,7 @@ class ConvertMarkdownToPdfTest {
|
||||
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(any(byte[].class)))
|
||||
.thenReturn(processedPdf);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(processedPdf);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf);
|
||||
|
||||
try (MockedStatic<FileToPdf> ftpMock = Mockito.mockStatic(FileToPdf.class);
|
||||
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
|
||||
@@ -92,10 +127,13 @@ class ConvertMarkdownToPdfTest {
|
||||
guMock.when(() -> GeneralUtils.generateFilename("readme.md", ".pdf"))
|
||||
.thenReturn("readme.pdf");
|
||||
|
||||
wrMock.when(() -> WebResponseUtils.bytesToWebResponse(processedPdf, "readme.pdf"))
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.markdownToPdf(generalFile);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.markdownToPdf(generalFile);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
|
||||
+38
-6
@@ -3,12 +3,15 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
@@ -18,6 +21,7 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -29,6 +33,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.SPDF.model.api.converters.ConvertPdfToEpubRequest;
|
||||
@@ -38,10 +43,21 @@ import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.ProcessExecutor.Processes;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertPDFToEpubControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static final MediaType EPUB_MEDIA_TYPE = MediaType.valueOf("application/epub+zip");
|
||||
|
||||
@@ -50,6 +66,22 @@ class ConvertPDFToEpubControllerTest {
|
||||
|
||||
@InjectMocks private ConvertPDFToEpubController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertPdfToEpub_buildsGoldenCommandAndCleansUp() throws Exception {
|
||||
when(endpointConfiguration.isGroupEnabled("Calibre")).thenReturn(true);
|
||||
@@ -110,7 +142,7 @@ class ConvertPDFToEpubControllerTest {
|
||||
|
||||
gu.when(() -> GeneralUtils.generateFilename("novel.pdf", "_convertedToEPUB.epub"))
|
||||
.thenReturn("novel_convertedToEPUB.epub");
|
||||
ResponseEntity<byte[]> response = controller.convertPdfToEpub(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToEpub(request);
|
||||
|
||||
List<String> command = commandCaptor.getValue();
|
||||
assertEquals(13, command.size());
|
||||
@@ -134,7 +166,7 @@ class ConvertPDFToEpubControllerTest {
|
||||
assertEquals(
|
||||
"novel_convertedToEPUB.epub",
|
||||
response.getHeaders().getContentDisposition().getFilename());
|
||||
assertEquals("epub", new String(response.getBody(), StandardCharsets.UTF_8));
|
||||
assertEquals("epub", new String(drainBody(response), StandardCharsets.UTF_8));
|
||||
|
||||
verify(tempFileManager).deleteTempDirectory(workingDir);
|
||||
assertEquals(workingDir, deletedDir.get());
|
||||
@@ -202,7 +234,7 @@ class ConvertPDFToEpubControllerTest {
|
||||
|
||||
gu.when(() -> GeneralUtils.generateFilename("story.pdf", "_convertedToEPUB.epub"))
|
||||
.thenReturn("story_convertedToEPUB.epub");
|
||||
ResponseEntity<byte[]> response = controller.convertPdfToEpub(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToEpub(request);
|
||||
|
||||
List<String> command = commandCaptor.getValue();
|
||||
assertTrue(command.stream().noneMatch(arg -> "--chapter".equals(arg)));
|
||||
@@ -220,7 +252,7 @@ class ConvertPDFToEpubControllerTest {
|
||||
assertEquals(
|
||||
"story_convertedToEPUB.epub",
|
||||
response.getHeaders().getContentDisposition().getFilename());
|
||||
assertEquals("epub", new String(response.getBody(), StandardCharsets.UTF_8));
|
||||
assertEquals("epub", new String(drainBody(response), StandardCharsets.UTF_8));
|
||||
} finally {
|
||||
deleteIfExists(workingDir);
|
||||
}
|
||||
@@ -287,7 +319,7 @@ class ConvertPDFToEpubControllerTest {
|
||||
|
||||
gu.when(() -> GeneralUtils.generateFilename("book.pdf", "_convertedToAZW3.azw3"))
|
||||
.thenReturn("book_convertedToAZW3.azw3");
|
||||
ResponseEntity<byte[]> response = controller.convertPdfToEpub(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToEpub(request);
|
||||
|
||||
List<String> command = commandCaptor.getValue();
|
||||
assertEquals("ebook-convert", command.get(0));
|
||||
@@ -308,7 +340,7 @@ class ConvertPDFToEpubControllerTest {
|
||||
assertEquals(
|
||||
"book_convertedToAZW3.azw3",
|
||||
response.getHeaders().getContentDisposition().getFilename());
|
||||
assertEquals("azw3", new String(response.getBody(), StandardCharsets.UTF_8));
|
||||
assertEquals("azw3", new String(drainBody(response), StandardCharsets.UTF_8));
|
||||
|
||||
verify(tempFileManager).deleteTempDirectory(workingDir);
|
||||
} finally {
|
||||
|
||||
+27
-1
@@ -2,11 +2,17 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -17,18 +23,38 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertPDFToExcelControllerTest {
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private ConvertPDFToExcelController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdfToExcel_noTablesReturnsNoContent() throws Exception {
|
||||
MockMultipartFile pdfFile =
|
||||
@@ -55,7 +81,7 @@ class ConvertPDFToExcelControllerTest {
|
||||
Mockito.eq(true)))
|
||||
.thenReturn(List.of(1));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.pdfToExcel(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.pdfToExcel(request);
|
||||
|
||||
// tabula may or may not find tables in an empty page
|
||||
assertNotNull(response);
|
||||
|
||||
+43
-8
@@ -4,10 +4,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -18,6 +24,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
|
||||
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
|
||||
@@ -27,11 +34,22 @@ import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.PDFToFile;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertPDFToOfficeTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@@ -39,6 +57,22 @@ class ConvertPDFToOfficeTest {
|
||||
|
||||
@InjectMocks private ConvertPDFToOffice controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private MockMultipartFile createPdfFile() {
|
||||
return new MockMultipartFile(
|
||||
"fileInput", "document.pdf", "application/pdf", "pdf-content".getBytes());
|
||||
@@ -51,7 +85,8 @@ class ConvertPDFToOfficeTest {
|
||||
request.setFileInput(pdfFile);
|
||||
request.setOutputFormat("pptx");
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("pptx-content".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("pptx-content".getBytes());
|
||||
|
||||
try (MockedStatic<PDFToFile> mock =
|
||||
Mockito.mockStatic(PDFToFile.class, Mockito.CALLS_REAL_METHODS)) {
|
||||
@@ -80,7 +115,8 @@ class ConvertPDFToOfficeTest {
|
||||
realDoc.addPage(new org.apache.pdfbox.pdmodel.PDPage());
|
||||
when(pdfDocumentFactory.load(pdfFile)).thenReturn(realDoc);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("text content".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("text content".getBytes());
|
||||
|
||||
try (MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
|
||||
MockedStatic<WebResponseUtils> wrMock =
|
||||
@@ -91,13 +127,12 @@ class ConvertPDFToOfficeTest {
|
||||
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
eq("document.txt"),
|
||||
eq(MediaType.TEXT_PLAIN)))
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class), anyString(), any(MediaType.class)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.processPdfToRTForTXT(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.processPdfToRTForTXT(request);
|
||||
|
||||
assertSame(expectedResponse, response);
|
||||
}
|
||||
|
||||
+53
-64
@@ -4,34 +4,67 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.service.PdfJsonConversionService;
|
||||
import stirling.software.common.model.api.GeneralFile;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertPdfJsonControllerTest {
|
||||
|
||||
@Mock private PdfJsonConversionService pdfJsonConversionService;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private ConvertPdfJsonController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertPdfToJson_nullFileInputThrows() {
|
||||
PDFFile request = new PDFFile();
|
||||
@@ -51,19 +84,11 @@ class ConvertPdfJsonControllerTest {
|
||||
|
||||
when(pdfJsonConversionService.convertPdfToJson(pdfFile, false)).thenReturn(jsonBytes);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(jsonBytes);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.convertPdfToJson(request, false);
|
||||
|
||||
try (MockedStatic<WebResponseUtils> wrMock = Mockito.mockStatic(WebResponseUtils.class)) {
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
jsonBytes, "doc.json", MediaType.APPLICATION_JSON))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertPdfToJson(request, false);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,20 +102,10 @@ class ConvertPdfJsonControllerTest {
|
||||
|
||||
when(pdfJsonConversionService.convertPdfToJson(pdfFile, true)).thenReturn(jsonBytes);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(jsonBytes);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertPdfToJson(request, true);
|
||||
|
||||
try (MockedStatic<WebResponseUtils> wrMock = Mockito.mockStatic(WebResponseUtils.class)) {
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
jsonBytes, "doc.json", MediaType.APPLICATION_JSON))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertPdfToJson(request, true);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
verify(pdfJsonConversionService).convertPdfToJson(pdfFile, true);
|
||||
}
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
verify(pdfJsonConversionService).convertPdfToJson(pdfFile, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,16 +127,10 @@ class ConvertPdfJsonControllerTest {
|
||||
|
||||
when(pdfJsonConversionService.convertJsonToPdf(jsonFile)).thenReturn(pdfBytes);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(pdfBytes);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertJsonToPdf(request);
|
||||
|
||||
try (MockedStatic<WebResponseUtils> wrMock = Mockito.mockStatic(WebResponseUtils.class)) {
|
||||
wrMock.when(() -> WebResponseUtils.bytesToWebResponse(pdfBytes, "doc.pdf"))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertJsonToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -144,7 +153,7 @@ class ConvertPdfJsonControllerTest {
|
||||
when(pdfJsonConversionService.extractDocumentMetadata(eq(pdfFile), any(String.class)))
|
||||
.thenReturn(jsonBytes);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractPdfMetadata(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractPdfMetadata(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
|
||||
@@ -168,19 +177,10 @@ class ConvertPdfJsonControllerTest {
|
||||
|
||||
when(pdfJsonConversionService.extractSinglePage(jobId, 1)).thenReturn(jsonBytes);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(jsonBytes);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractSinglePage(jobId, 1);
|
||||
|
||||
try (MockedStatic<WebResponseUtils> wrMock = Mockito.mockStatic(WebResponseUtils.class)) {
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
jsonBytes, "page_1.json", MediaType.APPLICATION_JSON))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractSinglePage(jobId, 1);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -190,20 +190,9 @@ class ConvertPdfJsonControllerTest {
|
||||
|
||||
when(pdfJsonConversionService.extractPageFonts(jobId, 1)).thenReturn(jsonBytes);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(jsonBytes);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractPageFonts(jobId, 1);
|
||||
|
||||
try (MockedStatic<WebResponseUtils> wrMock = Mockito.mockStatic(WebResponseUtils.class)) {
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
jsonBytes,
|
||||
"page_fonts_1.json",
|
||||
MediaType.APPLICATION_JSON))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractPageFonts(jobId, 1);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
+52
-20
@@ -3,11 +3,17 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -16,20 +22,31 @@ import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.SvgToPdfRequest;
|
||||
import stirling.software.SPDF.utils.SvgToPdf;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.SvgSanitizer;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConvertSvgToPDFTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private SvgSanitizer svgSanitizer;
|
||||
@@ -37,16 +54,32 @@ class ConvertSvgToPDFTest {
|
||||
|
||||
@InjectMocks private ConvertSvgToPDF controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertSvgToPdf_nullFilesReturnsBadRequest() {
|
||||
void convertSvgToPdf_nullFilesReturnsBadRequest() throws java.io.IOException {
|
||||
SvgToPdfRequest request = new SvgToPdfRequest();
|
||||
request.setFileInput(null);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
assertTrue(
|
||||
new String(response.getBody(), StandardCharsets.UTF_8)
|
||||
new String(drainBody(response), StandardCharsets.UTF_8)
|
||||
.contains("No files provided"));
|
||||
}
|
||||
|
||||
@@ -55,7 +88,7 @@ class ConvertSvgToPDFTest {
|
||||
SvgToPdfRequest request = new SvgToPdfRequest();
|
||||
request.setFileInput(new MockMultipartFile[0]);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
@@ -69,10 +102,11 @@ class ConvertSvgToPDFTest {
|
||||
request.setFileInput(new MockMultipartFile[] {txtFile});
|
||||
request.setCombineIntoSinglePdf(false);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
assertTrue(new String(response.getBody(), StandardCharsets.UTF_8).contains("No valid SVG"));
|
||||
assertTrue(
|
||||
new String(drainBody(response), StandardCharsets.UTF_8).contains("No valid SVG"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -84,7 +118,7 @@ class ConvertSvgToPDFTest {
|
||||
request.setFileInput(new MockMultipartFile[] {emptyFile});
|
||||
request.setCombineIntoSinglePdf(false);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
@@ -107,7 +141,7 @@ class ConvertSvgToPDFTest {
|
||||
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes))
|
||||
.thenReturn(processedPdf);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(processedPdf);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf);
|
||||
|
||||
try (MockedStatic<SvgToPdf> svgMock = Mockito.mockStatic(SvgToPdf.class);
|
||||
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
|
||||
@@ -121,11 +155,11 @@ class ConvertSvgToPDFTest {
|
||||
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
processedPdf, "drawing.pdf", MediaType.APPLICATION_PDF))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
@@ -154,7 +188,7 @@ class ConvertSvgToPDFTest {
|
||||
when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(combinedPdf))
|
||||
.thenReturn(processedPdf);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(processedPdf);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(processedPdf);
|
||||
|
||||
try (MockedStatic<SvgToPdf> svgMock = Mockito.mockStatic(SvgToPdf.class);
|
||||
MockedStatic<GeneralUtils> guMock = Mockito.mockStatic(GeneralUtils.class);
|
||||
@@ -168,13 +202,11 @@ class ConvertSvgToPDFTest {
|
||||
|
||||
wrMock.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
processedPdf,
|
||||
"a_combined.pdf",
|
||||
MediaType.APPLICATION_PDF))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
}
|
||||
@@ -189,7 +221,7 @@ class ConvertSvgToPDFTest {
|
||||
request.setFileInput(new MockMultipartFile[] {nullNameFile});
|
||||
request.setCombineIntoSinglePdf(false);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
@@ -206,7 +238,7 @@ class ConvertSvgToPDFTest {
|
||||
|
||||
when(svgSanitizer.sanitize(svgContent)).thenThrow(new IOException("sanitization error"));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertSvgToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.convertSvgToPdf(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
|
||||
+50
-32
@@ -4,9 +4,10 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -34,6 +35,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
@@ -43,13 +45,25 @@ import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.ProcessExecutor.Processes;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
public class ConvertWebsiteToPdfTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static final Pattern PDF_FILENAME_PATTERN = Pattern.compile("[A-Za-z0-9_]+\\.pdf");
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private RuntimePathConfig runtimePathConfig;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
private ConvertWebsiteToPDF sut;
|
||||
@@ -58,6 +72,18 @@ public class ConvertWebsiteToPdfTest {
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
mocks = MockitoAnnotations.openMocks(this);
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
|
||||
// Enable feature (adjust structure for your project if necessary)
|
||||
applicationProperties = new ApplicationProperties();
|
||||
@@ -68,7 +94,12 @@ public class ConvertWebsiteToPdfTest {
|
||||
when(pdfDocumentFactory.load(any(File.class))).thenReturn(new PDDocument());
|
||||
|
||||
// Build SUT
|
||||
sut = new ConvertWebsiteToPDF(pdfDocumentFactory, runtimePathConfig, applicationProperties);
|
||||
sut =
|
||||
new ConvertWebsiteToPDF(
|
||||
pdfDocumentFactory,
|
||||
runtimePathConfig,
|
||||
applicationProperties,
|
||||
tempFileManager);
|
||||
|
||||
// Provide RequestContext for ServletUriComponentsBuilder
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
@@ -172,38 +203,29 @@ public class ConvertWebsiteToPdfTest {
|
||||
request.setUrlInput("https://example.com");
|
||||
|
||||
try (MockedStatic<ProcessExecutor> pe = Mockito.mockStatic(ProcessExecutor.class);
|
||||
MockedStatic<WebResponseUtils> wr = Mockito.mockStatic(WebResponseUtils.class);
|
||||
MockedStatic<GeneralUtils> gu = Mockito.mockStatic(GeneralUtils.class);
|
||||
MockedStatic<HttpClient> httpClient = mockHttpClientReturning("<html></html>")) {
|
||||
|
||||
// Force URL checks to be positive
|
||||
gu.when(() -> GeneralUtils.isValidURL("https://example.com")).thenReturn(true);
|
||||
gu.when(() -> GeneralUtils.isURLReachable("https://example.com")).thenReturn(true);
|
||||
gu.when(() -> GeneralUtils.convertToFileName(anyString())).thenReturn("example_com");
|
||||
gu.when(() -> GeneralUtils.generateFilename(anyString(), anyString()))
|
||||
.thenAnswer(inv -> inv.<String>getArgument(0) + inv.<String>getArgument(1));
|
||||
|
||||
// correct ProcessExecutor!
|
||||
ProcessExecutor mockExec = Mockito.mock(ProcessExecutor.class);
|
||||
pe.when(() -> ProcessExecutor.getInstance(Processes.WEASYPRINT)).thenReturn(mockExec);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<String>> cmdCaptor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
// Return value of correct type
|
||||
ProcessExecutorResult dummyResult = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(mockExec.runCommandWithOutputHandling(cmdCaptor.capture()))
|
||||
.thenReturn(dummyResult);
|
||||
|
||||
ResponseEntity<byte[]> fakeResponse = ResponseEntity.ok(new byte[0]);
|
||||
|
||||
wr.when(
|
||||
() ->
|
||||
WebResponseUtils.baosToWebResponse(
|
||||
any(ByteArrayOutputStream.class), any()))
|
||||
.thenReturn(fakeResponse);
|
||||
|
||||
// Act
|
||||
ResponseEntity<?> resp = sut.urlToPdf(request);
|
||||
|
||||
// Assert – Response OK
|
||||
// Assert
|
||||
assertNotNull(resp);
|
||||
assertEquals(HttpStatus.OK, resp.getStatusCode());
|
||||
|
||||
// Assert – WeasyPrint command correct
|
||||
@@ -236,17 +258,19 @@ public class ConvertWebsiteToPdfTest {
|
||||
|
||||
try (MockedStatic<GeneralUtils> gu = Mockito.mockStatic(GeneralUtils.class);
|
||||
MockedStatic<ProcessExecutor> pe = Mockito.mockStatic(ProcessExecutor.class);
|
||||
MockedStatic<WebResponseUtils> wr = Mockito.mockStatic(WebResponseUtils.class);
|
||||
MockedStatic<Files> files = Mockito.mockStatic(Files.class);
|
||||
MockedStatic<HttpClient> httpClient = mockHttpClientReturning("<html></html>")) {
|
||||
|
||||
// Force URL checks to be positive
|
||||
gu.when(() -> GeneralUtils.isValidURL("https://example.com")).thenReturn(true);
|
||||
gu.when(() -> GeneralUtils.isURLReachable("https://example.com")).thenReturn(true);
|
||||
gu.when(() -> GeneralUtils.convertToFileName(anyString())).thenReturn("example_com");
|
||||
gu.when(() -> GeneralUtils.generateFilename(anyString(), anyString()))
|
||||
.thenAnswer(inv -> inv.<String>getArgument(0) + inv.<String>getArgument(1));
|
||||
|
||||
// Force temp files + provoke delete error
|
||||
files.when(() -> Files.createTempFile("url_input_", ".html")).thenReturn(htmlTemp);
|
||||
files.when(() -> Files.createTempFile("output_", ".pdf")).thenReturn(preCreatedTemp);
|
||||
files.when(() -> Files.createTempFile(eq("test"), anyString()))
|
||||
.thenReturn(preCreatedTemp);
|
||||
files.when(
|
||||
() ->
|
||||
Files.writeString(
|
||||
@@ -257,26 +281,20 @@ public class ConvertWebsiteToPdfTest {
|
||||
files.when(() -> Files.deleteIfExists(htmlTemp)).thenReturn(true);
|
||||
files.when(() -> Files.deleteIfExists(preCreatedTemp))
|
||||
.thenThrow(new IOException("fail delete"));
|
||||
files.when(() -> Files.exists(preCreatedTemp)).thenReturn(true); // for the assert
|
||||
files.when(() -> Files.exists(preCreatedTemp)).thenReturn(true);
|
||||
files.when(() -> Files.size(any(Path.class))).thenReturn(100L);
|
||||
files.when(() -> Files.copy(any(Path.class), any(java.io.OutputStream.class)))
|
||||
.thenReturn(0L);
|
||||
files.when(() -> Files.newOutputStream(any(Path.class)))
|
||||
.thenAnswer(inv -> new java.io.ByteArrayOutputStream());
|
||||
|
||||
// ProcessExecutor
|
||||
ProcessExecutor mockExec = Mockito.mock(ProcessExecutor.class);
|
||||
pe.when(() -> ProcessExecutor.getInstance(Processes.WEASYPRINT)).thenReturn(mockExec);
|
||||
ProcessExecutorResult dummy = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(mockExec.runCommandWithOutputHandling(Mockito.<List>any())).thenReturn(dummy);
|
||||
|
||||
// WebResponseUtils
|
||||
ResponseEntity<byte[]> fakeResponse = ResponseEntity.ok(new byte[0]);
|
||||
wr.when(
|
||||
() ->
|
||||
WebResponseUtils.baosToWebResponse(
|
||||
any(ByteArrayOutputStream.class), any()))
|
||||
.thenReturn(fakeResponse);
|
||||
|
||||
// Act: should not throw and should return a Response
|
||||
ResponseEntity<?> resp = assertDoesNotThrow(() -> sut.urlToPdf(request));
|
||||
|
||||
// Assert
|
||||
assertNotNull(resp, "Response should not be null");
|
||||
assertEquals(HttpStatus.OK, resp.getStatusCode());
|
||||
assertTrue(
|
||||
|
||||
+23
-3
@@ -3,11 +3,13 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -25,11 +27,13 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.SPDF.model.api.converters.PdfVectorExportRequest;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -44,6 +48,18 @@ class PdfVectorExportControllerTest {
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
when(tempFileManager.createTempFile(any()))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
@@ -106,7 +122,8 @@ class PdfVectorExportControllerTest {
|
||||
PdfVectorExportRequest request = new PdfVectorExportRequest();
|
||||
request.setFileInput(file);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertGhostscriptInputsToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.convertGhostscriptInputsToPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK);
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF);
|
||||
@@ -123,11 +140,14 @@ class PdfVectorExportControllerTest {
|
||||
PdfVectorExportRequest request = new PdfVectorExportRequest();
|
||||
request.setFileInput(file);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.convertGhostscriptInputsToPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.convertGhostscriptInputsToPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK);
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF);
|
||||
assertThat(response.getBody()).contains(content);
|
||||
java.io.ByteArrayOutputStream baosVerify = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baosVerify);
|
||||
assertThat(baosVerify.toByteArray()).contains(content);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+29
-10
@@ -17,6 +17,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFComparisonAndCount;
|
||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
@@ -26,12 +27,24 @@ import stirling.software.SPDF.model.api.filter.PageRotationRequest;
|
||||
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.PdfUtils;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FilterControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private FilterController filterController;
|
||||
|
||||
@@ -59,19 +72,22 @@ class FilterControllerTest {
|
||||
PDDocument mockDoc = mock(PDDocument.class);
|
||||
when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(new byte[] {1, 2, 3});
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(new byte[] {1, 2, 3});
|
||||
|
||||
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class);
|
||||
MockedStatic<WebResponseUtils> webMock = mockStatic(WebResponseUtils.class)) {
|
||||
|
||||
pdfUtilsMock.when(() -> PdfUtils.hasText(mockDoc, "all", "hello")).thenReturn(true);
|
||||
webMock.when(() -> WebResponseUtils.pdfDocToWebResponse(mockDoc, "test.pdf"))
|
||||
webMock.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfDocToWebResponse(
|
||||
mockDoc, "test.pdf", tempFileManager))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> result = filterController.containsText(request);
|
||||
ResponseEntity<StreamingResponseBody> result = filterController.containsText(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertArrayEquals(new byte[] {1, 2, 3}, result.getBody());
|
||||
assertArrayEquals(new byte[] {1, 2, 3}, drainBody(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +104,7 @@ class FilterControllerTest {
|
||||
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class)) {
|
||||
pdfUtilsMock.when(() -> PdfUtils.hasText(mockDoc, "all", "missing")).thenReturn(false);
|
||||
|
||||
ResponseEntity<byte[]> result = filterController.containsText(request);
|
||||
ResponseEntity<StreamingResponseBody> result = filterController.containsText(request);
|
||||
|
||||
assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode());
|
||||
assertNull(result.getBody());
|
||||
@@ -106,19 +122,22 @@ class FilterControllerTest {
|
||||
PDDocument mockDoc = mock(PDDocument.class);
|
||||
when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc);
|
||||
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(new byte[] {4, 5, 6});
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(new byte[] {4, 5, 6});
|
||||
|
||||
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class);
|
||||
MockedStatic<WebResponseUtils> webMock = mockStatic(WebResponseUtils.class)) {
|
||||
|
||||
pdfUtilsMock.when(() -> PdfUtils.hasImages(mockDoc, "all")).thenReturn(true);
|
||||
webMock.when(() -> WebResponseUtils.pdfDocToWebResponse(mockDoc, "test.pdf"))
|
||||
webMock.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfDocToWebResponse(
|
||||
mockDoc, "test.pdf", tempFileManager))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> result = filterController.containsImage(request);
|
||||
ResponseEntity<StreamingResponseBody> result = filterController.containsImage(request);
|
||||
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertArrayEquals(new byte[] {4, 5, 6}, result.getBody());
|
||||
assertArrayEquals(new byte[] {4, 5, 6}, drainBody(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +153,7 @@ class FilterControllerTest {
|
||||
try (MockedStatic<PdfUtils> pdfUtilsMock = mockStatic(PdfUtils.class)) {
|
||||
pdfUtilsMock.when(() -> PdfUtils.hasImages(mockDoc, "1")).thenReturn(false);
|
||||
|
||||
ResponseEntity<byte[]> result = filterController.containsImage(request);
|
||||
ResponseEntity<StreamingResponseBody> result = filterController.containsImage(request);
|
||||
|
||||
assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode());
|
||||
}
|
||||
|
||||
+35
-4
@@ -2,10 +2,13 @@ package stirling.software.SPDF.controller.api.form;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
@@ -22,8 +25,11 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
@@ -31,8 +37,19 @@ import tools.jackson.databind.json.JsonMapper;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("FormFillController Tests")
|
||||
class FormFillControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
private ObjectMapper realObjectMapper;
|
||||
|
||||
@@ -40,6 +57,18 @@ class FormFillControllerTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
realObjectMapper = JsonMapper.builder().build();
|
||||
// Inject real ObjectMapper via reflection since @InjectMocks uses the mock
|
||||
var field = FormFillController.class.getDeclaredField("objectMapper");
|
||||
@@ -203,7 +232,8 @@ class FormFillControllerTest {
|
||||
when(pdfDocumentFactory.load(eq(file))).thenReturn(doc);
|
||||
|
||||
byte[] payload = "{\"field1\":\"value1\"}".getBytes();
|
||||
ResponseEntity<byte[]> response = controller.fillForm(file, payload, false);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.fillForm(file, payload, false);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
@@ -216,7 +246,7 @@ class FormFillControllerTest {
|
||||
PDDocument doc = createMinimalPdf();
|
||||
when(pdfDocumentFactory.load(eq(file))).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.fillForm(file, null, false);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.fillForm(file, null, false);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -257,7 +287,7 @@ class FormFillControllerTest {
|
||||
when(pdfDocumentFactory.load(eq(file))).thenReturn(doc);
|
||||
|
||||
byte[] payload = "[\"field1\"]".getBytes();
|
||||
ResponseEntity<byte[]> response = controller.deleteFields(file, payload);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.deleteFields(file, payload);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -293,7 +323,8 @@ class FormFillControllerTest {
|
||||
String json =
|
||||
"[{\"targetName\":\"f1\",\"name\":null,\"label\":null,\"type\":null,"
|
||||
+ "\"required\":null,\"multiSelect\":null,\"options\":null,\"defaultValue\":\"newVal\",\"tooltip\":null}]";
|
||||
ResponseEntity<byte[]> response = controller.modifyFields(file, json.getBytes());
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.modifyFields(file, json.getBytes());
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
+44
-9
@@ -1,9 +1,12 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -19,18 +22,32 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.AddAttachmentRequest;
|
||||
import stirling.software.SPDF.service.AttachmentServiceInterface;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AttachmentControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Mock private AttachmentServiceInterface pdfAttachmentService;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private AttachmentController attachmentController;
|
||||
|
||||
@@ -42,7 +59,19 @@ class AttachmentControllerTest {
|
||||
private PDDocument modifiedMockDocument;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
pdfFile =
|
||||
new MockMultipartFile(
|
||||
"fileInput",
|
||||
@@ -71,8 +100,8 @@ class AttachmentControllerTest {
|
||||
List<MultipartFile> attachments = List.of(attachment1, attachment2);
|
||||
request.setAttachments(attachments);
|
||||
request.setFileInput(pdfFile);
|
||||
ResponseEntity<byte[]> expectedResponse =
|
||||
ResponseEntity.ok("modified PDF content".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("modified PDF content".getBytes());
|
||||
|
||||
when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument);
|
||||
when(pdfAttachmentService.addAttachment(mockDocument, attachments))
|
||||
@@ -84,10 +113,13 @@ class AttachmentControllerTest {
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfDocToWebResponse(
|
||||
eq(mockDocument), eq("test_with_attachments.pdf")))
|
||||
any(PDDocument.class),
|
||||
anyString(),
|
||||
any(TempFileManager.class)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = attachmentController.addAttachments(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
attachmentController.addAttachments(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -102,8 +134,8 @@ class AttachmentControllerTest {
|
||||
List<MultipartFile> attachments = List.of(attachment1);
|
||||
request.setAttachments(attachments);
|
||||
request.setFileInput(pdfFile);
|
||||
ResponseEntity<byte[]> expectedResponse =
|
||||
ResponseEntity.ok("modified PDF content".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("modified PDF content".getBytes());
|
||||
|
||||
when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument);
|
||||
when(pdfAttachmentService.addAttachment(mockDocument, attachments))
|
||||
@@ -115,10 +147,13 @@ class AttachmentControllerTest {
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfDocToWebResponse(
|
||||
eq(mockDocument), eq("test_with_attachments.pdf")))
|
||||
any(PDDocument.class),
|
||||
anyString(),
|
||||
any(TempFileManager.class)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = attachmentController.addAttachments(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
attachmentController.addAttachments(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
|
||||
+41
-8
@@ -1,8 +1,10 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -14,6 +16,7 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -24,17 +27,47 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AutoRenameControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@InjectMocks private AutoRenameController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private MockMultipartFile createPdfWithText(String text, float fontSize) throws IOException {
|
||||
Path path = tempDir.resolve("test.pdf");
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
@@ -68,10 +101,10 @@ class AutoRenameControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
String contentDisposition = response.getHeaders().getFirst("Content-Disposition");
|
||||
assertThat(contentDisposition).contains(".pdf");
|
||||
}
|
||||
@@ -94,7 +127,7 @@ class AutoRenameControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -107,7 +140,7 @@ class AutoRenameControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -155,7 +188,7 @@ class AutoRenameControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
// The largest font text should be used as title (URL-encoded in Content-Disposition)
|
||||
@@ -173,7 +206,7 @@ class AutoRenameControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
// Should fallback to original filename since header is too long
|
||||
@@ -189,7 +222,7 @@ class AutoRenameControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
// Special characters should be sanitized
|
||||
@@ -215,7 +248,7 @@ class AutoRenameControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.extractHeader(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
+44
-11
@@ -1,8 +1,10 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -14,6 +16,7 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -24,17 +27,47 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DecompressPdfControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@InjectMocks private DecompressPdfController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private MockMultipartFile createRealPdf(String content) throws IOException {
|
||||
Path path = tempDir.resolve("test.pdf");
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
@@ -64,12 +97,12 @@ class DecompressPdfControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.decompressPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
// Verify the result is a valid PDF
|
||||
try (PDDocument result = Loader.loadPDF(response.getBody())) {
|
||||
try (PDDocument result = Loader.loadPDF(drainBody(response))) {
|
||||
assertThat(result.getNumberOfPages()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -83,10 +116,10 @@ class DecompressPdfControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.decompressPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,7 +147,7 @@ class DecompressPdfControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.decompressPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
String contentDisposition = response.getHeaders().getFirst("Content-Disposition");
|
||||
@@ -150,10 +183,10 @@ class DecompressPdfControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.decompressPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
try (PDDocument result = Loader.loadPDF(response.getBody())) {
|
||||
try (PDDocument result = Loader.loadPDF(drainBody(response))) {
|
||||
assertThat(result.getNumberOfPages()).isEqualTo(3);
|
||||
}
|
||||
}
|
||||
@@ -167,11 +200,11 @@ class DecompressPdfControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.decompressPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request);
|
||||
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
// Decompressed PDF should generally be larger or equal to compressed
|
||||
assertThat(response.getBody().length).isGreaterThan(0);
|
||||
assertThat(drainBody(response).length).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -183,7 +216,7 @@ class DecompressPdfControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.decompressPdf(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.decompressPdf(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
+42
-9
@@ -1,8 +1,10 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -16,6 +18,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -26,17 +29,47 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.FlattenRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FlattenControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@TempDir Path tempDir;
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
@InjectMocks private FlattenController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
}
|
||||
|
||||
private MockMultipartFile createPdf() throws IOException {
|
||||
Path path = tempDir.resolve("test.pdf");
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
@@ -65,10 +98,10 @@ class FlattenControllerTest {
|
||||
PDDocument doc = Loader.loadPDF(file.getBytes());
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.flatten(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.flatten(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -85,7 +118,7 @@ class FlattenControllerTest {
|
||||
when(doc.getDocumentCatalog()).thenReturn(catalog);
|
||||
when(catalog.getAcroForm()).thenReturn(null);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.flatten(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.flatten(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(doc).close();
|
||||
@@ -105,7 +138,7 @@ class FlattenControllerTest {
|
||||
when(doc.getDocumentCatalog()).thenReturn(catalog);
|
||||
when(catalog.getAcroForm()).thenReturn(form);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.flatten(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.flatten(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
verify(form).flatten();
|
||||
@@ -137,10 +170,10 @@ class FlattenControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.flatten(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.flatten(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotEmpty();
|
||||
assertThat(drainBody(response)).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -155,7 +188,7 @@ class FlattenControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.flatten(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.flatten(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -173,7 +206,7 @@ class FlattenControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.flatten(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.flatten(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
@@ -191,7 +224,7 @@ class FlattenControllerTest {
|
||||
when(pdfDocumentFactory.load(file)).thenReturn(doc);
|
||||
when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.flatten(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.flatten(request);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
+58
-13
@@ -2,11 +2,14 @@ package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
@@ -24,15 +27,29 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OverlayImageControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private OverlayImageController controller;
|
||||
|
||||
@@ -41,6 +58,18 @@ class OverlayImageControllerTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
pdfFile =
|
||||
new MockMultipartFile(
|
||||
"fileInput",
|
||||
@@ -79,12 +108,16 @@ class OverlayImageControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("result".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("result".getBytes());
|
||||
mockedWebResponse
|
||||
.when(() -> WebResponseUtils.bytesToWebResponse(any(byte[].class), anyString()))
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayImage(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -103,7 +136,7 @@ class OverlayImageControllerTest {
|
||||
|
||||
when(pdfDocumentFactory.load(any(byte[].class))).thenThrow(new IOException("bad PDF"));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayImage(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request);
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
@@ -124,12 +157,16 @@ class OverlayImageControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("result".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("result".getBytes());
|
||||
mockedWebResponse
|
||||
.when(() -> WebResponseUtils.bytesToWebResponse(any(byte[].class), anyString()))
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayImage(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -152,12 +189,16 @@ class OverlayImageControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("result".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("result".getBytes());
|
||||
mockedWebResponse
|
||||
.when(() -> WebResponseUtils.bytesToWebResponse(any(byte[].class), anyString()))
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.overlayImage(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -180,13 +221,17 @@ class OverlayImageControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("result".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("result".getBytes());
|
||||
mockedWebResponse
|
||||
.when(() -> WebResponseUtils.bytesToWebResponse(any(byte[].class), anyString()))
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
// Should not throw - coordinates are passed to contentStream.drawImage
|
||||
ResponseEntity<byte[]> response = controller.overlayImage(request);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.overlayImage(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
|
||||
+49
-29
@@ -2,10 +2,13 @@ package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -19,17 +22,31 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
|
||||
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
|
||||
import stirling.software.common.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.common.model.api.misc.ReplaceAndInvert;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ReplaceAndInvertColorControllerTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private ReplaceAndInvertColorService replaceAndInvertColorService;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private ReplaceAndInvertColorController controller;
|
||||
|
||||
@@ -37,7 +54,19 @@ class ReplaceAndInvertColorControllerTest {
|
||||
private ReplaceAndInvertColorRequest request;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
pdfFile =
|
||||
new MockMultipartFile(
|
||||
"fileInput",
|
||||
@@ -67,17 +96,16 @@ class ReplaceAndInvertColorControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(resultBytes);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes);
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
anyString(),
|
||||
eq(MediaType.APPLICATION_PDF)))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.replaceAndInvertColor(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.replaceAndInvertColor(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -104,17 +132,16 @@ class ReplaceAndInvertColorControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(resultBytes);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes);
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
anyString(),
|
||||
eq(MediaType.APPLICATION_PDF)))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.replaceAndInvertColor(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.replaceAndInvertColor(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -139,17 +166,16 @@ class ReplaceAndInvertColorControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(resultBytes);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes);
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
anyString(),
|
||||
eq(MediaType.APPLICATION_PDF)))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.replaceAndInvertColor(request);
|
||||
ResponseEntity<StreamingResponseBody> response =
|
||||
controller.replaceAndInvertColor(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
@@ -179,24 +205,18 @@ class ReplaceAndInvertColorControllerTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok(resultBytes);
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse = streamingOk(resultBytes);
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
contains("_inverted.pdf"),
|
||||
eq(MediaType.APPLICATION_PDF)))
|
||||
WebResponseUtils.pdfFileToWebResponse(
|
||||
any(TempFile.class), anyString()))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
controller.replaceAndInvertColor(request);
|
||||
|
||||
mockedWebResponse.verify(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
contains("_inverted.pdf"),
|
||||
eq(MediaType.APPLICATION_PDF)));
|
||||
() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+58
-56
@@ -1,9 +1,11 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
@@ -21,15 +23,29 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ShowJavascriptTest {
|
||||
private static ResponseEntity<StreamingResponseBody> streamingOk(byte[] bytes) {
|
||||
return ResponseEntity.ok(out -> out.write(bytes));
|
||||
}
|
||||
|
||||
private static byte[] drainBody(ResponseEntity<StreamingResponseBody> response)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
response.getBody().writeTo(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private ShowJavascript showJavascript;
|
||||
|
||||
@@ -37,7 +53,19 @@ class ShowJavascriptTest {
|
||||
private PDFFile request;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
pdfFile =
|
||||
new MockMultipartFile(
|
||||
"fileInput",
|
||||
@@ -58,31 +86,25 @@ class ShowJavascriptTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("no js".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("no js".getBytes());
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class),
|
||||
eq("test.pdf.js"),
|
||||
eq(MediaType.TEXT_PLAIN)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = showJavascript.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
// Verify the bytes passed contain the "does not contain" message
|
||||
mockedWebResponse.verify(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
argThat(
|
||||
bytes -> {
|
||||
String content =
|
||||
new String(bytes, StandardCharsets.UTF_8);
|
||||
return content.contains(
|
||||
"does not contain Javascript");
|
||||
}),
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class),
|
||||
eq("test.pdf.js"),
|
||||
eq(MediaType.TEXT_PLAIN)));
|
||||
}
|
||||
@@ -107,30 +129,24 @@ class ShowJavascriptTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("js content".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("js content".getBytes());
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class),
|
||||
eq("test.pdf.js"),
|
||||
eq(MediaType.TEXT_PLAIN)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = showJavascript.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request);
|
||||
|
||||
assertNotNull(response);
|
||||
// Verify the bytes passed contain the script content
|
||||
mockedWebResponse.verify(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
argThat(
|
||||
bytes -> {
|
||||
String content =
|
||||
new String(bytes, StandardCharsets.UTF_8);
|
||||
return content.contains("alert('hello');")
|
||||
&& content.contains("Script1");
|
||||
}),
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class),
|
||||
eq("test.pdf.js"),
|
||||
eq(MediaType.TEXT_PLAIN)));
|
||||
}
|
||||
@@ -144,31 +160,24 @@ class ShowJavascriptTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("no js".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("no js".getBytes());
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class),
|
||||
anyString(),
|
||||
eq(MediaType.TEXT_PLAIN)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = showJavascript.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request);
|
||||
|
||||
assertNotNull(response);
|
||||
mockedWebResponse.verify(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
argThat(
|
||||
bytes -> {
|
||||
String content =
|
||||
new String(bytes, StandardCharsets.UTF_8);
|
||||
return content.contains(
|
||||
"does not contain Javascript");
|
||||
}),
|
||||
anyString(),
|
||||
eq(MediaType.TEXT_PLAIN)));
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class), anyString(), eq(MediaType.TEXT_PLAIN)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,31 +200,24 @@ class ShowJavascriptTest {
|
||||
|
||||
try (MockedStatic<WebResponseUtils> mockedWebResponse =
|
||||
mockStatic(WebResponseUtils.class)) {
|
||||
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("no js".getBytes());
|
||||
ResponseEntity<StreamingResponseBody> expectedResponse =
|
||||
streamingOk("no js".getBytes());
|
||||
mockedWebResponse
|
||||
.when(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
any(byte[].class),
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class),
|
||||
anyString(),
|
||||
eq(MediaType.TEXT_PLAIN)))
|
||||
.thenReturn(expectedResponse);
|
||||
|
||||
ResponseEntity<byte[]> response = showJavascript.extractHeader(request);
|
||||
ResponseEntity<StreamingResponseBody> response = showJavascript.extractHeader(request);
|
||||
|
||||
assertNotNull(response);
|
||||
mockedWebResponse.verify(
|
||||
() ->
|
||||
WebResponseUtils.bytesToWebResponse(
|
||||
argThat(
|
||||
bytes -> {
|
||||
String content =
|
||||
new String(bytes, StandardCharsets.UTF_8);
|
||||
return content.contains(
|
||||
"does not contain Javascript");
|
||||
}),
|
||||
anyString(),
|
||||
eq(MediaType.TEXT_PLAIN)));
|
||||
WebResponseUtils.fileToWebResponse(
|
||||
any(TempFile.class), anyString(), eq(MediaType.TEXT_PLAIN)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-7
@@ -2,8 +2,12 @@ package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
@@ -14,22 +18,38 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UnlockPDFFormsControllerTest {
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
private UnlockPDFFormsController controller;
|
||||
|
||||
private MockMultipartFile mockPdfFile;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
controller = new UnlockPDFFormsController(pdfDocumentFactory);
|
||||
void setUp() throws Exception {
|
||||
lenient()
|
||||
.when(tempFileManager.createManagedTempFile(anyString()))
|
||||
.thenAnswer(
|
||||
inv -> {
|
||||
File f =
|
||||
Files.createTempFile("test", inv.<String>getArgument(0))
|
||||
.toFile();
|
||||
TempFile tf = mock(TempFile.class);
|
||||
lenient().when(tf.getFile()).thenReturn(f);
|
||||
lenient().when(tf.getPath()).thenReturn(f.toPath());
|
||||
return tf;
|
||||
});
|
||||
controller = new UnlockPDFFormsController(pdfDocumentFactory, tempFileManager);
|
||||
mockPdfFile =
|
||||
new MockMultipartFile(
|
||||
"fileInput",
|
||||
@@ -47,7 +67,7 @@ class UnlockPDFFormsControllerTest {
|
||||
PDFFile file = new PDFFile();
|
||||
file.setFileInput(mockPdfFile);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.unlockPDFForms(file);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -65,7 +85,7 @@ class UnlockPDFFormsControllerTest {
|
||||
PDFFile file = new PDFFile();
|
||||
file.setFileInput(mockPdfFile);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.unlockPDFForms(file);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
@@ -79,7 +99,7 @@ class UnlockPDFFormsControllerTest {
|
||||
PDFFile file = new PDFFile();
|
||||
file.setFileInput(mockPdfFile);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.unlockPDFForms(file);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file);
|
||||
|
||||
// Controller catches exceptions and returns null
|
||||
assertNull(response);
|
||||
@@ -94,7 +114,7 @@ class UnlockPDFFormsControllerTest {
|
||||
PDFFile file = new PDFFile();
|
||||
file.setFileInput(mockPdfFile);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.unlockPDFForms(file);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file);
|
||||
|
||||
assertNotNull(response);
|
||||
String contentDisposition = response.getHeaders().getFirst("Content-Disposition");
|
||||
@@ -113,7 +133,7 @@ class UnlockPDFFormsControllerTest {
|
||||
PDFFile file = new PDFFile();
|
||||
file.setFileInput(mockPdfFile);
|
||||
|
||||
ResponseEntity<byte[]> response = controller.unlockPDFForms(file);
|
||||
ResponseEntity<StreamingResponseBody> response = controller.unlockPDFForms(file);
|
||||
|
||||
assertNotNull(response);
|
||||
assertTrue(acroForm.getNeedAppearances());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user