mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Cleanup work + stream endpoints to reduce memory usage (#6106)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user