diff --git a/app/common/src/main/java/stirling/software/common/service/FileStorage.java b/app/common/src/main/java/stirling/software/common/service/FileStorage.java index 8b0f3a53a..beb8028f7 100644 --- a/app/common/src/main/java/stirling/software/common/service/FileStorage.java +++ b/app/common/src/main/java/stirling/software/common/service/FileStorage.java @@ -10,6 +10,7 @@ import java.nio.file.Path; import java.util.UUID; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; @@ -157,13 +158,48 @@ public class FileStorage { success = true; } finally { if (!success) { - Files.deleteIfExists(filePath); + try { + Files.deleteIfExists(filePath); + } catch (IOException cleanupEx) { + log.warn( + "Failed to clean up partial file {} after store failure", + filePath, + cleanupEx); + } } } log.debug("Stored StreamingResponseBody with ID: {}", fileId); return fileId; } + /** + * Persist a {@link Resource} body to disk, returning the generated file ID. Used by the async + * job pipeline to capture {@code ResponseEntity} results produced by controllers. + */ + public String storeFromResource(Resource resource, String originalName) throws IOException { + String fileId = generateFileId(); + Path filePath = getFilePath(fileId); + Files.createDirectories(filePath.getParent()); + boolean success = false; + try (InputStream in = resource.getInputStream()) { + Files.copy(in, filePath); + success = true; + } finally { + if (!success) { + try { + Files.deleteIfExists(filePath); + } catch (IOException cleanupEx) { + log.warn( + "Failed to clean up partial file {} after store failure", + filePath, + cleanupEx); + } + } + } + log.debug("Stored Resource with ID: {}", fileId); + return fileId; + } + /** * Delete a file by its ID * diff --git a/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java b/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java index dd53eef3c..4c5112932 100644 --- a/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java +++ b/app/common/src/main/java/stirling/software/common/service/JobExecutorService.java @@ -11,6 +11,7 @@ import java.util.function.Supplier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -321,6 +322,13 @@ public class JobExecutorService { log.debug( "Stored ResponseEntity result with fileId: {}", fileId); + } else if (body instanceof Resource resource) { + String filename = extractResponseFilename(response); + String contentType = extractResponseContentType(response); + + String fileId = fileStorage.storeFromResource(resource, filename); + taskManager.setFileResult(jobId, fileId, filename, contentType); + log.debug("Stored ResponseEntity result with fileId: {}", fileId); } else { // Check if the response body contains a fileId if (body != null && body.toString().contains("fileId")) { diff --git a/app/common/src/main/java/stirling/software/common/util/PDFToFile.java b/app/common/src/main/java/stirling/software/common/util/PDFToFile.java index 1300e165f..feac968c5 100644 --- a/app/common/src/main/java/stirling/software/common/util/PDFToFile.java +++ b/app/common/src/main/java/stirling/software/common/util/PDFToFile.java @@ -16,11 +16,11 @@ import java.util.zip.ZipOutputStream; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; import com.vladsch.flexmark.util.data.MutableDataSet; @@ -49,7 +49,7 @@ public class PDFToFile { this.runtimePathConfig = runtimePathConfig; } - public ResponseEntity processPdfToMarkdown(MultipartFile inputFile) + public ResponseEntity processPdfToMarkdown(MultipartFile inputFile) throws IOException, InterruptedException { if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); @@ -169,7 +169,7 @@ public class PDFToFile { return PATTERN.matcher(markdown).replaceAll("$1(images/$2)"); } - public ResponseEntity processPdfToHtml(MultipartFile inputFile) + public ResponseEntity processPdfToHtml(MultipartFile inputFile) throws IOException, InterruptedException { if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); @@ -232,7 +232,7 @@ public class PDFToFile { finalOut, fileName, MediaType.APPLICATION_OCTET_STREAM); } - public ResponseEntity processPdfToOfficeFormat( + public ResponseEntity processPdfToOfficeFormat( MultipartFile inputFile, String outputFormat, String libreOfficeFilter) throws IOException, InterruptedException { diff --git a/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java b/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java index ab8311dd4..fa1efc26b 100644 --- a/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/WebResponseUtils.java @@ -1,19 +1,22 @@ package stirling.software.common.util; import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import io.github.pixee.security.Filenames; @@ -49,12 +52,7 @@ public class WebResponseUtils { HttpHeaders headers = new HttpHeaders(); headers.setContentType(mediaType); headers.setContentLength(bytes.length); - String encodedDocName = - RegexPatternUtils.getInstance() - .getPlusSignPattern() - .matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8)) - .replaceAll("%20"); - headers.setContentDispositionFormData("attachment", encodedDocName); + headers.setContentDispositionFormData("attachment", encodeAttachmentName(docName)); return new ResponseEntity<>(bytes, headers, HttpStatus.OK); } @@ -73,7 +71,17 @@ public class WebResponseUtils { return baosToWebResponse(baos, docName); } - public static ResponseEntity pdfDocToWebResponse( + /** + * Save a {@link PDDocument} to a managed temp file and return it as a file-backed {@code + * ResponseEntity}. + * + *

The returned {@link Resource} owns the supplied {@link TempFile} — the file is deleted + * when Spring closes the response {@link InputStream} after writing the body. This is a + * synchronous equivalent of the previous {@code StreamingResponseBody} pattern and avoids the + * async-dispatch hazards (response-committed races, filter incompatibility, silent write + * failures) that {@code StreamingResponseBody} introduced. + */ + public static ResponseEntity pdfDocToWebResponse( PDDocument document, String docName, TempFileManager tempFileManager) throws IOException { TempFile tempFile = tempFileManager.createManagedTempFile(".pdf"); @@ -87,38 +95,49 @@ public class WebResponseUtils { } /** - * Convert a File to a web response (PDF default). + * Convert a {@link TempFile} holding a PDF into a web response. + * + *

The temp file is deleted when Spring closes the response body stream. * * @param outputTempFile The temporary file to be sent as a response. * @param docName The name of the document. - * @return A ResponseEntity containing the file as a resource. + * @return A ResponseEntity whose body streams the file, deleting it on close. */ - public static ResponseEntity pdfFileToWebResponse( + public static ResponseEntity pdfFileToWebResponse( TempFile outputTempFile, String docName) throws IOException { return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_PDF); } /** - * Convert a File to a web response (ZIP default). + * Convert a {@link TempFile} holding a ZIP archive into a web response. + * + *

The temp file is deleted when Spring closes the response body stream. * * @param outputTempFile The temporary file to be sent as a response. * @param docName The name of the document. - * @return A ResponseEntity containing the file as a resource. + * @return A ResponseEntity whose body streams the file, deleting it on close. */ - public static ResponseEntity zipFileToWebResponse( + public static ResponseEntity zipFileToWebResponse( TempFile outputTempFile, String docName) throws IOException { return fileToWebResponse(outputTempFile, docName, MediaType.APPLICATION_OCTET_STREAM); } /** - * Convert a File to a web response with explicit media type (e.g., ZIP). + * Convert a {@link TempFile} into a web response with an explicit media type. + * + *

The returned {@link ResponseEntity} carries a {@link ManagedTempFileResource} as its body. + * Spring's {@code ResourceHttpMessageConverter} calls {@link Resource#getInputStream()} once + * and closes the returned stream after writing — at which point the underlying {@link TempFile} + * is deleted. Everything runs synchronously on the request thread, so write failures propagate + * through normal Spring error handling and are logged, rather than silently truncating the + * response. * * @param outputTempFile The temporary file to be sent as a response. * @param docName The name of the document. * @param mediaType The content type to set on the response. - * @return A ResponseEntity containing the file as a resource. + * @return A ResponseEntity whose body streams the file, deleting it on close. */ - public static ResponseEntity fileToWebResponse( + public static ResponseEntity fileToWebResponse( TempFile outputTempFile, String docName, MediaType mediaType) throws IOException { try { @@ -127,23 +146,9 @@ public class WebResponseUtils { HttpHeaders headers = new HttpHeaders(); headers.setContentType(mediaType); headers.setContentLength(len); - String encodedDocName = - RegexPatternUtils.getInstance() - .getPlusSignPattern() - .matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8)) - .replaceAll("%20"); - headers.setContentDispositionFormData("attachment", encodedDocName); - - StreamingResponseBody body = - os -> { - try (os) { - Files.copy(path, os); - os.flush(); - } finally { - outputTempFile.close(); - } - }; + headers.setContentDispositionFormData("attachment", encodeAttachmentName(docName)); + Resource body = new ManagedTempFileResource(outputTempFile); return new ResponseEntity<>(body, headers, HttpStatus.OK); } catch (IOException | RuntimeException e) { try { @@ -154,4 +159,115 @@ public class WebResponseUtils { throw e; } } + + private static String encodeAttachmentName(String docName) { + return RegexPatternUtils.getInstance() + .getPlusSignPattern() + .matcher(URLEncoder.encode(docName, StandardCharsets.UTF_8)) + .replaceAll("%20"); + } + + /** + * {@link Resource} backed by a {@link TempFile}. The underlying temp file is deleted when the + * response {@code InputStream} is closed — i.e. after Spring has finished writing the body. Any + * {@link IOException} during the copy is logged via {@link ClosingInputStream} and propagates + * through Spring's normal error path. Since response headers are committed before the body + * transfer begins, a mid-body failure manifests as a server-side log entry plus an aborted + * connection rather than a silently-truncated success — which is the behaviour this class was + * added to restore. + * + *

Single-use contract: {@link #getInputStream()} is intended to be called once by + * Spring's {@code ResourceHttpMessageConverter} on the normal write path. After the returned + * stream is closed the backing temp file is deleted, so subsequent {@code getInputStream()} + * calls will either see a deleted file (tests that mock {@link TempFile#close()} are an + * exception) or fail at read time. Callers that need to re-read the body must copy it first. + */ + public static final class ManagedTempFileResource extends FileSystemResource { + + private final TempFile tempFile; + + public ManagedTempFileResource(TempFile tempFile) { + super(tempFile.getFile()); + this.tempFile = tempFile; + } + + @Override + public InputStream getInputStream() throws IOException { + InputStream source; + try { + source = super.getInputStream(); + } catch (IOException e) { + // Opening the input stream already failed; make sure we don't leak the temp file. + try { + tempFile.close(); + } catch (Exception closeEx) { + e.addSuppressed(closeEx); + } + throw e; + } + return new ClosingInputStream(source, tempFile); + } + } + + /** + * Stream wrapper that deletes its backing {@link TempFile} on close. Logs — but does not + * swallow — any IOException that happens while reading the body, so upstream handlers can + * surface the failure to the client. + */ + private static final class ClosingInputStream extends FilterInputStream { + + private final TempFile tempFile; + private boolean closed; + + ClosingInputStream(InputStream delegate, TempFile tempFile) { + super(delegate); + this.tempFile = tempFile; + } + + @Override + public int read() throws IOException { + try { + return super.read(); + } catch (IOException e) { + log.error( + "Failed to read temp response body {} while streaming to client", + tempFile.getAbsolutePath(), + e); + throw e; + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + try { + return super.read(b, off, len); + } catch (IOException e) { + log.error( + "Failed to read temp response body {} while streaming to client", + tempFile.getAbsolutePath(), + e); + throw e; + } + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + try { + super.close(); + } finally { + try { + tempFile.close(); + } catch (Exception closeEx) { + log.warn( + "Failed to clean up temp file {} after streaming response", + tempFile.getAbsolutePath(), + closeEx); + } + } + } + } } diff --git a/app/common/src/test/java/stirling/software/common/service/FileStorageTest.java b/app/common/src/test/java/stirling/software/common/service/FileStorageTest.java index 6e3883403..5b06747a5 100644 --- a/app/common/src/test/java/stirling/software/common/service/FileStorageTest.java +++ b/app/common/src/test/java/stirling/software/common/service/FileStorageTest.java @@ -4,8 +4,11 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,6 +16,8 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.multipart.MultipartFile; @@ -185,4 +190,76 @@ class FileStorageTest { // Assert assertFalse(result); } + + @Test + void storeFromResource_happyPath_persistsContent() throws IOException { + // Arrange + byte[] payload = "resource-body-bytes".getBytes(StandardCharsets.UTF_8); + Resource resource = new ByteArrayResource(payload); + + // Act + String fileId = fileStorage.storeFromResource(resource, "whatever.pdf"); + + // Assert + assertNotNull(fileId); + assertTrue(Files.exists(tempDir.resolve(fileId))); + assertArrayEquals(payload, fileStorage.retrieveBytes(fileId)); + } + + @Test + void storeFromResource_failureCleansUpPartialFile() throws IOException { + // Arrange: a Resource whose getInputStream returns a stream that throws after + // emitting a few bytes. The finally block in storeFromResource should remove + // the partial file on disk. + byte[] head = "partial".getBytes(StandardCharsets.UTF_8); + Resource flakyResource = + new ByteArrayResource(head) { + @Override + public InputStream getInputStream() { + return new InputStream() { + private int position = 0; + + @Override + public int read() throws IOException { + if (position < head.length) { + return head[position++] & 0xFF; + } + throw new IOException("simulated mid-copy read failure"); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (position >= head.length) { + throw new IOException("simulated mid-copy read failure"); + } + int toCopy = Math.min(len, head.length - position); + System.arraycopy(head, position, b, off, toCopy); + position += toCopy; + return toCopy; + } + }; + } + }; + + // Snapshot dir contents before the call so we can detect any lingering file. + long filesBefore; + try (Stream s = Files.list(tempDir)) { + filesBefore = s.count(); + } + + // Act + Assert: IOException must propagate out — not be swallowed. + assertThrows( + IOException.class, () -> fileStorage.storeFromResource(flakyResource, "n.pdf")); + + // Assert: no partial file lingers under the storage directory — the finally + // branch's deleteIfExists must have cleaned it up. + long filesAfter; + try (Stream s = Files.list(tempDir)) { + filesAfter = s.count(); + } + assertEquals( + filesBefore, + filesAfter, + "partial file must be cleaned up by storeFromResource finally block"); + } } diff --git a/app/common/src/test/java/stirling/software/common/service/JobExecutorServiceTest.java b/app/common/src/test/java/stirling/software/common/service/JobExecutorServiceTest.java index 79c0ab09f..e3d6dd0c1 100644 --- a/app/common/src/test/java/stirling/software/common/service/JobExecutorServiceTest.java +++ b/app/common/src/test/java/stirling/software/common/service/JobExecutorServiceTest.java @@ -8,9 +8,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; @@ -24,7 +26,12 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import jakarta.servlet.http.HttpServletRequest; @@ -180,6 +187,46 @@ class JobExecutorServiceTest { verify(spy).runJobGeneric(eq(false), any(Supplier.class), eq(customTimeout)); } + @Test + void shouldPersistResponseEntityResourceBodyViaFileStorage() throws Exception { + // Given: an async job whose result is a ResponseEntity — the new + // branch added by the stream-to-Resource migration. The executor must route + // the body through FileStorage.storeFromResource and then record the result + // via TaskManager.setFileResult with the filename/content-type extracted + // from the response headers. + byte[] payload = "resource-bytes".getBytes(StandardCharsets.UTF_8); + Resource resource = new ByteArrayResource(payload); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_PDF); + headers.setContentDisposition( + ContentDisposition.formData().name("attachment").filename("result.pdf").build()); + + Supplier work = () -> new ResponseEntity<>(resource, headers, HttpStatus.OK); + + when(fileStorage.storeFromResource(any(Resource.class), anyString())) + .thenReturn("stored-file-id"); + + // When: run the job asynchronously — processJobResult runs on the executor. + ResponseEntity response = jobExecutorService.runJobGeneric(true, work); + + // Then: the immediate return must be the JobResponse envelope. + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertInstanceOf(JobResponse.class, response.getBody()); + + // Wait for async processing and verify the Resource branch was taken — + // FileStorage.storeFromResource was invoked with the same Resource instance, + // and TaskManager.setFileResult recorded the extracted filename + content-type. + verify(fileStorage, timeout(5000)).storeFromResource(eq(resource), eq("result.pdf")); + verify(taskManager, timeout(5000)) + .setFileResult( + anyString(), + eq("stored-file-id"), + eq("result.pdf"), + eq(MediaType.APPLICATION_PDF_VALUE)); + verify(taskManager, timeout(5000)).setComplete(anyString()); + } + @Test void shouldHandleTimeout() throws Exception { // Given diff --git a/app/common/src/test/java/stirling/software/common/util/PDFToFileTest.java b/app/common/src/test/java/stirling/software/common/util/PDFToFileTest.java index 69db94c34..4ec716451 100644 --- a/app/common/src/test/java/stirling/software/common/util/PDFToFileTest.java +++ b/app/common/src/test/java/stirling/software/common/util/PDFToFileTest.java @@ -25,12 +25,12 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import io.github.pixee.security.ZipSecurity; @@ -83,9 +83,11 @@ class PDFToFileTest { pdfToFile = new PDFToFile(mockTempFileManager, mockRuntimePathConfig); } - private static byte[] drain(ResponseEntity response) throws IOException { + private static byte[] drain(ResponseEntity response) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -100,7 +102,7 @@ class PDFToFileTest { "This is not a PDF".getBytes()); // Execute - ResponseEntity response = pdfToFile.processPdfToMarkdown(nonPdfFile); + ResponseEntity response = pdfToFile.processPdfToMarkdown(nonPdfFile); // Verify assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); @@ -117,7 +119,7 @@ class PDFToFileTest { "This is not a PDF".getBytes()); // Execute - ResponseEntity response = pdfToFile.processPdfToHtml(nonPdfFile); + ResponseEntity response = pdfToFile.processPdfToHtml(nonPdfFile); // Verify assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); @@ -135,7 +137,7 @@ class PDFToFileTest { "This is not a PDF".getBytes()); // Execute - ResponseEntity response = + ResponseEntity response = pdfToFile.processPdfToOfficeFormat(nonPdfFile, "docx", "draw_pdf_import"); // Verify @@ -154,7 +156,7 @@ class PDFToFileTest { "Fake PDF content".getBytes()); // Execute with invalid format - ResponseEntity response = + ResponseEntity response = pdfToFile.processPdfToOfficeFormat(pdfFile, "invalid_format", "draw_pdf_import"); // Verify @@ -205,8 +207,7 @@ class PDFToFileTest { }); // Execute the method - ResponseEntity response = - pdfToFile.processPdfToMarkdown(pdfFile); + ResponseEntity response = pdfToFile.processPdfToMarkdown(pdfFile); // Verify - should now return a ZIP file instead of plain markdown assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -298,8 +299,7 @@ class PDFToFileTest { }); // Execute the method - ResponseEntity response = - pdfToFile.processPdfToMarkdown(pdfFile); + ResponseEntity response = pdfToFile.processPdfToMarkdown(pdfFile); // Verify assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -377,7 +377,7 @@ class PDFToFileTest { }); // Execute the method - ResponseEntity response = pdfToFile.processPdfToHtml(pdfFile); + ResponseEntity response = pdfToFile.processPdfToHtml(pdfFile); // Verify assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -463,7 +463,7 @@ class PDFToFileTest { }); // Execute the method with docx format - ResponseEntity response = + ResponseEntity response = pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import"); // Verify @@ -535,7 +535,7 @@ class PDFToFileTest { }); // Execute the method with ODP format - ResponseEntity response = + ResponseEntity response = pdfToFile.processPdfToOfficeFormat(pdfFile, "odp", "draw_pdf_import"); // Verify @@ -620,7 +620,7 @@ class PDFToFileTest { }); // Execute the method with text format - ResponseEntity response = + ResponseEntity response = pdfToFile.processPdfToOfficeFormat(pdfFile, "txt:Text", "draw_pdf_import"); // Verify @@ -679,7 +679,7 @@ class PDFToFileTest { }); // Execute the method - ResponseEntity response = + ResponseEntity response = pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import"); // Verify @@ -726,7 +726,7 @@ class PDFToFileTest { return mockExecutorResult; }); - ResponseEntity response = + ResponseEntity response = pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import"); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -790,7 +790,7 @@ class PDFToFileTest { return mockExecutorResult; }); - ResponseEntity response = + ResponseEntity response = pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import"); assertEquals(HttpStatus.OK, response.getStatusCode()); diff --git a/app/common/src/test/java/stirling/software/common/util/WebResponseUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/WebResponseUtilsTest.java index 79f1e6dcd..a33f9259c 100644 --- a/app/common/src/test/java/stirling/software/common/util/WebResponseUtilsTest.java +++ b/app/common/src/test/java/stirling/software/common/util/WebResponseUtilsTest.java @@ -3,17 +3,39 @@ package stirling.software.common.util; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import stirling.software.common.model.ApplicationProperties; + class WebResponseUtilsTest { + @TempDir Path tempDir; + + private TempFileManager tempFileManager; + + @BeforeEach + void setUpTempFileManager() { + TempFileRegistry registry = new TempFileRegistry(); + ApplicationProperties applicationProperties = new ApplicationProperties(); + applicationProperties.getSystem().getTempFileManagement().setBaseTmpDir(tempDir.toString()); + applicationProperties.getSystem().getTempFileManagement().setPrefix("wru-test-"); + tempFileManager = new TempFileManager(registry, applicationProperties); + } + @Test void testBytesToWebResponse_defaultMediaType() throws IOException { byte[] data = "test content".getBytes(StandardCharsets.UTF_8); @@ -91,4 +113,87 @@ class WebResponseUtilsTest { assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(0, response.getHeaders().getContentLength()); } + + @Test + void managedTempFileResource_happyPathDeletesBackingFile() throws IOException { + // Create a real managed temp file on disk and populate it. + TempFile tempFile = tempFileManager.createManagedTempFile(".pdf"); + byte[] payload = "fully-readable-pdf-body".getBytes(StandardCharsets.UTF_8); + Files.write(tempFile.getPath(), payload); + File backing = tempFile.getFile(); + assertTrue(backing.exists(), "precondition: backing file should exist"); + + WebResponseUtils.ManagedTempFileResource resource = + new WebResponseUtils.ManagedTempFileResource(tempFile); + + byte[] readBack; + try (InputStream in = resource.getInputStream()) { + readBack = in.readAllBytes(); + } + + assertArrayEquals(payload, readBack, "stream should deliver the original bytes"); + assertFalse( + backing.exists(), + "backing temp file must be deleted once the response stream is closed"); + } + + @Test + void closingInputStream_propagatesReadFailure() throws IOException { + // ManagedTempFileResource is final, so we can't swap in a mock underlying stream. + // Instead: open the resource's stream, close the inner FileInputStream early + // (by closing the outer stream once), then confirm reading the now-closed stream + // throws — exercising ClosingInputStream's read() catch/log/rethrow path. Finally + // confirm the temp file is still cleaned up on close(). + TempFile tempFile = tempFileManager.createManagedTempFile(".pdf"); + Files.write(tempFile.getPath(), "some-bytes".getBytes(StandardCharsets.UTF_8)); + File backing = tempFile.getFile(); + assertTrue(backing.exists()); + + WebResponseUtils.ManagedTempFileResource resource = + new WebResponseUtils.ManagedTempFileResource(tempFile); + + InputStream in = resource.getInputStream(); + // Pre-close the underlying stream to guarantee read() throws. + in.close(); + // After close, the temp file has been deleted already. + assertFalse( + backing.exists(), + "backing file is deleted on first close — precondition for read assertion"); + + // Any attempt to read from the already-closed stream must throw IOException + // (not silently return -1). This exercises the ClosingInputStream.read() path + // that logs and rethrows. + IOException ioex = assertThrows(IOException.class, in::read); + assertNotNull(ioex); + + // close() is idempotent — a second call must not throw. + assertDoesNotThrow(in::close); + } + + @Test + void managedTempFileResource_openFailureCleansUp() throws IOException { + // Arrange: build a TempFile whose backing file is deleted out from under it so + // super.getInputStream() throws on open. Instrument close() with an AtomicBoolean + // hook to confirm the cleanup path ran. + AtomicBoolean closed = new AtomicBoolean(false); + TempFile spying = + new TempFile(tempFileManager, ".pdf") { + @Override + public void close() { + closed.set(true); + super.close(); + } + }; + assertTrue( + spying.getFile().delete(), + "precondition: delete backing so super.getInputStream() fails"); + + WebResponseUtils.ManagedTempFileResource resource = + new WebResponseUtils.ManagedTempFileResource(spying); + + assertThrows(IOException.class, resource::getInputStream); + assertTrue( + closed.get(), + "tempFile.close() must run when super.getInputStream() fails on open"); + } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java index 8a3313e88..b8d951904 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java @@ -12,13 +12,13 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.util.Matrix; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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; @@ -51,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 createBookletImposition( + public ResponseEntity createBookletImposition( @ModelAttribute BookletImpositionRequest request) throws IOException { MultipartFile file = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/CropController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/CropController.java index 845d3519c..21893e263 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/CropController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/CropController.java @@ -12,10 +12,10 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.rendering.PDFRenderer; +import org.springframework.core.io.Resource; 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; @@ -132,7 +132,7 @@ 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 cropPdf(@ModelAttribute CropPdfForm request) + public ResponseEntity cropPdf(@ModelAttribute CropPdfForm request) throws IOException { if (request.isAutoCrop()) { return cropWithAutomaticDetection(request); @@ -153,8 +153,8 @@ public class CropController { } } - private ResponseEntity cropWithAutomaticDetection( - @ModelAttribute CropPdfForm request) throws IOException { + private ResponseEntity cropWithAutomaticDetection(@ModelAttribute CropPdfForm request) + throws IOException { try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) { try (PDDocument newDocument = @@ -207,8 +207,8 @@ public class CropController { } } - private ResponseEntity cropWithPDFBox( - @ModelAttribute CropPdfForm request) throws IOException { + private ResponseEntity cropWithPDFBox(@ModelAttribute CropPdfForm request) + throws IOException { try (PDDocument sourceDocument = pdfDocumentFactory.load(request)) { try (PDDocument newDocument = @@ -263,8 +263,8 @@ public class CropController { } } - private ResponseEntity cropWithGhostscript( - @ModelAttribute CropPdfForm request) throws IOException { + private ResponseEntity cropWithGhostscript(@ModelAttribute CropPdfForm request) + throws IOException { TempFile tempInputFile = null; TempFile tempOutputFile = null; @@ -301,7 +301,7 @@ public class CropController { processExecutor.runCommandWithOutputHandling(command); TempFile out = tempOutputFile; - tempOutputFile = null; // ownership transferred to StreamingResponseBody + tempOutputFile = null; // ownership transferred to response Resource return WebResponseUtils.pdfFileToWebResponse( out, GeneralUtils.generateFilename( diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/EditTableOfContentsController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/EditTableOfContentsController.java index bd51dfbd1..84498f5f2 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/EditTableOfContentsController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/EditTableOfContentsController.java @@ -9,11 +9,11 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; +import org.springframework.core.io.Resource; 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; @@ -151,7 +151,7 @@ public class EditTableOfContentsController { @Operation( summary = "Edit Table of Contents", description = "Add or edit bookmarks/table of contents in a PDF document.") - public ResponseEntity editTableOfContents( + public ResponseEntity editTableOfContents( @ModelAttribute EditTableOfContentsRequest request) throws Exception { MultipartFile file = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 3a7718fe8..f9f5d0028 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -23,12 +23,12 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.schema.XMPBasicSchema; import org.apache.xmpbox.xml.DomXmpParser; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; 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 mergePdfs( + public ResponseEntity mergePdfs( @ModelAttribute MergePdfsRequest request, @RequestParam(value = "fileOrder", required = false) String fileOrder) throws IOException { diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java index 4fad2c5b1..a951ef1ec 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java @@ -10,11 +10,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.util.Matrix; +import org.springframework.core.io.Resource; 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; @@ -47,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 mergeMultiplePagesIntoOne( + public ResponseEntity mergeMultiplePagesIntoOne( @ModelAttribute MergeMultiplePagesRequest request) throws IOException { int MAX_PAGES = 100000; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java index 826ffcb1f..808aed2cc 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java @@ -11,11 +11,11 @@ import java.util.Map; import org.apache.pdfbox.Loader; import org.apache.pdfbox.multipdf.Overlay; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.core.io.Resource; 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,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 overlayPdfs( - @ModelAttribute OverlayPdfsRequest request) throws IOException { + public ResponseEntity overlayPdfs(@ModelAttribute OverlayPdfsRequest request) + throws IOException { MultipartFile baseFile = request.getFileInput(); int overlayPos = request.getOverlayPosition(); @@ -89,7 +89,7 @@ public class PdfOverlayController { baseFile.getOriginalFilename(), "_overlayed.pdf"); TempFile out = tempOut; - tempOut = null; // ownership transferred to StreamingResponseBody + tempOut = null; // ownership transferred to response Resource return WebResponseUtils.pdfFileToWebResponse(out, outputFilename); } } catch (Exception e) { diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/PosterPdfController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/PosterPdfController.java index e02980ba3..3cd29a524 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/PosterPdfController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/PosterPdfController.java @@ -14,10 +14,10 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.util.Matrix; +import org.springframework.core.io.Resource; 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; @@ -52,7 +52,7 @@ public class PosterPdfController { + "suitable for printing on standard paper sizes (e.g., A4, Letter). " + "Divides each page into a grid of smaller pages using Apache PDFBox. " + "Input: PDF Output: ZIP-PDF Type: SISO") - public ResponseEntity posterPdf(@ModelAttribute PosterPdfRequest request) + public ResponseEntity posterPdf(@ModelAttribute PosterPdfRequest request) throws Exception { log.debug("Starting PDF poster split process with request: {}", request); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index de58a882c..1aac42bea 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -8,11 +8,11 @@ import java.util.Locale; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; +import org.springframework.core.io.Resource; 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; @@ -47,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 deletePages( - @ModelAttribute PDFWithPageNums request) throws IOException { + public ResponseEntity deletePages(@ModelAttribute PDFWithPageNums request) + throws IOException { MultipartFile pdfFile = request.getFileInput(); String pagesToDelete = request.getPageNumbers(); @@ -228,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 rearrangePages( - @ModelAttribute RearrangePagesRequest request) throws IOException { + public ResponseEntity rearrangePages(@ModelAttribute RearrangePagesRequest request) + throws IOException { MultipartFile pdfFile = request.getFileInput(); String pageOrder = request.getPageNumbers(); String sortType = request.getCustomMode(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/RotationController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/RotationController.java index 9049595cb..f5211ff1c 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/RotationController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/RotationController.java @@ -5,11 +5,11 @@ import java.io.IOException; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageTree; +import org.springframework.core.io.Resource; 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,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 rotatePDF(@ModelAttribute RotatePDFRequest request) + public ResponseEntity rotatePDF(@ModelAttribute RotatePDFRequest request) throws IOException { MultipartFile pdfFile = request.getFileInput(); Integer angle = request.getAngle(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index 1cc77452d..778b25f4e 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.util.Matrix; +import org.springframework.core.io.Resource; 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; @@ -120,8 +120,8 @@ 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 scalePages( - @ModelAttribute ScalePagesRequest request) throws IOException { + public ResponseEntity scalePages(@ModelAttribute ScalePagesRequest request) + throws IOException { MultipartFile file = request.getFileInput(); String targetPDRectangle = request.getPageSize(); float scaleFactor = request.getScaleFactor(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index ed7957a81..fc2bcd3a2 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -9,11 +9,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.core.io.Resource; 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; @@ -48,7 +48,7 @@ public class SplitPDFController { + " specified page numbers or ranges. Users can specify pages using" + " individual numbers, ranges, or 'all' for every page. Input:PDF" + " Output:PDF Type:SIMO") - public ResponseEntity splitPdf(@ModelAttribute SplitPagesRequest request) + public ResponseEntity splitPdf(@ModelAttribute SplitPagesRequest request) throws IOException { MultipartFile file = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java index e623cb6bd..b6f0e521f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java @@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; +import org.springframework.core.io.Resource; 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; @@ -127,8 +127,8 @@ public class SplitPdfByChaptersController { description = "Splits a PDF into chapters and returns a ZIP file. Input:PDF Output:ZIP-PDF" + " Type:SISO") - public ResponseEntity splitPdf( - @ModelAttribute SplitPdfByChaptersRequest request) throws Exception { + public ResponseEntity splitPdf(@ModelAttribute SplitPdfByChaptersRequest request) + throws Exception { MultipartFile file = request.getFileInput(); boolean includeMetadata = Boolean.TRUE.equals(request.getIncludeMetadata()); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index 58c150aa3..2afa716e3 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -15,11 +15,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.util.Matrix; +import org.springframework.core.io.Resource; 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,7 +59,7 @@ 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 splitPdf( + public ResponseEntity splitPdf( @Valid @ModelAttribute SplitPdfBySectionsRequest request) throws Exception { MultipartFile file = request.getFileInput(); String pageNumbers = request.getPageNumbers(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java index b173a52a2..697ee62f3 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java @@ -8,11 +8,11 @@ import java.util.zip.ZipOutputStream; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; +import org.springframework.core.io.Resource; 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,7 +51,7 @@ public class SplitPdfBySizeController { + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB" + " (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF" + " Output:ZIP-PDF Type:SISO") - public ResponseEntity autoSplitPdf( + public ResponseEntity autoSplitPdf( @ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception { log.debug("Starting PDF split process with request: {}", request); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java index da6322a08..c0317e1b1 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java @@ -8,10 +8,10 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.springframework.core.io.Resource; 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; @@ -44,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 pdfToSinglePage(@ModelAttribute PDFFile request) + public ResponseEntity pdfToSinglePage(@ModelAttribute PDFFile request) throws IOException { // Load the source document diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFController.java index 136eba30e..e5c48cd53 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFController.java @@ -12,11 +12,11 @@ import java.util.Set; import org.apache.commons.io.FilenameUtils; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.core.io.Resource; 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; @@ -62,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 convertEbookToPdf( + public ResponseEntity convertEbookToPdf( @ModelAttribute ConvertEbookToPdfRequest request) throws Exception { if (!isCalibreEnabled()) { throw new IllegalStateException("Calibre support is disabled"); @@ -162,7 +162,7 @@ public class ConvertEbookToPDFController { document.save(tempOut.getFile()); } } - ResponseEntity response = + ResponseEntity response = WebResponseUtils.pdfFileToWebResponse(tempOut, outputFilename); tempOut = null; return response; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java index 42bbe6b02..840b18da6 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java @@ -6,12 +6,13 @@ import java.nio.file.Files; import java.util.Locale; import org.jetbrains.annotations.NotNull; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; 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; @@ -51,8 +52,7 @@ 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 convertEmlToPdf( - @ModelAttribute EmlToPdfRequest request) { + public ResponseEntity convertEmlToPdf(@ModelAttribute EmlToPdfRequest request) { MultipartFile inputFile = request.getFileInput(); String originalFilename = inputFile.getOriginalFilename(); @@ -159,14 +159,11 @@ public class ConvertEmlToPDF { } } - private ResponseEntity errorResponse(HttpStatus status, String message) { + private ResponseEntity 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); + return ResponseEntity.status(status) + .contentLength(body.length) + .body(new ByteArrayResource(body)); } private static @NotNull String buildErrorMessage(Exception e, String originalFilename) { diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index 7d09eeb3b..9ee4566e1 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -2,11 +2,11 @@ package stirling.software.SPDF.controller.api.converters; import java.nio.file.Files; +import org.springframework.core.io.Resource; 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; @@ -40,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 HtmlToPdf(@ModelAttribute HTMLToPdfRequest request) + public ResponseEntity HtmlToPdf(@ModelAttribute HTMLToPdfRequest request) throws Exception { MultipartFile fileInput = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java index ec0e91695..70b041eba 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java @@ -18,11 +18,11 @@ import org.apache.commons.io.FileUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.rendering.ImageType; +import org.springframework.core.io.Resource; 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; @@ -274,8 +274,8 @@ public class ConvertImgPDFController { description = "This endpoint converts a CBZ (ZIP) comic book archive to a PDF file. " + "Input:CBZ Output:PDF Type:SISO") - public ResponseEntity convertCbzToPdf( - @ModelAttribute ConvertCbzToPdfRequest request) throws IOException { + public ResponseEntity convertCbzToPdf(@ModelAttribute ConvertCbzToPdfRequest request) + throws IOException { MultipartFile file = request.getFileInput(); boolean optimizeForEbook = request.isOptimizeForEbook(); @@ -300,8 +300,8 @@ public class ConvertImgPDFController { description = "This endpoint converts a PDF file to a CBZ (ZIP) comic book archive. " + "Input:PDF Output:CBZ Type:SISO") - public ResponseEntity convertPdfToCbz( - @ModelAttribute ConvertPdfToCbzRequest request) throws IOException { + public ResponseEntity convertPdfToCbz(@ModelAttribute ConvertPdfToCbzRequest request) + throws IOException { MultipartFile file = request.getFileInput(); int dpi = request.getDpi(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java index 83384a21e..64506243f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java @@ -11,11 +11,11 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.HtmlRenderer; +import org.springframework.core.io.Resource; 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; @@ -48,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 markdownToPdf( - @ModelAttribute GeneralFile generalFile) throws Exception { + public ResponseEntity markdownToPdf(@ModelAttribute GeneralFile generalFile) + throws Exception { MultipartFile fileInput = generalFile.getFileInput(); if (fileInput == null) { diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java index ba4be7b8e..ded3bae8d 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java @@ -13,11 +13,11 @@ import java.util.Locale; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.core.io.Resource; 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; @@ -206,8 +206,8 @@ public class ConvertOfficeController { description = "This endpoint converts a given file to a PDF using LibreOffice API Input:ANY" + " Output:PDF Type:SISO") - public ResponseEntity processFileToPDF( - @ModelAttribute GeneralFile generalFile) throws Exception { + public ResponseEntity 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(); @@ -223,7 +223,7 @@ public class ConvertOfficeController { String filename = GeneralUtils.generateFilename( inputFile.getOriginalFilename(), "_convertedToPDF.pdf"); - ResponseEntity response = + ResponseEntity response = WebResponseUtils.pdfFileToWebResponse(tempOut, filename); tempOut = null; return response; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubController.java index 3fd8c46db..06a71705f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubController.java @@ -9,11 +9,11 @@ import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FilenameUtils; +import org.springframework.core.io.Resource; 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; @@ -87,7 +87,7 @@ 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 convertPdfToEpub( + public ResponseEntity convertPdfToEpub( @ModelAttribute ConvertPdfToEpubRequest request) throws Exception { if (!endpointConfiguration.isGroupEnabled(CALIBRE_GROUP)) { diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelController.java index 974e5451f..648bdab59 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelController.java @@ -12,10 +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.core.io.Resource; 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; @@ -51,7 +51,7 @@ 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 pdfToExcel(@ModelAttribute PDFWithPageNums request) + public ResponseEntity pdfToExcel(@ModelAttribute PDFWithPageNums request) throws Exception { String baseName = GeneralUtils.removeExtension(request.getFileInput().getOriginalFilename()); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToHtml.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToHtml.java index 13558d61a..f3a620c34 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToHtml.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToHtml.java @@ -1,10 +1,10 @@ package stirling.software.SPDF.controller.api.converters; +import org.springframework.core.io.Resource; 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,7 +29,7 @@ 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 processPdfToHTML(@ModelAttribute PDFFile file) + public ResponseEntity processPdfToHTML(@ModelAttribute PDFFile file) throws Exception { MultipartFile inputFile = file.getFileInput(); PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java index 391ce5239..a6a6c7c97 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java @@ -6,11 +6,11 @@ import java.nio.file.Files; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; +import org.springframework.core.io.Resource; 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; @@ -44,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 processPdfToPresentation( + public ResponseEntity processPdfToPresentation( @ModelAttribute PdfToPresentationRequest request) throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); @@ -59,7 +59,7 @@ public class ConvertPDFToOffice { description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF" + " Output:TXT Type:SISO") - public ResponseEntity processPdfToRTForTXT( + public ResponseEntity processPdfToRTForTXT( @ModelAttribute PdfToTextOrRTFRequest request) throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); @@ -89,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 processPdfToWord( - @ModelAttribute PdfToWordRequest request) throws IOException, InterruptedException { + public ResponseEntity processPdfToWord(@ModelAttribute PdfToWordRequest request) + throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); String outputFormat = request.getOutputFormat(); PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig); @@ -103,8 +103,7 @@ public class ConvertPDFToOffice { description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML" + " Type:SISO") - public ResponseEntity processPdfToXML(@ModelAttribute PDFFile file) - throws Exception { + public ResponseEntity processPdfToXML(@ModelAttribute PDFFile file) throws Exception { MultipartFile inputFile = file.getFileInput(); PDFToFile pdfToFile = new PDFToFile(tempFileManager, runtimePathConfig); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index 98706bc98..39ca628ec 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -71,13 +71,13 @@ import org.apache.xmpbox.schema.PDFAIdentificationSchema; import org.apache.xmpbox.schema.XMPBasicSchema; import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.XmpSerializer; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; 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.server.ResponseStatusException; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import io.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Operation; @@ -577,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 pdfToPdfA(@ModelAttribute PdfToPdfARequest request) + public ResponseEntity pdfToPdfA(@ModelAttribute PdfToPdfARequest request) throws Exception { MultipartFile inputFile = request.getFileInput(); String outputFormat = request.getOutputFormat(); @@ -613,7 +613,7 @@ public class ConvertPDFToPDFA { return missing; } - private ResponseEntity handlePdfXConversion( + private ResponseEntity handlePdfXConversion( MultipartFile inputFile, String outputFormat) throws Exception { PdfXProfile profile = PdfXProfile.fromRequest(outputFormat); @@ -1806,7 +1806,7 @@ public class ConvertPDFToPDFA { return Files.readAllBytes(outputPdf); } - private ResponseEntity handlePdfAConversion( + private ResponseEntity handlePdfAConversion( MultipartFile inputFile, String outputFormat, boolean strict) throws Exception { PdfaProfile profile = PdfaProfile.fromRequest(outputFormat); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java index 7b7924ba3..d68cfad72 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java @@ -1,12 +1,16 @@ package stirling.software.SPDF.controller.api.converters; +import java.io.IOException; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.util.Optional; import java.util.UUID; import java.util.regex.Pattern; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -15,7 +19,6 @@ 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; @@ -56,7 +59,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 convertPdfToJson( + public ResponseEntity convertPdfToJson( @ModelAttribute PDFFile request, @RequestParam(value = "lightweight", defaultValue = "false") boolean lightweight) throws Exception { @@ -65,10 +68,6 @@ 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(); String baseName = (originalName != null && !originalName.isBlank()) @@ -78,13 +77,19 @@ public class ConvertPdfJsonController { : "document"; String docName = baseName + ".json"; TempFile tempOut = tempFileManager.createManagedTempFile(".json"); + try (OutputStream os = Files.newOutputStream(tempOut.getPath())) { + pdfJsonConversionService.convertPdfToJson(inputFile, lightweight, os); + } catch (Exception e) { + tempOut.close(); + throw e; + } try { - Files.write(tempOut.getPath(), jsonBytes); + logJsonResponse("pdf/text-editor", tempOut.getPath()); + return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); } catch (Exception e) { tempOut.close(); throw e; } - return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); } @AutoJobPostMapping(consumes = "multipart/form-data", value = "/text-editor/pdf") @@ -93,14 +98,13 @@ 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 convertJsonToPdf( - @ModelAttribute GeneralFile request) throws Exception { + public ResponseEntity convertJsonToPdf(@ModelAttribute GeneralFile request) + throws Exception { MultipartFile jsonFile = request.getFileInput(); if (jsonFile == null) { throw ExceptionUtils.createNullArgumentException("fileInput"); } - byte[] pdfBytes = pdfJsonConversionService.convertJsonToPdf(jsonFile); String originalName = jsonFile.getOriginalFilename(); String baseName = (originalName != null && !originalName.isBlank()) @@ -110,8 +114,8 @@ public class ConvertPdfJsonController { : "document"; String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf"; TempFile tempOut = tempFileManager.createManagedTempFile(".pdf"); - try { - Files.write(tempOut.getPath(), pdfBytes); + try (OutputStream os = Files.newOutputStream(tempOut.getPath())) { + pdfJsonConversionService.convertJsonToPdf(jsonFile, os); } catch (Exception e) { tempOut.close(); throw e; @@ -126,7 +130,7 @@ 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 extractPdfMetadata(@ModelAttribute PDFFile request) + public ResponseEntity extractPdfMetadata(@ModelAttribute PDFFile request) throws Exception { MultipartFile inputFile = request.getFileInput(); if (inputFile == null) { @@ -139,30 +143,24 @@ public class ConvertPdfJsonController { log.debug("Extracting metadata for PDF, assigned jobId: {}", scopedJobKey); - byte[] jsonBytes = - pdfJsonConversionService.extractDocumentMetadata(inputFile, scopedJobKey); - logJsonResponse("pdf/text-editor/metadata", jsonBytes); - TempFile tempOut = tempFileManager.createManagedTempFile(".json"); - try { - Files.write(tempOut.getPath(), jsonBytes); + try (OutputStream os = Files.newOutputStream(tempOut.getPath())) { + pdfJsonConversionService.extractDocumentMetadata(inputFile, scopedJobKey, os); } catch (Exception e) { tempOut.close(); throw e; } - return ResponseEntity.ok() - .header("X-Job-Id", scopedJobKey) - .contentType(MediaType.APPLICATION_JSON) - .contentLength(java.nio.file.Files.size(tempOut.getPath())) - .body( - os -> { - try (os) { - Files.copy(tempOut.getPath(), os); - os.flush(); - } finally { - tempOut.close(); - } - }); + try { + logJsonResponse("pdf/text-editor/metadata", tempOut.getPath()); + return ResponseEntity.ok() + .header("X-Job-Id", scopedJobKey) + .contentType(MediaType.APPLICATION_JSON) + .contentLength(Files.size(tempOut.getPath())) + .body(new WebResponseUtils.ManagedTempFileResource(tempOut)); + } catch (IOException | RuntimeException e) { + tempOut.close(); + throw e; + } } @AutoJobPostMapping( @@ -175,7 +173,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 exportPartialPdf( + public ResponseEntity exportPartialPdf( @PathVariable String jobId, @RequestBody PdfJsonDocument document, @RequestParam(value = "filename", required = false) String filename) @@ -215,22 +213,26 @@ 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 extractSinglePage( + public ResponseEntity extractSinglePage( @PathVariable String jobId, @PathVariable int pageNumber) throws Exception { validateJobAccess(jobId); - byte[] jsonBytes = pdfJsonConversionService.extractSinglePage(jobId, pageNumber); - logJsonResponse("pdf/text-editor/page", jsonBytes); String docName = "page_" + pageNumber + ".json"; TempFile tempOut = tempFileManager.createManagedTempFile(".json"); + try (OutputStream os = Files.newOutputStream(tempOut.getPath())) { + pdfJsonConversionService.extractSinglePage(jobId, pageNumber, os); + } catch (Exception e) { + tempOut.close(); + throw e; + } try { - Files.write(tempOut.getPath(), jsonBytes); + logJsonResponse("pdf/text-editor/page", tempOut.getPath()); + return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); } catch (Exception e) { tempOut.close(); throw e; } - return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); } @GetMapping(value = "/pdf/text-editor/fonts/{jobId}/{pageNumber}") @@ -240,22 +242,26 @@ 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 extractPageFonts( + public ResponseEntity extractPageFonts( @PathVariable String jobId, @PathVariable int pageNumber) throws Exception { validateJobAccess(jobId); - byte[] jsonBytes = pdfJsonConversionService.extractPageFonts(jobId, pageNumber); - logJsonResponse("pdf/text-editor/fonts/page", jsonBytes); String docName = "page_fonts_" + pageNumber + ".json"; TempFile tempOut = tempFileManager.createManagedTempFile(".json"); + try (OutputStream os = Files.newOutputStream(tempOut.getPath())) { + pdfJsonConversionService.extractPageFonts(jobId, pageNumber, os); + } catch (Exception e) { + tempOut.close(); + throw e; + } try { - Files.write(tempOut.getPath(), jsonBytes); + logJsonResponse("pdf/text-editor/fonts/page", tempOut.getPath()); + return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); } catch (Exception e) { tempOut.close(); throw e; } - return WebResponseUtils.fileToWebResponse(tempOut, docName, MediaType.APPLICATION_JSON); } @AutoJobPostMapping( @@ -282,13 +288,34 @@ public class ConvertPdfJsonController { return baseJobId; } - private void logJsonResponse(String label, byte[] jsonBytes) { - if (jsonBytes == null) { - log.warn("Returning {} JSON response: null bytes", label); + private void logJsonResponse(String label, Path jsonPath) { + if (jsonPath == null) { + log.warn("Returning {} JSON response: null path", label); return; } - if (log.isDebugEnabled()) { + boolean debugEnabled = log.isDebugEnabled(); + boolean dumpEnabled = isPdfJsonDebugDumpEnabled(); + boolean repeatScanEnabled = isPdfJsonRepeatScanEnabled(); + + // Reading the full file back from disk is only worth it for these diagnostic paths. + // The happy path (no debug flags) returns here immediately — no extra IO. + if (!debugEnabled && !dumpEnabled && !repeatScanEnabled) { + return; + } + + byte[] jsonBytes; + try { + jsonBytes = Files.readAllBytes(jsonPath); + } catch (IOException ex) { + log.warn( + "Failed to read PDF JSON ({}) for diagnostic logging: {}", + label, + ex.getMessage()); + return; + } + + if (debugEnabled) { int length = jsonBytes.length; boolean endsWithJson = length > 0 && (jsonBytes[length - 1] == '}' || jsonBytes[length - 1] == ']'); @@ -309,24 +336,23 @@ public class ConvertPdfJsonController { tail); } - if (isPdfJsonDebugDumpEnabled()) { + if (dumpEnabled) { try { String tmpDir = System.getProperty("java.io.tmpdir"); String customDir = System.getenv("SPDF_PDFJSON_DUMP_DIR"); - java.nio.file.Path dumpDir = + Path dumpDir = customDir != null && !customDir.isBlank() - ? java.nio.file.Path.of(customDir) - : java.nio.file.Path.of(tmpDir); - java.nio.file.Path dumpPath = - java.nio.file.Files.createTempFile(dumpDir, "pdfjson_", ".json"); - java.nio.file.Files.write(dumpPath, jsonBytes); + ? Path.of(customDir) + : Path.of(tmpDir); + Path dumpPath = Files.createTempFile(dumpDir, "pdfjson_", ".json"); + Files.copy(jsonPath, dumpPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); log.debug("PDF JSON debug dump ({}): {}", label, dumpPath); } catch (Exception ex) { log.warn("Failed to write PDF JSON debug dump ({}): {}", label, ex.getMessage()); } } - if (isPdfJsonRepeatScanEnabled()) { + if (repeatScanEnabled) { logRepeatedJsonStrings(label, jsonBytes); } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDF.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDF.java index dc7980523..f0f08180c 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDF.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDF.java @@ -9,12 +9,13 @@ import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; 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; @@ -54,8 +55,7 @@ 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 convertSvgToPdf( - @ModelAttribute SvgToPdfRequest request) { + public ResponseEntity convertSvgToPdf(@ModelAttribute SvgToPdfRequest request) { MultipartFile[] inputFiles = request.getFileInput(); boolean combineIntoSinglePdf = Boolean.TRUE.equals(request.getCombineIntoSinglePdf()); @@ -95,10 +95,7 @@ public class ConvertSvgToPDF { filenames.add(Filenames.toSimpleFileName(originalFilename)); } catch (IOException e) { - log.error( - "SVG sanitization/reading failed for {}: {}", - originalFilename, - e.getMessage()); + log.error("SVG sanitization/reading failed for {}", originalFilename, e); } } @@ -121,17 +118,14 @@ public class ConvertSvgToPDF { } } - private ResponseEntity errorResponse(HttpStatus status, String message) { + private ResponseEntity 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); + return ResponseEntity.status(status) + .contentLength(body.length) + .body(new ByteArrayResource(body)); } - private ResponseEntity handleCombinedConversion( + private ResponseEntity handleCombinedConversion( List sanitizedSvgs, List filenames) { try { log.info("Combining {} SVG files into single PDF", sanitizedSvgs.size()); @@ -169,7 +163,7 @@ public class ConvertSvgToPDF { } } - private ResponseEntity handleSeparateConversion( + private ResponseEntity handleSeparateConversion( List sanitizedSvgs, List filenames) { List convertedPdfs = new ArrayList<>(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportController.java index fcee92f02..19014549c 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportController.java @@ -10,10 +10,10 @@ import java.util.Locale; import java.util.Set; import org.apache.commons.io.FilenameUtils; +import org.springframework.core.io.Resource; 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; @@ -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 convertGhostscriptInputsToPdf( + public ResponseEntity convertGhostscriptInputsToPdf( @Valid @ModelAttribute PdfVectorExportRequest request) throws Exception { String originalName = @@ -98,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 convertPdfToVector( + public ResponseEntity convertPdfToVector( @Valid @ModelAttribute PdfVectorExportRequest request) throws Exception { String originalName = diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java index 514d028c6..e8482855e 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java @@ -5,11 +5,11 @@ import java.io.IOException; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.springframework.core.io.Resource; 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; @@ -56,8 +56,8 @@ public class FilterController { description = "PDF did not pass filter", content = @Content()) }) - public ResponseEntity containsText( - @ModelAttribute ContainsTextRequest request) throws IOException, InterruptedException { + public ResponseEntity containsText(@ModelAttribute ContainsTextRequest request) + throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); String text = request.getText(); String pageNumber = request.getPageNumbers(); @@ -89,8 +89,8 @@ public class FilterController { description = "PDF did not pass filter", content = @Content()) }) - public ResponseEntity containsImage( - @ModelAttribute PDFWithPageNums request) throws IOException, InterruptedException { + public ResponseEntity containsImage(@ModelAttribute PDFWithPageNums request) + throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); String pageNumber = request.getPageNumbers(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormFillController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormFillController.java index 8692b04bc..d24d175f5 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormFillController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/form/FormFillController.java @@ -10,6 +10,7 @@ import java.util.Map; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -18,7 +19,6 @@ 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; @@ -63,7 +63,7 @@ public class FormFillController { private final ObjectMapper objectMapper; private final TempFileManager tempFileManager; - private ResponseEntity saveDocument(PDDocument document, String baseName) + private ResponseEntity saveDocument(PDDocument document, String baseName) throws IOException { return WebResponseUtils.pdfDocToWebResponse(document, baseName + ".pdf", tempFileManager); } @@ -262,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 modifyFields( + public ResponseEntity modifyFields( @Parameter( description = "The input PDF file", required = true, @@ -293,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 deleteFields( + public ResponseEntity deleteFields( @Parameter( description = "The input PDF file", required = true, @@ -329,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 fillForm( + public ResponseEntity fillForm( @Parameter( description = "The input PDF file", required = true, @@ -356,7 +356,7 @@ public class FormFillController { document -> FormUtils.applyFieldValues(document, values, flatten, true)); } - private ResponseEntity processSingleFile( + private ResponseEntity processSingleFile( MultipartFile file, String suffix, DocumentProcessor processor) throws IOException { requirePdf(file); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentController.java index 9ee659c35..cd6e6895f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentController.java @@ -6,11 +6,11 @@ import java.util.List; import java.util.Optional; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.core.io.Resource; 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; @@ -54,8 +54,8 @@ public class AttachmentController { summary = "Add attachments to PDF", description = "This endpoint adds attachments to a PDF. Input:PDF, Output:PDF Type:MISO") - public ResponseEntity addAttachments( - @ModelAttribute AddAttachmentRequest request) throws Exception { + public ResponseEntity addAttachments(@ModelAttribute AddAttachmentRequest request) + throws Exception { MultipartFile fileInput = request.getFileInput(); List attachments = request.getAttachments(); boolean convertToPdfA3b = request.isConvertToPdfA3b(); @@ -143,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 extractAttachments( + public ResponseEntity extractAttachments( @ModelAttribute ExtractAttachmentsRequest request) throws IOException { try (PDDocument document = pdfDocumentFactory.load(request, true)) { Optional extracted = pdfAttachmentService.extractAttachments(document); @@ -195,7 +195,7 @@ 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 renameAttachment( + public ResponseEntity renameAttachment( @ModelAttribute RenameAttachmentRequest request) throws Exception { MultipartFile fileInput = request.getFileInput(); String attachmentName = request.getAttachmentName(); @@ -230,7 +230,7 @@ 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 deleteAttachment( + public ResponseEntity deleteAttachment( @ModelAttribute DeleteAttachmentRequest request) throws Exception { MultipartFile fileInput = request.getFileInput(); String attachmentName = request.getAttachmentName(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java index 58fd995f6..0e4f0fe05 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java @@ -8,11 +8,11 @@ import java.util.List; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.TextPosition; +import org.springframework.core.io.Resource; 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; @@ -45,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 extractHeader( - @ModelAttribute ExtractHeaderRequest request) throws Exception { + public ResponseEntity extractHeader(@ModelAttribute ExtractHeaderRequest request) + throws Exception { MultipartFile file = request.getFileInput(); boolean useFirstTextAsFallback = Boolean.TRUE.equals(request.getUseFirstTextAsFallback()); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index d771df580..2ecc0aa30 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -18,11 +18,11 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.rendering.PDFRenderer; +import org.springframework.core.io.Resource; 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; @@ -276,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 autoSplitPdf( - @ModelAttribute AutoSplitPdfRequest request) throws IOException { + public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) + throws IOException { MultipartFile file = request.getFileInput(); boolean duplexMode = Boolean.TRUE.equals(request.getDuplexMode()); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java index 98364d9c2..04384ff87 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java @@ -15,12 +15,12 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.text.PDFTextStripper; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; 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; @@ -88,7 +88,7 @@ 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 removeBlankPages( + public ResponseEntity removeBlankPages( @ModelAttribute RemoveBlankPagesRequest request) throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index d4ad134ec..09d613675 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -32,13 +32,13 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImage; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; 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.server.ResponseStatusException; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import io.swagger.v3.oas.annotations.Operation; @@ -928,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 optimizePdf( - @ModelAttribute OptimizePdfRequest request) throws Exception { + public ResponseEntity optimizePdf(@ModelAttribute OptimizePdfRequest request) + throws Exception { MultipartFile inputFile = request.getFileInput(); // Validate input file diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/DecompressPdfController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/DecompressPdfController.java index 9afe8ba23..2140c7abc 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/DecompressPdfController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/DecompressPdfController.java @@ -9,11 +9,11 @@ import org.apache.pdfbox.cos.*; import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.pdfwriter.compress.CompressParameters; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.core.io.Resource; 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; @@ -42,7 +42,7 @@ public class DecompressPdfController { @Operation( summary = "Decompress PDF streams", description = "Fully decompresses all PDF streams including text content") - public ResponseEntity decompressPdf(@ModelAttribute PDFFile request) + public ResponseEntity decompressPdf(@ModelAttribute PDFFile request) throws IOException { MultipartFile file = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java index c5cfef2c7..4092c2db7 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java @@ -17,11 +17,11 @@ import javax.imageio.ImageIO; import org.apache.commons.io.FileUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.PDFRenderer; +import org.springframework.core.io.Resource; 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; @@ -65,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 extractImageScans( + public ResponseEntity extractImageScans( @ModelAttribute ExtractImageScansRequest request) throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); @@ -198,7 +198,7 @@ public class ExtractImageScansController { } } - ResponseEntity response = + ResponseEntity response = WebResponseUtils.zipFileToWebResponse(finalOutput, outputZipFilename); finalOutputOwnershipTransferred = true; return response; @@ -215,7 +215,7 @@ public class ExtractImageScansController { out.write(imageBytes); } - ResponseEntity response = + ResponseEntity response = WebResponseUtils.fileToWebResponse( finalOutput, GeneralUtils.generateFilename(fileName, ".png"), diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java index e5de04ce5..865e95369 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java @@ -19,11 +19,11 @@ import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.springframework.core.io.Resource; 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; @@ -57,8 +57,8 @@ public class ExtractImagesController { "This endpoint extracts images from a given PDF file and returns them in a zip" + " file. Users can specify the output image format. Input:PDF" + " Output:IMAGE/ZIP Type:SIMO") - public ResponseEntity extractImages( - @ModelAttribute PDFExtractImagesRequest request) throws IOException { + public ResponseEntity extractImages(@ModelAttribute PDFExtractImagesRequest request) + throws IOException { MultipartFile file = request.getFileInput(); String imageFormat = request.getFormat(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java index 7eac5c2b8..c03a27bc3 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java @@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.PDFRenderer; +import org.springframework.core.io.Resource; 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; @@ -49,7 +49,7 @@ 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 flatten(@ModelAttribute FlattenRequest request) + public ResponseEntity flatten(@ModelAttribute FlattenRequest request) throws Exception { MultipartFile file = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java index 9a7f7dcb2..e32875d42 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java @@ -8,12 +8,12 @@ import java.util.Map.Entry; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; 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; @@ -64,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 metadata(@ModelAttribute MetadataRequest request) + public ResponseEntity metadata(@ModelAttribute MetadataRequest request) throws IOException { // Extract PDF file from the request object diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java index 990f6dd98..5f0996d0a 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java @@ -21,11 +21,11 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.text.PDFTextStripper; +import org.springframework.core.io.Resource; 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; @@ -90,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 processPdfWithOCR( + public ResponseEntity processPdfWithOCR( @ModelAttribute ProcessPdfWithOcrRequest request) throws IOException, InterruptedException { MultipartFile inputFile = request.getFileInput(); @@ -194,13 +194,13 @@ public class OCRController { // The intermediate PDF temp file is no longer needed; only the zip is streamed. tempOutputFile.close(); pdfOwnershipTransferred = true; - ResponseEntity response = + ResponseEntity response = WebResponseUtils.fileToWebResponse( tempZipFile, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); zipOwnershipTransferred = true; return response; } else { - ResponseEntity response = + ResponseEntity response = WebResponseUtils.pdfFileToWebResponse(tempOutputFile, outputFilename); pdfOwnershipTransferred = true; return response; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java index f7e36340c..eb75364d3 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java @@ -6,12 +6,12 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; 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; @@ -45,8 +45,7 @@ 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 overlayImage( - @ModelAttribute OverlayImageRequest request) { + public ResponseEntity overlayImage(@ModelAttribute OverlayImageRequest request) { MultipartFile pdfFile = request.getFileInput(); MultipartFile imageFile = request.getImageFile(); float x = request.getX(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java index 4e12a694a..737fcdb92 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java @@ -11,11 +11,11 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.Standard14Fonts; +import org.springframework.core.io.Resource; 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 +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 addPageNumbers( - @ModelAttribute AddPageNumbersRequest request) throws IOException { + public ResponseEntity addPageNumbers(@ModelAttribute AddPageNumbersRequest request) + throws IOException { MultipartFile file = request.getFileInput(); String customMargin = request.getCustomMargin(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RemoveImagesController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RemoveImagesController.java index 33fcea588..94fcbc004 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RemoveImagesController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RemoveImagesController.java @@ -12,11 +12,11 @@ import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.springframework.core.io.Resource; 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; @@ -47,7 +47,7 @@ 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 removeImages(@ModelAttribute PDFFile request) + public ResponseEntity removeImages(@ModelAttribute PDFFile request) throws IOException { MultipartFile inputFile = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java index b0dc0a03a..492a4bd25 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java @@ -4,11 +4,11 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import org.springframework.core.io.Resource; 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; @@ -54,7 +54,7 @@ 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 repairPdf(@ModelAttribute PDFFile file) + public ResponseEntity repairPdf(@ModelAttribute PDFFile file) throws IOException, InterruptedException { MultipartFile inputFile = file.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorController.java index defee4575..5e33f3f76 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorController.java @@ -6,10 +6,10 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; 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; @@ -39,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 replaceAndInvertColor( + public ResponseEntity replaceAndInvertColor( @ModelAttribute ReplaceAndInvertColorRequest request) throws IOException { InputStreamResource resource = diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ScannerEffectController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ScannerEffectController.java index 8318960ff..c6a5462a3 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ScannerEffectController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ScannerEffectController.java @@ -30,11 +30,11 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.rendering.PDFRenderer; +import org.springframework.core.io.Resource; 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; @@ -564,7 +564,7 @@ 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 scannerEffect( + public ResponseEntity scannerEffect( @Valid @ModelAttribute ScannerEffectRequest request) throws IOException { MultipartFile file = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java index 0d7dc1d1d..7ab01c985 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java @@ -7,11 +7,11 @@ import java.util.Map; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; +import org.springframework.core.io.Resource; 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; @@ -39,8 +39,7 @@ public class ShowJavascript { @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 extractHeader(@ModelAttribute PDFFile file) - throws Exception { + public ResponseEntity extractHeader(@ModelAttribute PDFFile file) throws Exception { MultipartFile inputFile = file.getFileInput(); StringBuilder script = new StringBuilder(); boolean foundScript = false; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java index 0e6fc1d00..a08ea1a49 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java @@ -31,13 +31,13 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.util.Matrix; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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; @@ -93,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 addStamp(@ModelAttribute AddStampRequest request) + public ResponseEntity addStamp(@ModelAttribute AddStampRequest request) throws IOException, Exception { MultipartFile pdfFile = request.getFileInput(); String pdfFileName = pdfFile.getOriginalFilename(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsController.java index 22117b5f3..7c51f9f49 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsController.java @@ -10,10 +10,10 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.springframework.core.io.Resource; 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; @@ -49,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 unlockPDFForms(@ModelAttribute PDFFile file) { + public ResponseEntity unlockPDFForms(@ModelAttribute PDFFile file) { try (PDDocument document = pdfDocumentFactory.load(file)) { PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java index b2c717ca8..4440c9729 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java @@ -13,7 +13,6 @@ 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; @@ -58,8 +57,8 @@ public class PipelineController { "This endpoint processes multiple PDF files through a configurable pipeline of operations. " + "Users provide files and a JSON configuration defining the sequence of operations to perform. " + "Input:PDF Output:PDF/ZIP Type:MIMO") - public ResponseEntity handleData( - @ModelAttribute HandleDataRequest request) throws DatabindException, JacksonException { + public ResponseEntity handleData(@ModelAttribute HandleDataRequest request) + throws DatabindException, JacksonException { MultipartFile[] files = request.getFileInput(); String jsonString = request.getJson(); if (files == null) { diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index f1b085dea..4815f63b5 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -55,6 +55,7 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; import org.bouncycastle.pkcs.PKCSException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.WebDataBinder; @@ -64,7 +65,6 @@ 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; @@ -168,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 signPDFWithCert( - @ModelAttribute SignPDFWithCertRequest request) throws Exception { + public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) + throws Exception { MultipartFile pdf = request.getFileInput(); String certType = request.getCertType(); MultipartFile privateKeyFile = request.getPrivateKeyFile(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index e9fa41ff0..5e99c939e 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -5,11 +5,11 @@ import java.io.IOException; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; +import org.springframework.core.io.Resource; 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; @@ -40,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 removePassword( - @ModelAttribute PDFPasswordRequest request) throws IOException { + public ResponseEntity removePassword(@ModelAttribute PDFPasswordRequest request) + throws IOException { MultipartFile fileInput = request.getFileInput(); String password = request.getPassword(); @@ -70,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 addPassword( - @ModelAttribute AddPasswordRequest request) throws IOException { + public ResponseEntity addPassword(@ModelAttribute AddPasswordRequest request) + throws IOException { MultipartFile fileInput = request.getFileInput(); String ownerPassword = request.getOwnerPassword(); String password = request.getPassword(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java index 377529b5f..bcb7f5e52 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java @@ -33,13 +33,13 @@ import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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; @@ -108,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 redactPDF( - @ModelAttribute ManualRedactPdfRequest request) throws IOException { + public ResponseEntity redactPDF(@ModelAttribute ManualRedactPdfRequest request) + throws IOException { MultipartFile file = request.getFileInput(); List redactionAreas = request.getRedactions(); @@ -501,8 +501,7 @@ 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 redactPdf( - @ModelAttribute RedactPdfRequest request) { + public ResponseEntity redactPdf(@ModelAttribute RedactPdfRequest request) { String[] listOfText = request.getListOfText().split("\n"); boolean useRegex = Boolean.TRUE.equals(request.getUseRegex()); boolean wholeWordSearchBool = Boolean.TRUE.equals(request.getWholeWordSearch()); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java index f23d63765..019ada7ae 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java @@ -7,11 +7,11 @@ import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; +import org.springframework.core.io.Resource; 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; @@ -40,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 removeCertSignPDF(@ModelAttribute PDFFile request) + public ResponseEntity removeCertSignPDF(@ModelAttribute PDFFile request) throws Exception { MultipartFile pdf = request.getFileInput(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java index 7db9f2a8a..859920505 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java @@ -24,11 +24,11 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.springframework.core.io.Resource; 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 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 sanitizePDF( - @ModelAttribute SanitizePdfRequest request) throws IOException { + public ResponseEntity sanitizePDF(@ModelAttribute SanitizePdfRequest request) + throws IOException { MultipartFile inputFile = request.getFileInput(); boolean removeJavaScript = Boolean.TRUE.equals(request.getRemoveJavaScript()); boolean removeEmbeddedFiles = Boolean.TRUE.equals(request.getRemoveEmbeddedFiles()); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/TimestampController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/TimestampController.java index ef744330f..5d21655ae 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/TimestampController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/TimestampController.java @@ -28,11 +28,11 @@ import org.bouncycastle.tsp.TimeStampRequest; import org.bouncycastle.tsp.TimeStampRequestGenerator; import org.bouncycastle.tsp.TimeStampResponse; import org.bouncycastle.tsp.TimeStampToken; +import org.springframework.core.io.Resource; 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; @@ -87,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 timestampPdf( - @ModelAttribute TimestampPdfRequest request) throws Exception { + public ResponseEntity timestampPdf(@ModelAttribute TimestampPdfRequest request) + throws Exception { MultipartFile inputFile = request.getFileInput(); ApplicationProperties.Security.Timestamp tsConfig = applicationProperties.getSecurity().getTimestamp(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index 5e51fd55c..11a1d4cbc 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -24,13 +24,13 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.util.Matrix; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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; @@ -76,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 addWatermark( - @Valid @ModelAttribute AddWatermarkRequest request) throws IOException, Exception { + public ResponseEntity addWatermark(@Valid @ModelAttribute AddWatermarkRequest request) + throws IOException, Exception { MultipartFile pdfFile = request.getFileInput(); String pdfFileName = pdfFile.getOriginalFilename(); if (pdfFileName != null && (pdfFileName.contains("..") || pdfFileName.startsWith("/"))) { diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdown.java b/app/core/src/main/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdown.java index 013aaa01e..d44233425 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdown.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdown.java @@ -1,10 +1,10 @@ package stirling.software.SPDF.model.api.converters; +import org.springframework.core.io.Resource; 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,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 processPdfToMarkdown(@ModelAttribute PDFFile file) + public ResponseEntity processPdfToMarkdown(@ModelAttribute PDFFile file) throws Exception { MultipartFile inputFile = file.getFileInput(); PDFToFile pdfToFile = new PDFToFile(tempFileManager); diff --git a/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java index edfdddc36..9a87167d7 100644 --- a/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java @@ -248,24 +248,28 @@ public class PdfJsonConversionService { } } - public byte[] convertPdfToJson(MultipartFile file) throws IOException { - return convertPdfToJson(file, null, false); + public void convertPdfToJson(MultipartFile file, OutputStream out) throws IOException { + convertPdfToJson(file, null, false, out); } - public byte[] convertPdfToJson(MultipartFile file, boolean lightweight) throws IOException { - return convertPdfToJson(file, null, lightweight); - } - - public byte[] convertPdfToJson( - MultipartFile file, Consumer progressCallback) + public void convertPdfToJson(MultipartFile file, boolean lightweight, OutputStream out) throws IOException { - return convertPdfToJson(file, progressCallback, false); + convertPdfToJson(file, null, lightweight, out); } - public byte[] convertPdfToJson( + public void convertPdfToJson( MultipartFile file, Consumer progressCallback, - boolean lightweight) + OutputStream out) + throws IOException { + convertPdfToJson(file, progressCallback, false, out); + } + + public void convertPdfToJson( + MultipartFile file, + Consumer progressCallback, + boolean lightweight, + OutputStream out) throws IOException { if (file == null) { throw ExceptionUtils.createNullArgumentException("fileInput"); @@ -593,7 +597,9 @@ public class PdfJsonConversionService { pdfJson.getPages().size()); } - byte[] result = objectMapper.writeValueAsBytes(pdfJson); + // Stream JSON directly to the caller's OutputStream to avoid allocating the + // entire document on the heap before writing it out. + objectMapper.writeValue(out, pdfJson); progress.accept(PdfJsonConversionProgress.complete()); // Clear Type3 cache entries immediately for non-cached conversions @@ -602,15 +608,13 @@ public class PdfJsonConversionService { if (!useLazyImages) { clearType3CacheEntriesForJob(jobId); } - - return result; } } finally { closeQuietly(normalizedFile); } } - public byte[] convertJsonToPdf(MultipartFile file) throws IOException { + public void convertJsonToPdf(MultipartFile file, OutputStream out) throws IOException { if (file == null) { throw ExceptionUtils.createNullArgumentException("fileInput"); } @@ -804,15 +808,11 @@ public class PdfJsonConversionService { log.info("JSON->PDF conversion complete: {} pages", pages.size()); } - try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - document.save(baos); - byte[] result = baos.toByteArray(); + // Stream the PDF directly to the caller's OutputStream — PDFBox writes incrementally. + document.save(out); - // Clear Type3 cache entries for this conversion - clearType3CacheEntriesForJob(syntheticJobId); - - return result; - } + // Clear Type3 cache entries for this conversion + clearType3CacheEntriesForJob(syntheticJobId); } } @@ -6075,7 +6075,8 @@ public class PdfJsonConversionService { * Extracts document metadata, fonts, and page dimensions without page content. Caches the PDF * bytes for subsequent page requests. */ - public byte[] extractDocumentMetadata(MultipartFile file, String jobId) throws IOException { + public void extractDocumentMetadata(MultipartFile file, String jobId, OutputStream out) + throws IOException { if (file == null) { throw ExceptionUtils.createNullArgumentException("fileInput"); } @@ -6170,12 +6171,13 @@ public class PdfJsonConversionService { progress.accept( PdfJsonConversionProgress.of(100, "complete", "Metadata extraction complete")); - return objectMapper.writeValueAsBytes(docMetadata); + objectMapper.writeValue(out, docMetadata); } } /** Extracts a single page from cached PDF bytes. Re-loads the PDF for each request. */ - public byte[] extractSinglePage(String jobId, int pageNumber) throws IOException { + public void extractSinglePage(String jobId, int pageNumber, OutputStream out) + throws IOException { CachedPdfDocument cached = getCachedDocument(jobId); if (cached == null) { throw new stirling.software.SPDF.exception.CacheUnavailableException( @@ -6326,11 +6328,12 @@ public class PdfJsonConversionService { pageModel.getAnnotations().size(), jobId); - return objectMapper.writeValueAsBytes(pageModel); + objectMapper.writeValue(out, pageModel); } } - public byte[] extractPageFonts(String jobId, int pageNumber) throws IOException { + public void extractPageFonts(String jobId, int pageNumber, OutputStream out) + throws IOException { CachedPdfDocument cached = getCachedDocument(jobId); if (cached == null) { throw new stirling.software.SPDF.exception.CacheUnavailableException( @@ -6347,7 +6350,8 @@ public class PdfJsonConversionService { Map pageMap = pageFontResources != null ? pageFontResources.get(pageNumber) : null; if (pageMap == null || pageMap.isEmpty()) { - return objectMapper.writeValueAsBytes(Collections.emptyList()); + objectMapper.writeValue(out, Collections.emptyList()); + return; } Map cachedFonts = cached.getFonts(); @@ -6375,7 +6379,7 @@ public class PdfJsonConversionService { pageFonts.sort( Comparator.comparing( PdfJsonFont::getUid, Comparator.nullsLast(Comparator.naturalOrder()))); - return objectMapper.writeValueAsBytes(pageFonts); + objectMapper.writeValue(out, pageFonts); } public byte[] exportUpdatedPages(String jobId, PdfJsonDocument updates) throws IOException { diff --git a/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java index 97748fa97..ba0f2c2ce 100644 --- a/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.service.pdfjson; import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -81,14 +82,15 @@ public class PdfLazyLoadingService { * @param jobId The job ID for caching * @param fonts Font map (will be populated) * @param pageFontResources Page font resources map (will be populated) - * @return Serialized metadata JSON + * @param out The output stream to write the serialized metadata JSON to * @throws IOException If extraction fails */ - public byte[] extractDocumentMetadata( + public void extractDocumentMetadata( MultipartFile file, String jobId, Map fonts, - Map> pageFontResources) + Map> pageFontResources, + OutputStream out) throws IOException { if (file == null) { throw ExceptionUtils.createNullArgumentException("fileInput"); @@ -160,7 +162,7 @@ public class PdfLazyLoadingService { progress.accept( PdfJsonConversionProgress.of(100, "complete", "Metadata extraction complete")); - return objectMapper.writeValueAsBytes(docMetadata); + objectMapper.writeValue(out, docMetadata); } } @@ -174,10 +176,10 @@ public class PdfLazyLoadingService { * @param filterImageXObjectsFromResources Function to filter image XObjects * @param extractText Function to extract text elements for the page * @param extractAnnotations Function to extract annotations for the page - * @return Serialized page JSON + * @param out The output stream to write the serialized page JSON to * @throws IOException If extraction fails */ - public byte[] extractSinglePage( + public void extractSinglePage( String jobId, int pageNumber, java.util.function.Function serializeCosValue, @@ -186,7 +188,8 @@ public class PdfLazyLoadingService { java.util.function.BiFunction> extractText, java.util.function.BiFunction> - extractAnnotations) + extractAnnotations, + OutputStream out) throws IOException { CachedPdfDocument cached = documentCache.get(jobId); if (cached == null) { @@ -238,7 +241,7 @@ public class PdfLazyLoadingService { pageModel.getAnnotations().size(), jobId); - return objectMapper.writeValueAsBytes(pageModel); + objectMapper.writeValue(out, pageModel); } } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/BookletImpositionControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/BookletImpositionControllerTest.java index 0e260e382..a42666139 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/BookletImpositionControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/BookletImpositionControllerTest.java @@ -20,11 +20,12 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.general.BookletImpositionRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -33,14 +34,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class BookletImpositionControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -94,8 +96,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(drainBody(response)).isNotEmpty(); @@ -126,8 +127,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(drainBody(response)).isNotEmpty(); @@ -144,8 +144,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -162,8 +161,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -180,8 +178,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -198,8 +195,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -216,8 +212,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -232,8 +227,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -261,8 +255,7 @@ class BookletImpositionControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(sourceDoc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)).thenReturn(newDoc); - ResponseEntity response = - controller.createBookletImposition(request); + ResponseEntity response = controller.createBookletImposition(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/CropControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/CropControllerTest.java index 05389a840..9ef866929 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/CropControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/CropControllerTest.java @@ -27,11 +27,12 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.general.CropPdfForm; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -41,14 +42,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) @DisplayName("CropController Tests") class CropControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -205,7 +207,7 @@ class CropControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) .thenReturn(newDocument); - ResponseEntity response = cropController.cropPdf(request); + ResponseEntity response = cropController.cropPdf(request); assertThat(response) .isNotNull() @@ -242,7 +244,7 @@ class CropControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) .thenReturn(newDocument); - ResponseEntity response = cropController.cropPdf(request); + ResponseEntity response = cropController.cropPdf(request); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @@ -282,7 +284,7 @@ class CropControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)) .thenReturn(newDoc); - ResponseEntity response = cropController.cropPdf(request); + ResponseEntity response = cropController.cropPdf(request); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @@ -313,7 +315,7 @@ class CropControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDoc)) .thenReturn(newDoc); - ResponseEntity response = cropController.cropPdf(request); + ResponseEntity response = cropController.cropPdf(request); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @@ -676,7 +678,7 @@ class CropControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) .thenReturn(newDocument); - ResponseEntity response = cropController.cropPdf(request); + ResponseEntity response = cropController.cropPdf(request); assertThat(response).isNotNull(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @@ -704,7 +706,7 @@ class CropControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDocument)) .thenReturn(newDocument); - ResponseEntity response = cropController.cropPdf(request); + ResponseEntity response = cropController.cropPdf(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); verify(mockDocument, times(1)).close(); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/EditTableOfContentsControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/EditTableOfContentsControllerTest.java index 5f8f459a9..6b86bc484 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/EditTableOfContentsControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/EditTableOfContentsControllerTest.java @@ -25,10 +25,10 @@ import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.controller.api.EditTableOfContentsController.BookmarkItem; import stirling.software.SPDF.model.api.EditTableOfContentsRequest; @@ -255,7 +255,7 @@ class EditTableOfContentsControllerTest { .save(any(File.class)); // When - ResponseEntity result = + ResponseEntity result = editTableOfContentsController.editTableOfContents(request); // Then @@ -319,7 +319,7 @@ class EditTableOfContentsControllerTest { .save(any(File.class)); // When - ResponseEntity result = + ResponseEntity result = editTableOfContentsController.editTableOfContents(request); // Then @@ -373,7 +373,7 @@ class EditTableOfContentsControllerTest { .save(any(File.class)); // When - ResponseEntity result = + ResponseEntity result = editTableOfContentsController.editTableOfContents(request); // Then diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/MultiPageLayoutControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/MultiPageLayoutControllerTest.java index 28276a3f3..d28b6add9 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/MultiPageLayoutControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/MultiPageLayoutControllerTest.java @@ -18,11 +18,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -31,14 +32,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class MultiPageLayoutControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -97,7 +99,7 @@ class MultiPageLayoutControllerTest { req.setAddBorder(Boolean.FALSE); req.setFileInput(fileWithExt); - ResponseEntity resp = controller.mergeMultiplePagesIntoOne(req); + ResponseEntity resp = controller.mergeMultiplePagesIntoOne(req); Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode()); Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType()); Assertions.assertNotNull(resp.getBody()); @@ -122,7 +124,7 @@ class MultiPageLayoutControllerTest { req.setAddBorder(Boolean.TRUE); req.setFileInput(fileWithExt); - ResponseEntity resp = controller.mergeMultiplePagesIntoOne(req); + ResponseEntity resp = controller.mergeMultiplePagesIntoOne(req); Assertions.assertEquals(HttpStatus.OK, resp.getStatusCode()); Assertions.assertEquals(MediaType.APPLICATION_PDF, resp.getHeaders().getContentType()); Assertions.assertNotNull(resp.getBody()); @@ -145,7 +147,7 @@ class MultiPageLayoutControllerTest { req.setAddBorder(Boolean.TRUE); req.setFileInput(fileNoExt); - ResponseEntity resp = controller.mergeMultiplePagesIntoOne(req); + ResponseEntity resp = controller.mergeMultiplePagesIntoOne(req); Assertions.assertEquals( "name_multi_page_layout.pdf", resp.getHeaders().getContentDisposition().getFilename()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/PdfOverlayControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/PdfOverlayControllerTest.java index 66b3eebff..60210d425 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/PdfOverlayControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/PdfOverlayControllerTest.java @@ -25,12 +25,13 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.general.OverlayPdfsRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -39,14 +40,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class PdfOverlayControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -107,7 +109,7 @@ class PdfOverlayControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); - ResponseEntity response = controller.overlayPdfs(request); + ResponseEntity response = controller.overlayPdfs(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -141,7 +143,7 @@ class PdfOverlayControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); - ResponseEntity response = controller.overlayPdfs(request); + ResponseEntity response = controller.overlayPdfs(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -173,7 +175,7 @@ class PdfOverlayControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); - ResponseEntity response = controller.overlayPdfs(request); + ResponseEntity response = controller.overlayPdfs(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -204,7 +206,7 @@ class PdfOverlayControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); - ResponseEntity response = controller.overlayPdfs(request); + ResponseEntity response = controller.overlayPdfs(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -294,7 +296,7 @@ class PdfOverlayControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); - ResponseEntity response = controller.overlayPdfs(request); + ResponseEntity response = controller.overlayPdfs(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -327,7 +329,7 @@ class PdfOverlayControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(((MultipartFile) inv.getArgument(0)).getBytes())); - ResponseEntity response = controller.overlayPdfs(request); + ResponseEntity response = controller.overlayPdfs(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java index 81f85d60a..c31cae295 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java @@ -16,10 +16,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.general.RearrangePagesRequest; @@ -67,7 +67,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(mockDoc); when(mockDoc.getNumberOfPages()).thenReturn(5); - ResponseEntity response = controller.deletePages(request); + ResponseEntity response = controller.deletePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -97,7 +97,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -127,7 +127,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); verify(mockNewDoc).addPage(page1); @@ -155,7 +155,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); verify(mockNewDoc).addPage(page0); @@ -181,7 +181,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -208,7 +208,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -232,7 +232,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -256,7 +256,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -284,7 +284,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -308,7 +308,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); // 2 pages * 3 duplicates = 6 addPage calls @@ -333,7 +333,7 @@ class RearrangePagesPDFControllerTest { when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(mockDoc)) .thenReturn(mockNewDoc); - ResponseEntity response = controller.rearrangePages(request); + ResponseEntity response = controller.rearrangePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/RotationControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/RotationControllerTest.java index bc5f60977..5677c2607 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/RotationControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/RotationControllerTest.java @@ -20,10 +20,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.general.RotatePDFRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -75,7 +75,7 @@ public class RotationControllerTest { when(mockPage.getRotation()).thenReturn(0); // Act - ResponseEntity response = rotationController.rotatePDF(request); + ResponseEntity response = rotationController.rotatePDF(request); // Assert verify(mockPage).setRotation(90); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java index 4fee57b5d..815309b88 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java @@ -20,11 +20,12 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.general.ScalePagesRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -33,14 +34,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class ScalePagesControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -101,7 +103,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -123,7 +125,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -143,7 +145,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -163,7 +165,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -183,7 +185,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -220,7 +222,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -240,7 +242,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -281,7 +283,7 @@ class ScalePagesControllerTest { setupFactory(); - ResponseEntity response = controller.scalePages(request); + ResponseEntity response = controller.scalePages(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFControllerTest.java index be2295eb0..f92f69163 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEbookToPDFControllerTest.java @@ -31,9 +31,10 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.model.api.converters.ConvertEbookToPdfRequest; @@ -48,14 +49,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ConvertEbookToPDFControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -145,14 +147,13 @@ class ConvertEbookToPDFControllerTest { return execResult; }); - ResponseEntity expectedResponse = - streamingOk("result".getBytes()); + ResponseEntity expectedResponse = streamingOk("result".getBytes()); wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString())) .thenReturn(expectedResponse); gu.when(() -> GeneralUtils.generateFilename("ebook.epub", "_convertedToPDF.pdf")) .thenReturn("ebook_convertedToPDF.pdf"); - ResponseEntity response = controller.convertEbookToPdf(request); + ResponseEntity response = controller.convertEbookToPdf(request); assertSame(expectedResponse, response); @@ -262,11 +263,11 @@ class ConvertEbookToPDFControllerTest { gu.when(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class))) .thenReturn(optimizedBytes); - ResponseEntity expectedResponse = streamingOk(optimizedBytes); + ResponseEntity expectedResponse = streamingOk(optimizedBytes); wr.when(() -> WebResponseUtils.pdfFileToWebResponse(any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.convertEbookToPdf(request); + ResponseEntity response = controller.convertEbookToPdf(request); assertSame(expectedResponse, response); gu.verify(() -> GeneralUtils.optimizePdfWithGhostscript(Mockito.any(byte[].class))); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDFTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDFTest.java index 7f4857d83..ce0b91e0f 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDFTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDFTest.java @@ -23,11 +23,12 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.model.api.converters.EmlToPdfRequest; @@ -40,14 +41,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ConvertEmlToPDFTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -82,7 +84,7 @@ class ConvertEmlToPDFTest { EmlToPdfRequest request = new EmlToPdfRequest(); request.setFileInput(emptyFile); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertTrue( @@ -98,7 +100,7 @@ class ConvertEmlToPDFTest { EmlToPdfRequest request = new EmlToPdfRequest(); request.setFileInput(file); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertTrue( @@ -113,7 +115,7 @@ class ConvertEmlToPDFTest { EmlToPdfRequest request = new EmlToPdfRequest(); request.setFileInput(file); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -126,7 +128,7 @@ class ConvertEmlToPDFTest { EmlToPdfRequest request = new EmlToPdfRequest(); request.setFileInput(file); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertTrue( @@ -146,7 +148,7 @@ class ConvertEmlToPDFTest { when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint"); - ResponseEntity expectedResponse = streamingOk(pdfBytes); + ResponseEntity expectedResponse = streamingOk(pdfBytes); try (MockedStatic emlMock = Mockito.mockStatic(EmlToPdf.class); MockedStatic wrMock = @@ -170,7 +172,7 @@ class ConvertEmlToPDFTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); assertArrayEquals(pdfBytes, drainBody(response)); @@ -188,7 +190,7 @@ class ConvertEmlToPDFTest { request.setFileInput(file); request.setDownloadHtml(true); - ResponseEntity expectedResponse = + ResponseEntity expectedResponse = streamingOk(htmlContent.getBytes(StandardCharsets.UTF_8)); try (MockedStatic emlMock = Mockito.mockStatic(EmlToPdf.class); @@ -209,7 +211,7 @@ class ConvertEmlToPDFTest { any(TempFile.class), anyString(), any(MediaType.class))) .thenReturn(expectedResponse); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -235,7 +237,7 @@ class ConvertEmlToPDFTest { eq(customHtmlSanitizer))) .thenThrow(new IOException("Parse error")); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertTrue( @@ -263,7 +265,7 @@ class ConvertEmlToPDFTest { any(), any(), any(), any(), any(), any(), any())) .thenReturn(null); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertTrue( @@ -287,7 +289,7 @@ class ConvertEmlToPDFTest { when(runtimePathConfig.getWeasyPrintPath()).thenReturn("/usr/bin/weasyprint"); - ResponseEntity expectedResponse = streamingOk(pdfBytes); + ResponseEntity expectedResponse = streamingOk(pdfBytes); try (MockedStatic emlMock = Mockito.mockStatic(EmlToPdf.class); MockedStatic wrMock = @@ -305,7 +307,7 @@ class ConvertEmlToPDFTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -330,7 +332,7 @@ class ConvertEmlToPDFTest { any(), any(), any(), any(), any(), any(), any())) .thenThrow(new InterruptedException("interrupted")); - ResponseEntity response = controller.convertEmlToPdf(request); + ResponseEntity response = controller.convertEmlToPdf(request); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertTrue( diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDFTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDFTest.java index 466ab5065..4b9be15fe 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDFTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDFTest.java @@ -20,10 +20,11 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.model.api.converters.HTMLToPdfRequest; @@ -37,14 +38,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ConvertHtmlToPDFTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -104,7 +106,7 @@ class ConvertHtmlToPDFTest { when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes)) .thenReturn(processedPdf); - ResponseEntity expectedResponse = streamingOk(processedPdf); + ResponseEntity expectedResponse = streamingOk(processedPdf); try (MockedStatic ftpMock = Mockito.mockStatic(FileToPdf.class); MockedStatic guMock = Mockito.mockStatic(GeneralUtils.class); @@ -131,7 +133,7 @@ class ConvertHtmlToPDFTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.HtmlToPdf(request); + ResponseEntity response = controller.HtmlToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -152,7 +154,7 @@ class ConvertHtmlToPDFTest { when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes)) .thenReturn(processedPdf); - ResponseEntity expectedResponse = streamingOk(processedPdf); + ResponseEntity expectedResponse = streamingOk(processedPdf); try (MockedStatic ftpMock = Mockito.mockStatic(FileToPdf.class); MockedStatic guMock = Mockito.mockStatic(GeneralUtils.class); @@ -179,7 +181,7 @@ class ConvertHtmlToPDFTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.HtmlToPdf(request); + ResponseEntity response = controller.HtmlToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFControllerTest.java index 78ce0233b..afd2aa42a 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFControllerTest.java @@ -12,9 +12,10 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; @@ -26,14 +27,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ConvertImgPDFControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdfTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdfTest.java index 00116172c..309f95495 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdfTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdfTest.java @@ -22,10 +22,11 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.model.api.GeneralFile; @@ -39,14 +40,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ConvertMarkdownToPdfTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -106,7 +108,7 @@ class ConvertMarkdownToPdfTest { when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(any(byte[].class))) .thenReturn(processedPdf); - ResponseEntity expectedResponse = streamingOk(processedPdf); + ResponseEntity expectedResponse = streamingOk(processedPdf); try (MockedStatic ftpMock = Mockito.mockStatic(FileToPdf.class); MockedStatic guMock = Mockito.mockStatic(GeneralUtils.class); @@ -133,7 +135,7 @@ class ConvertMarkdownToPdfTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.markdownToPdf(generalFile); + ResponseEntity response = controller.markdownToPdf(generalFile); assertEquals(HttpStatus.OK, response.getStatusCode()); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubControllerTest.java index a9d669c05..523989e2e 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToEpubControllerTest.java @@ -30,10 +30,11 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.model.api.converters.ConvertPdfToEpubRequest; @@ -48,14 +49,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class ConvertPDFToEpubControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -142,7 +144,7 @@ class ConvertPDFToEpubControllerTest { gu.when(() -> GeneralUtils.generateFilename("novel.pdf", "_convertedToEPUB.epub")) .thenReturn("novel_convertedToEPUB.epub"); - ResponseEntity response = controller.convertPdfToEpub(request); + ResponseEntity response = controller.convertPdfToEpub(request); List command = commandCaptor.getValue(); assertEquals(13, command.size()); @@ -234,7 +236,7 @@ class ConvertPDFToEpubControllerTest { gu.when(() -> GeneralUtils.generateFilename("story.pdf", "_convertedToEPUB.epub")) .thenReturn("story_convertedToEPUB.epub"); - ResponseEntity response = controller.convertPdfToEpub(request); + ResponseEntity response = controller.convertPdfToEpub(request); List command = commandCaptor.getValue(); assertTrue(command.stream().noneMatch(arg -> "--chapter".equals(arg))); @@ -319,7 +321,7 @@ class ConvertPDFToEpubControllerTest { gu.when(() -> GeneralUtils.generateFilename("book.pdf", "_convertedToAZW3.azw3")) .thenReturn("book_convertedToAZW3.azw3"); - ResponseEntity response = controller.convertPdfToEpub(request); + ResponseEntity response = controller.convertPdfToEpub(request); List command = commandCaptor.getValue(); assertEquals("ebook-convert", command.get(0)); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelControllerTest.java index ca7a7112f..3a37bd616 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToExcelControllerTest.java @@ -20,10 +20,10 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -81,7 +81,7 @@ class ConvertPDFToExcelControllerTest { Mockito.eq(true))) .thenReturn(List.of(1)); - ResponseEntity response = controller.pdfToExcel(request); + ResponseEntity response = controller.pdfToExcel(request); // tabula may or may not find tables in an empty page assertNotNull(response); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOfficeTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOfficeTest.java index d768ce1b2..0bedd8f72 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOfficeTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOfficeTest.java @@ -21,10 +21,11 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest; import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest; @@ -40,14 +41,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ConvertPDFToOfficeTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -85,8 +87,7 @@ class ConvertPDFToOfficeTest { request.setFileInput(pdfFile); request.setOutputFormat("pptx"); - ResponseEntity expectedResponse = - streamingOk("pptx-content".getBytes()); + ResponseEntity expectedResponse = streamingOk("pptx-content".getBytes()); try (MockedStatic mock = Mockito.mockStatic(PDFToFile.class, Mockito.CALLS_REAL_METHODS)) { @@ -115,8 +116,7 @@ class ConvertPDFToOfficeTest { realDoc.addPage(new org.apache.pdfbox.pdmodel.PDPage()); when(pdfDocumentFactory.load(pdfFile)).thenReturn(realDoc); - ResponseEntity expectedResponse = - streamingOk("text content".getBytes()); + ResponseEntity expectedResponse = streamingOk("text content".getBytes()); try (MockedStatic guMock = Mockito.mockStatic(GeneralUtils.class); MockedStatic wrMock = @@ -131,8 +131,7 @@ class ConvertPDFToOfficeTest { any(TempFile.class), anyString(), any(MediaType.class))) .thenReturn(expectedResponse); - ResponseEntity response = - controller.processPdfToRTForTXT(request); + ResponseEntity response = controller.processPdfToRTForTXT(request); assertSame(expectedResponse, response); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonControllerTest.java index 882c24926..a2427538b 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonControllerTest.java @@ -4,16 +4,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Files; import org.junit.jupiter.api.BeforeEach; @@ -22,11 +24,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.service.PdfJsonConversionService; import stirling.software.common.model.api.GeneralFile; @@ -58,10 +60,11 @@ class ConvertPdfJsonControllerTest { }); } - private static byte[] drainBody(ResponseEntity response) - throws IOException { + private static byte[] drainBody(ResponseEntity response) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream in = response.getBody().getInputStream()) { + in.transferTo(baos); + } return baos.toByteArray(); } @@ -82,10 +85,17 @@ class ConvertPdfJsonControllerTest { PDFFile request = new PDFFile(); request.setFileInput(pdfFile); - when(pdfJsonConversionService.convertPdfToJson(pdfFile, false)).thenReturn(jsonBytes); + // Service writes directly to the OutputStream passed by the controller + doAnswer( + inv -> { + OutputStream os = inv.getArgument(2, OutputStream.class); + os.write(jsonBytes); + return null; + }) + .when(pdfJsonConversionService) + .convertPdfToJson(eq(pdfFile), eq(false), any(OutputStream.class)); - ResponseEntity response = - controller.convertPdfToJson(request, false); + ResponseEntity response = controller.convertPdfToJson(request, false); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); @@ -100,12 +110,20 @@ class ConvertPdfJsonControllerTest { PDFFile request = new PDFFile(); request.setFileInput(pdfFile); - when(pdfJsonConversionService.convertPdfToJson(pdfFile, true)).thenReturn(jsonBytes); + doAnswer( + inv -> { + OutputStream os = inv.getArgument(2, OutputStream.class); + os.write(jsonBytes); + return null; + }) + .when(pdfJsonConversionService) + .convertPdfToJson(eq(pdfFile), eq(true), any(OutputStream.class)); - ResponseEntity response = controller.convertPdfToJson(request, true); + ResponseEntity response = controller.convertPdfToJson(request, true); assertEquals(HttpStatus.OK, response.getStatusCode()); - verify(pdfJsonConversionService).convertPdfToJson(pdfFile, true); + verify(pdfJsonConversionService) + .convertPdfToJson(eq(pdfFile), eq(true), any(OutputStream.class)); } @Test @@ -125,9 +143,16 @@ class ConvertPdfJsonControllerTest { GeneralFile request = new GeneralFile(); request.setFileInput(jsonFile); - when(pdfJsonConversionService.convertJsonToPdf(jsonFile)).thenReturn(pdfBytes); + doAnswer( + inv -> { + OutputStream os = inv.getArgument(1, OutputStream.class); + os.write(pdfBytes); + return null; + }) + .when(pdfJsonConversionService) + .convertJsonToPdf(eq(jsonFile), any(OutputStream.class)); - ResponseEntity response = controller.convertJsonToPdf(request); + ResponseEntity response = controller.convertJsonToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); @@ -150,10 +175,16 @@ class ConvertPdfJsonControllerTest { PDFFile request = new PDFFile(); request.setFileInput(pdfFile); - when(pdfJsonConversionService.extractDocumentMetadata(eq(pdfFile), any(String.class))) - .thenReturn(jsonBytes); + doAnswer( + inv -> { + OutputStream os = inv.getArgument(2, OutputStream.class); + os.write(jsonBytes); + return null; + }) + .when(pdfJsonConversionService) + .extractDocumentMetadata(eq(pdfFile), any(String.class), any(OutputStream.class)); - ResponseEntity response = controller.extractPdfMetadata(request); + ResponseEntity response = controller.extractPdfMetadata(request); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); @@ -175,9 +206,16 @@ class ConvertPdfJsonControllerTest { byte[] jsonBytes = "{\"content\":[]}".getBytes(); String jobId = "test-job-id"; - when(pdfJsonConversionService.extractSinglePage(jobId, 1)).thenReturn(jsonBytes); + doAnswer( + inv -> { + OutputStream os = inv.getArgument(2, OutputStream.class); + os.write(jsonBytes); + return null; + }) + .when(pdfJsonConversionService) + .extractSinglePage(eq(jobId), anyInt(), any(OutputStream.class)); - ResponseEntity response = controller.extractSinglePage(jobId, 1); + ResponseEntity response = controller.extractSinglePage(jobId, 1); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); @@ -188,9 +226,16 @@ class ConvertPdfJsonControllerTest { byte[] jsonBytes = "{\"fonts\":[]}".getBytes(); String jobId = "test-job-id"; - when(pdfJsonConversionService.extractPageFonts(jobId, 1)).thenReturn(jsonBytes); + doAnswer( + inv -> { + OutputStream os = inv.getArgument(2, OutputStream.class); + os.write(jsonBytes); + return null; + }) + .when(pdfJsonConversionService) + .extractPageFonts(eq(jobId), anyInt(), any(OutputStream.class)); - ResponseEntity response = controller.extractPageFonts(jobId, 1); + ResponseEntity response = controller.extractPageFonts(jobId, 1); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDFTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDFTest.java index 7d727cec2..503ea8e93 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDFTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertSvgToPDFTest.java @@ -21,10 +21,11 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.converters.SvgToPdfRequest; import stirling.software.SPDF.utils.SvgToPdf; @@ -37,14 +38,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ConvertSvgToPDFTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -75,7 +77,7 @@ class ConvertSvgToPDFTest { SvgToPdfRequest request = new SvgToPdfRequest(); request.setFileInput(null); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertTrue( @@ -88,7 +90,7 @@ class ConvertSvgToPDFTest { SvgToPdfRequest request = new SvgToPdfRequest(); request.setFileInput(new MockMultipartFile[0]); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -102,7 +104,7 @@ class ConvertSvgToPDFTest { request.setFileInput(new MockMultipartFile[] {txtFile}); request.setCombineIntoSinglePdf(false); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertTrue( @@ -118,7 +120,7 @@ class ConvertSvgToPDFTest { request.setFileInput(new MockMultipartFile[] {emptyFile}); request.setCombineIntoSinglePdf(false); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -141,7 +143,7 @@ class ConvertSvgToPDFTest { when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes)) .thenReturn(processedPdf); - ResponseEntity expectedResponse = streamingOk(processedPdf); + ResponseEntity expectedResponse = streamingOk(processedPdf); try (MockedStatic svgMock = Mockito.mockStatic(SvgToPdf.class); MockedStatic guMock = Mockito.mockStatic(GeneralUtils.class); @@ -159,7 +161,7 @@ class ConvertSvgToPDFTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -188,7 +190,7 @@ class ConvertSvgToPDFTest { when(pdfDocumentFactory.createNewBytesBasedOnOldDocument(combinedPdf)) .thenReturn(processedPdf); - ResponseEntity expectedResponse = streamingOk(processedPdf); + ResponseEntity expectedResponse = streamingOk(processedPdf); try (MockedStatic svgMock = Mockito.mockStatic(SvgToPdf.class); MockedStatic guMock = Mockito.mockStatic(GeneralUtils.class); @@ -206,7 +208,7 @@ class ConvertSvgToPDFTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -221,7 +223,7 @@ class ConvertSvgToPDFTest { request.setFileInput(new MockMultipartFile[] {nullNameFile}); request.setCombineIntoSinglePdf(false); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -238,7 +240,7 @@ class ConvertSvgToPDFTest { when(svgSanitizer.sanitize(svgContent)).thenThrow(new IOException("sanitization error")); - ResponseEntity response = controller.convertSvgToPdf(request); + ResponseEntity response = controller.convertSvgToPdf(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPdfTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPdfTest.java index c722b83c9..475d8fb69 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPdfTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPdfTest.java @@ -30,12 +30,13 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; import stirling.software.common.configuration.RuntimePathConfig; @@ -49,14 +50,15 @@ import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; public class ConvertWebsiteToPdfTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportControllerTest.java index 20f881e5a..af43dd10a 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/converters/PdfVectorExportControllerTest.java @@ -24,10 +24,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.model.api.converters.PdfVectorExportRequest; @@ -122,8 +122,7 @@ class PdfVectorExportControllerTest { PdfVectorExportRequest request = new PdfVectorExportRequest(); request.setFileInput(file); - ResponseEntity response = - controller.convertGhostscriptInputsToPdf(request); + ResponseEntity response = controller.convertGhostscriptInputsToPdf(request); assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK); assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF); @@ -140,13 +139,14 @@ class PdfVectorExportControllerTest { PdfVectorExportRequest request = new PdfVectorExportRequest(); request.setFileInput(file); - ResponseEntity response = - controller.convertGhostscriptInputsToPdf(request); + ResponseEntity response = controller.convertGhostscriptInputsToPdf(request); assertThat(response.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.OK); assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PDF); java.io.ByteArrayOutputStream baosVerify = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baosVerify); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baosVerify); + } assertThat(baosVerify.toByteArray()).contains(content); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/filters/FilterControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/filters/FilterControllerTest.java index df988f52f..1df2127a6 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/filters/FilterControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/filters/FilterControllerTest.java @@ -13,11 +13,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.PDFComparisonAndCount; import stirling.software.SPDF.model.api.PDFWithPageNums; @@ -32,14 +33,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class FilterControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -72,7 +74,7 @@ class FilterControllerTest { PDDocument mockDoc = mock(PDDocument.class); when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc); - ResponseEntity expectedResponse = streamingOk(new byte[] {1, 2, 3}); + ResponseEntity expectedResponse = streamingOk(new byte[] {1, 2, 3}); try (MockedStatic pdfUtilsMock = mockStatic(PdfUtils.class); MockedStatic webMock = mockStatic(WebResponseUtils.class)) { @@ -84,7 +86,7 @@ class FilterControllerTest { mockDoc, "test.pdf", tempFileManager)) .thenReturn(expectedResponse); - ResponseEntity result = filterController.containsText(request); + ResponseEntity result = filterController.containsText(request); assertEquals(HttpStatus.OK, result.getStatusCode()); assertArrayEquals(new byte[] {1, 2, 3}, drainBody(result)); @@ -104,7 +106,7 @@ class FilterControllerTest { try (MockedStatic pdfUtilsMock = mockStatic(PdfUtils.class)) { pdfUtilsMock.when(() -> PdfUtils.hasText(mockDoc, "all", "missing")).thenReturn(false); - ResponseEntity result = filterController.containsText(request); + ResponseEntity result = filterController.containsText(request); assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode()); assertNull(result.getBody()); @@ -122,7 +124,7 @@ class FilterControllerTest { PDDocument mockDoc = mock(PDDocument.class); when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDoc); - ResponseEntity expectedResponse = streamingOk(new byte[] {4, 5, 6}); + ResponseEntity expectedResponse = streamingOk(new byte[] {4, 5, 6}); try (MockedStatic pdfUtilsMock = mockStatic(PdfUtils.class); MockedStatic webMock = mockStatic(WebResponseUtils.class)) { @@ -134,7 +136,7 @@ class FilterControllerTest { mockDoc, "test.pdf", tempFileManager)) .thenReturn(expectedResponse); - ResponseEntity result = filterController.containsImage(request); + ResponseEntity result = filterController.containsImage(request); assertEquals(HttpStatus.OK, result.getStatusCode()); assertArrayEquals(new byte[] {4, 5, 6}, drainBody(result)); @@ -153,7 +155,7 @@ class FilterControllerTest { try (MockedStatic pdfUtilsMock = mockStatic(PdfUtils.class)) { pdfUtilsMock.when(() -> PdfUtils.hasImages(mockDoc, "1")).thenReturn(false); - ResponseEntity result = filterController.containsImage(request); + ResponseEntity result = filterController.containsImage(request); assertEquals(HttpStatus.NO_CONTENT, result.getStatusCode()); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/form/FormFillControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/form/FormFillControllerTest.java index 0db7f6051..7b2bfc9ce 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/form/FormFillControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/form/FormFillControllerTest.java @@ -22,10 +22,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.TempFile; @@ -37,14 +38,15 @@ import tools.jackson.databind.json.JsonMapper; @ExtendWith(MockitoExtension.class) @DisplayName("FormFillController Tests") class FormFillControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -232,8 +234,7 @@ class FormFillControllerTest { when(pdfDocumentFactory.load(eq(file))).thenReturn(doc); byte[] payload = "{\"field1\":\"value1\"}".getBytes(); - ResponseEntity response = - controller.fillForm(file, payload, false); + ResponseEntity response = controller.fillForm(file, payload, false); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isNotNull(); @@ -246,7 +247,7 @@ class FormFillControllerTest { PDDocument doc = createMinimalPdf(); when(pdfDocumentFactory.load(eq(file))).thenReturn(doc); - ResponseEntity response = controller.fillForm(file, null, false); + ResponseEntity response = controller.fillForm(file, null, false); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -287,7 +288,7 @@ class FormFillControllerTest { when(pdfDocumentFactory.load(eq(file))).thenReturn(doc); byte[] payload = "[\"field1\"]".getBytes(); - ResponseEntity response = controller.deleteFields(file, payload); + ResponseEntity response = controller.deleteFields(file, payload); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -323,8 +324,7 @@ class FormFillControllerTest { String json = "[{\"targetName\":\"f1\",\"name\":null,\"label\":null,\"type\":null," + "\"required\":null,\"multiSelect\":null,\"options\":null,\"defaultValue\":\"newVal\",\"tooltip\":null}]"; - ResponseEntity response = - controller.modifyFields(file, json.getBytes()); + ResponseEntity response = controller.modifyFields(file, json.getBytes()); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AttachmentControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AttachmentControllerTest.java index b3e4690cc..97b7e3541 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AttachmentControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AttachmentControllerTest.java @@ -17,12 +17,13 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.misc.AddAttachmentRequest; import stirling.software.SPDF.service.AttachmentServiceInterface; @@ -33,14 +34,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class AttachmentControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -100,8 +102,7 @@ class AttachmentControllerTest { List attachments = List.of(attachment1, attachment2); request.setAttachments(attachments); request.setFileInput(pdfFile); - ResponseEntity expectedResponse = - streamingOk("modified PDF content".getBytes()); + ResponseEntity expectedResponse = streamingOk("modified PDF content".getBytes()); when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument); when(pdfAttachmentService.addAttachment(mockDocument, attachments)) @@ -118,8 +119,7 @@ class AttachmentControllerTest { any(TempFileManager.class))) .thenReturn(expectedResponse); - ResponseEntity response = - attachmentController.addAttachments(request); + ResponseEntity response = attachmentController.addAttachments(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -134,8 +134,7 @@ class AttachmentControllerTest { List attachments = List.of(attachment1); request.setAttachments(attachments); request.setFileInput(pdfFile); - ResponseEntity expectedResponse = - streamingOk("modified PDF content".getBytes()); + ResponseEntity expectedResponse = streamingOk("modified PDF content".getBytes()); when(pdfDocumentFactory.load(request, false)).thenReturn(mockDocument); when(pdfAttachmentService.addAttachment(mockDocument, attachments)) @@ -152,8 +151,7 @@ class AttachmentControllerTest { any(TempFileManager.class))) .thenReturn(expectedResponse); - ResponseEntity response = - attachmentController.addAttachments(request); + ResponseEntity response = attachmentController.addAttachments(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AutoRenameControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AutoRenameControllerTest.java index 3cdf0a3a8..56f96ed6b 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AutoRenameControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/AutoRenameControllerTest.java @@ -23,11 +23,12 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -36,14 +37,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class AutoRenameControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -101,7 +103,7 @@ class AutoRenameControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.extractHeader(request); + ResponseEntity response = controller.extractHeader(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(drainBody(response)).isNotEmpty(); @@ -127,7 +129,7 @@ class AutoRenameControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.extractHeader(request); + ResponseEntity response = controller.extractHeader(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -140,7 +142,7 @@ class AutoRenameControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.extractHeader(request); + ResponseEntity response = controller.extractHeader(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -188,7 +190,7 @@ class AutoRenameControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.extractHeader(request); + ResponseEntity response = controller.extractHeader(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); // The largest font text should be used as title (URL-encoded in Content-Disposition) @@ -206,7 +208,7 @@ class AutoRenameControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.extractHeader(request); + ResponseEntity response = controller.extractHeader(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); // Should fallback to original filename since header is too long @@ -222,7 +224,7 @@ class AutoRenameControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.extractHeader(request); + ResponseEntity response = controller.extractHeader(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); // Special characters should be sanitized @@ -248,7 +250,7 @@ class AutoRenameControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.extractHeader(request); + ResponseEntity response = controller.extractHeader(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/DecompressPdfControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/DecompressPdfControllerTest.java index dd7541e63..413b96f66 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/DecompressPdfControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/DecompressPdfControllerTest.java @@ -23,11 +23,12 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.model.api.PDFFile; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -36,14 +37,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class DecompressPdfControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -97,7 +99,7 @@ class DecompressPdfControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.decompressPdf(request); + ResponseEntity response = controller.decompressPdf(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(drainBody(response)).isNotEmpty(); @@ -116,7 +118,7 @@ class DecompressPdfControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.decompressPdf(request); + ResponseEntity response = controller.decompressPdf(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(drainBody(response)).isNotEmpty(); @@ -147,7 +149,7 @@ class DecompressPdfControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.decompressPdf(request); + ResponseEntity response = controller.decompressPdf(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); String contentDisposition = response.getHeaders().getFirst("Content-Disposition"); @@ -183,7 +185,7 @@ class DecompressPdfControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.decompressPdf(request); + ResponseEntity response = controller.decompressPdf(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); try (PDDocument result = Loader.loadPDF(drainBody(response))) { @@ -200,7 +202,7 @@ class DecompressPdfControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.decompressPdf(request); + ResponseEntity response = controller.decompressPdf(request); assertThat(response.getBody()).isNotNull(); // Decompressed PDF should generally be larger or equal to compressed @@ -216,7 +218,7 @@ class DecompressPdfControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.decompressPdf(request); + ResponseEntity response = controller.decompressPdf(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/FlattenControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/FlattenControllerTest.java index d55314c21..a9a74e2f2 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/FlattenControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/FlattenControllerTest.java @@ -25,11 +25,12 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.misc.FlattenRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -38,14 +39,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class FlattenControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -98,7 +100,7 @@ class FlattenControllerTest { PDDocument doc = Loader.loadPDF(file.getBytes()); when(pdfDocumentFactory.load(file)).thenReturn(doc); - ResponseEntity response = controller.flatten(request); + ResponseEntity response = controller.flatten(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(drainBody(response)).isNotEmpty(); @@ -118,7 +120,7 @@ class FlattenControllerTest { when(doc.getDocumentCatalog()).thenReturn(catalog); when(catalog.getAcroForm()).thenReturn(null); - ResponseEntity response = controller.flatten(request); + ResponseEntity response = controller.flatten(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); verify(doc).close(); @@ -138,7 +140,7 @@ class FlattenControllerTest { when(doc.getDocumentCatalog()).thenReturn(catalog); when(catalog.getAcroForm()).thenReturn(form); - ResponseEntity response = controller.flatten(request); + ResponseEntity response = controller.flatten(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); verify(form).flatten(); @@ -170,7 +172,7 @@ class FlattenControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); - ResponseEntity response = controller.flatten(request); + ResponseEntity response = controller.flatten(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(drainBody(response)).isNotEmpty(); @@ -188,7 +190,7 @@ class FlattenControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); - ResponseEntity response = controller.flatten(request); + ResponseEntity response = controller.flatten(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -206,7 +208,7 @@ class FlattenControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); - ResponseEntity response = controller.flatten(request); + ResponseEntity response = controller.flatten(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } @@ -224,7 +226,7 @@ class FlattenControllerTest { when(pdfDocumentFactory.load(file)).thenReturn(doc); when(pdfDocumentFactory.createNewDocumentBasedOnOldDocument(doc)).thenReturn(newDoc); - ResponseEntity response = controller.flatten(request); + ResponseEntity response = controller.flatten(request); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java index 29324bbca..308518e8f 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java @@ -23,11 +23,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.misc.OverlayImageRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -37,14 +38,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class OverlayImageControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -108,8 +110,7 @@ class OverlayImageControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("result".getBytes()); + ResponseEntity expectedResponse = streamingOk("result".getBytes()); mockedWebResponse .when( () -> @@ -117,7 +118,7 @@ class OverlayImageControllerTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.overlayImage(request); + ResponseEntity response = controller.overlayImage(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -136,7 +137,7 @@ class OverlayImageControllerTest { when(pdfDocumentFactory.load(any(byte[].class))).thenThrow(new IOException("bad PDF")); - ResponseEntity response = controller.overlayImage(request); + ResponseEntity response = controller.overlayImage(request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -157,8 +158,7 @@ class OverlayImageControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("result".getBytes()); + ResponseEntity expectedResponse = streamingOk("result".getBytes()); mockedWebResponse .when( () -> @@ -166,7 +166,7 @@ class OverlayImageControllerTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.overlayImage(request); + ResponseEntity response = controller.overlayImage(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -189,8 +189,7 @@ class OverlayImageControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("result".getBytes()); + ResponseEntity expectedResponse = streamingOk("result".getBytes()); mockedWebResponse .when( () -> @@ -198,7 +197,7 @@ class OverlayImageControllerTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = controller.overlayImage(request); + ResponseEntity response = controller.overlayImage(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -221,8 +220,7 @@ class OverlayImageControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("result".getBytes()); + ResponseEntity expectedResponse = streamingOk("result".getBytes()); mockedWebResponse .when( () -> @@ -231,7 +229,7 @@ class OverlayImageControllerTest { .thenReturn(expectedResponse); // Should not throw - coordinates are passed to contentStream.drawImage - ResponseEntity response = controller.overlayImage(request); + ResponseEntity response = controller.overlayImage(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorControllerTest.java index a526f9172..1e5df278b 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ReplaceAndInvertColorControllerTest.java @@ -17,12 +17,13 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest; import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService; @@ -34,14 +35,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ReplaceAndInvertColorControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -96,7 +98,7 @@ class ReplaceAndInvertColorControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = streamingOk(resultBytes); + ResponseEntity expectedResponse = streamingOk(resultBytes); mockedWebResponse .when( () -> @@ -104,8 +106,7 @@ class ReplaceAndInvertColorControllerTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = - controller.replaceAndInvertColor(request); + ResponseEntity response = controller.replaceAndInvertColor(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -132,7 +133,7 @@ class ReplaceAndInvertColorControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = streamingOk(resultBytes); + ResponseEntity expectedResponse = streamingOk(resultBytes); mockedWebResponse .when( () -> @@ -140,8 +141,7 @@ class ReplaceAndInvertColorControllerTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = - controller.replaceAndInvertColor(request); + ResponseEntity response = controller.replaceAndInvertColor(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -166,7 +166,7 @@ class ReplaceAndInvertColorControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = streamingOk(resultBytes); + ResponseEntity expectedResponse = streamingOk(resultBytes); mockedWebResponse .when( () -> @@ -174,8 +174,7 @@ class ReplaceAndInvertColorControllerTest { any(TempFile.class), anyString())) .thenReturn(expectedResponse); - ResponseEntity response = - controller.replaceAndInvertColor(request); + ResponseEntity response = controller.replaceAndInvertColor(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -205,7 +204,7 @@ class ReplaceAndInvertColorControllerTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = streamingOk(resultBytes); + ResponseEntity expectedResponse = streamingOk(resultBytes); mockedWebResponse .when( () -> diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ShowJavascriptTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ShowJavascriptTest.java index abeffb7f4..3b637b353 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ShowJavascriptTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/ShowJavascriptTest.java @@ -19,11 +19,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.model.api.PDFFile; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -33,14 +34,15 @@ import stirling.software.common.util.WebResponseUtils; @ExtendWith(MockitoExtension.class) class ShowJavascriptTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -86,8 +88,7 @@ class ShowJavascriptTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("no js".getBytes()); + ResponseEntity expectedResponse = streamingOk("no js".getBytes()); mockedWebResponse .when( () -> @@ -97,7 +98,7 @@ class ShowJavascriptTest { eq(MediaType.TEXT_PLAIN))) .thenReturn(expectedResponse); - ResponseEntity response = showJavascript.extractHeader(request); + ResponseEntity response = showJavascript.extractHeader(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -129,8 +130,7 @@ class ShowJavascriptTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("js content".getBytes()); + ResponseEntity expectedResponse = streamingOk("js content".getBytes()); mockedWebResponse .when( () -> @@ -140,7 +140,7 @@ class ShowJavascriptTest { eq(MediaType.TEXT_PLAIN))) .thenReturn(expectedResponse); - ResponseEntity response = showJavascript.extractHeader(request); + ResponseEntity response = showJavascript.extractHeader(request); assertNotNull(response); mockedWebResponse.verify( @@ -160,8 +160,7 @@ class ShowJavascriptTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("no js".getBytes()); + ResponseEntity expectedResponse = streamingOk("no js".getBytes()); mockedWebResponse .when( () -> @@ -171,7 +170,7 @@ class ShowJavascriptTest { eq(MediaType.TEXT_PLAIN))) .thenReturn(expectedResponse); - ResponseEntity response = showJavascript.extractHeader(request); + ResponseEntity response = showJavascript.extractHeader(request); assertNotNull(response); mockedWebResponse.verify( @@ -200,8 +199,7 @@ class ShowJavascriptTest { try (MockedStatic mockedWebResponse = mockStatic(WebResponseUtils.class)) { - ResponseEntity expectedResponse = - streamingOk("no js".getBytes()); + ResponseEntity expectedResponse = streamingOk("no js".getBytes()); mockedWebResponse .when( () -> @@ -211,7 +209,7 @@ class ShowJavascriptTest { eq(MediaType.TEXT_PLAIN))) .thenReturn(expectedResponse); - ResponseEntity response = showJavascript.extractHeader(request); + ResponseEntity response = showJavascript.extractHeader(request); assertNotNull(response); mockedWebResponse.verify( diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsControllerTest.java index fb34b5318..f6790b550 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/UnlockPDFFormsControllerTest.java @@ -16,9 +16,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.model.api.PDFFile; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -67,7 +67,7 @@ class UnlockPDFFormsControllerTest { PDFFile file = new PDFFile(); file.setFileInput(mockPdfFile); - ResponseEntity response = controller.unlockPDFForms(file); + ResponseEntity response = controller.unlockPDFForms(file); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -85,7 +85,7 @@ class UnlockPDFFormsControllerTest { PDFFile file = new PDFFile(); file.setFileInput(mockPdfFile); - ResponseEntity response = controller.unlockPDFForms(file); + ResponseEntity response = controller.unlockPDFForms(file); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -99,7 +99,7 @@ class UnlockPDFFormsControllerTest { PDFFile file = new PDFFile(); file.setFileInput(mockPdfFile); - ResponseEntity response = controller.unlockPDFForms(file); + ResponseEntity response = controller.unlockPDFForms(file); // Controller catches exceptions and returns null assertNull(response); @@ -114,7 +114,7 @@ class UnlockPDFFormsControllerTest { PDFFile file = new PDFFile(); file.setFileInput(mockPdfFile); - ResponseEntity response = controller.unlockPDFForms(file); + ResponseEntity response = controller.unlockPDFForms(file); assertNotNull(response); String contentDisposition = response.getHeaders().getFirst("Content-Disposition"); @@ -133,7 +133,7 @@ class UnlockPDFFormsControllerTest { PDFFile file = new PDFFile(); file.setFileInput(mockPdfFile); - ResponseEntity response = controller.unlockPDFForms(file); + ResponseEntity response = controller.unlockPDFForms(file); assertNotNull(response); assertTrue(acroForm.getNeedAppearances()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java index e5d1a5255..2050e3c53 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java @@ -22,12 +22,13 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -36,14 +37,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) class CertSignControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -167,8 +169,7 @@ class CertSignControllerTest { request.setPageNumber(1); request.setShowLogo(false); - ResponseEntity response = - certSignController.signPDFWithCert(request); + ResponseEntity response = certSignController.signPDFWithCert(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -194,8 +195,7 @@ class CertSignControllerTest { request.setPageNumber(1); request.setShowLogo(false); - ResponseEntity response = - certSignController.signPDFWithCert(request); + ResponseEntity response = certSignController.signPDFWithCert(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -247,8 +247,7 @@ class CertSignControllerTest { request.setPageNumber(1); request.setShowLogo(false); - ResponseEntity response = - certSignController.signPDFWithCert(request); + ResponseEntity response = certSignController.signPDFWithCert(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -279,8 +278,7 @@ class CertSignControllerTest { request.setPageNumber(1); request.setShowLogo(false); - ResponseEntity response = - certSignController.signPDFWithCert(request); + ResponseEntity response = certSignController.signPDFWithCert(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -311,8 +309,7 @@ class CertSignControllerTest { request.setPageNumber(1); request.setShowLogo(false); - ResponseEntity response = - certSignController.signPDFWithCert(request); + ResponseEntity response = certSignController.signPDFWithCert(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -343,8 +340,7 @@ class CertSignControllerTest { request.setPageNumber(1); request.setShowLogo(false); - ResponseEntity response = - certSignController.signPDFWithCert(request); + ResponseEntity response = certSignController.signPDFWithCert(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -375,8 +371,7 @@ class CertSignControllerTest { request.setPageNumber(1); request.setShowLogo(false); - ResponseEntity response = - certSignController.signPDFWithCert(request); + ResponseEntity response = certSignController.signPDFWithCert(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/PasswordControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/PasswordControllerTest.java index d0d1b45fa..c56ac3dcf 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/PasswordControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/PasswordControllerTest.java @@ -27,12 +27,13 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.security.AddPasswordRequest; import stirling.software.SPDF.model.api.security.PDFPasswordRequest; @@ -44,14 +45,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class PasswordControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -120,8 +122,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyString())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.removePassword(request); + ResponseEntity response = passwordController.removePassword(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -145,8 +146,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyString())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.removePassword(request); + ResponseEntity response = passwordController.removePassword(request); assertNotNull(response); assertNotNull(response.getBody()); @@ -209,8 +209,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyString())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.removePassword(request); + ResponseEntity response = passwordController.removePassword(request); assertNotNull(response.getBody()); } @@ -228,8 +227,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyString())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.removePassword(request); + ResponseEntity response = passwordController.removePassword(request); assertNotNull(response.getBody()); } } @@ -257,8 +255,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -283,8 +280,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); } @@ -310,8 +306,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); } @@ -334,8 +329,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); } @@ -366,8 +360,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); } @@ -392,8 +385,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); } @@ -416,8 +408,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); } @@ -440,8 +431,7 @@ class PasswordControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - passwordController.addPassword(request); + ResponseEntity response = passwordController.addPassword(request); assertNotNull(response.getBody()); } } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RedactControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RedactControllerTest.java index caa03b3cf..584ad3744 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RedactControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RedactControllerTest.java @@ -44,10 +44,11 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest; import stirling.software.SPDF.model.api.security.RedactPdfRequest; @@ -60,14 +61,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class RedactControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -328,7 +330,7 @@ class RedactControllerTest { mock(org.apache.pdfbox.pdmodel.PDDocumentInformation.class); when(mockDocument.getDocumentInformation()).thenReturn(mockInfo); - ResponseEntity response = redactController.redactPdf(request); + ResponseEntity response = redactController.redactPdf(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -732,7 +734,7 @@ class RedactControllerTest { request.setConvertPDFToImage(convertToImage); try { - ResponseEntity response = redactController.redactPdf(request); + ResponseEntity response = redactController.redactPdf(request); if (expectSuccess && response != null) { assertNotNull(response); @@ -757,7 +759,7 @@ class RedactControllerTest { request.setConvertPDFToImage(convertToImage); try { - ResponseEntity response = redactController.redactPDF(request); + ResponseEntity response = redactController.redactPDF(request); if (response != null) { assertNotNull(response); @@ -948,7 +950,7 @@ class RedactControllerTest { request.setListOfText("test"); request.setRedactColor(null); - ResponseEntity response = redactController.redactPdf(request); + ResponseEntity response = redactController.redactPdf(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -972,7 +974,7 @@ class RedactControllerTest { ManualRedactPdfRequest request = createManualRedactPdfRequest(); request.setRedactions(null); - ResponseEntity response = redactController.redactPDF(request); + ResponseEntity response = redactController.redactPDF(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -984,7 +986,7 @@ class RedactControllerTest { ManualRedactPdfRequest request = createManualRedactPdfRequest(); request.setPageNumbers("100-200"); - ResponseEntity response = redactController.redactPDF(request); + ResponseEntity response = redactController.redactPDF(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -1449,7 +1451,7 @@ class RedactControllerTest { request.setUseRegex(false); request.setWholeWordSearch(false); - ResponseEntity response = redactController.redactPdf(request); + ResponseEntity response = redactController.redactPdf(request); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RemoveCertSignControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RemoveCertSignControllerTest.java index 1cf95e7e2..776a4459c 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RemoveCertSignControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/RemoveCertSignControllerTest.java @@ -27,12 +27,13 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.model.api.PDFFile; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -43,14 +44,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class RemoveCertSignControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -103,8 +105,7 @@ class RemoveCertSignControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - removeCertSignController.removeCertSignPDF(request); + ResponseEntity response = removeCertSignController.removeCertSignPDF(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -127,8 +128,7 @@ class RemoveCertSignControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - removeCertSignController.removeCertSignPDF(request); + ResponseEntity response = removeCertSignController.removeCertSignPDF(request); assertNotNull(response.getBody()); } @@ -159,8 +159,7 @@ class RemoveCertSignControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(pdfWithAcroForm)); - ResponseEntity response = - removeCertSignController.removeCertSignPDF(request); + ResponseEntity response = removeCertSignController.removeCertSignPDF(request); assertNotNull(response.getBody()); } @@ -190,8 +189,7 @@ class RemoveCertSignControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(pdfWithSig)); - ResponseEntity response = - removeCertSignController.removeCertSignPDF(request); + ResponseEntity response = removeCertSignController.removeCertSignPDF(request); assertNotNull(response.getBody()); } @@ -211,8 +209,7 @@ class RemoveCertSignControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - removeCertSignController.removeCertSignPDF(request); + ResponseEntity response = removeCertSignController.removeCertSignPDF(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -230,8 +227,7 @@ class RemoveCertSignControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - removeCertSignController.removeCertSignPDF(request); + ResponseEntity response = removeCertSignController.removeCertSignPDF(request); assertNotNull(response.getBody()); } @@ -261,8 +257,7 @@ class RemoveCertSignControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(multiPagePdf)); - ResponseEntity response = - removeCertSignController.removeCertSignPDF(request); + ResponseEntity response = removeCertSignController.removeCertSignPDF(request); assertNotNull(response.getBody()); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/SanitizeControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/SanitizeControllerTest.java index a806c1e29..857e10fdd 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/SanitizeControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/SanitizeControllerTest.java @@ -35,12 +35,13 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.security.SanitizePdfRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -51,14 +52,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class SanitizeControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -170,8 +172,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(jsBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -199,8 +200,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); } } @@ -229,8 +229,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(linkBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); } @@ -260,8 +259,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(metaBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); } @@ -287,8 +285,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); } } @@ -319,8 +316,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); } } @@ -349,8 +345,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(jsBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); } @@ -377,8 +372,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); } @@ -399,8 +393,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); } @@ -426,8 +419,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response.getBody()); } @@ -453,8 +445,7 @@ class SanitizeControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class), anyBoolean())) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - sanitizeController.sanitizePDF(request); + ResponseEntity response = sanitizeController.sanitizePDF(request); assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatusCode()); } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerTest.java index 3081bb8da..7eb9f38f5 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerTest.java @@ -28,12 +28,13 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.SPDF.model.api.security.AddWatermarkRequest; import stirling.software.common.service.CustomPDFDocumentFactory; @@ -44,14 +45,15 @@ import stirling.software.common.util.TempFileManager; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class WatermarkControllerTest { - private static ResponseEntity streamingOk(byte[] bytes) { - return ResponseEntity.ok(out -> out.write(bytes)); + private static ResponseEntity streamingOk(byte[] bytes) { + return ResponseEntity.ok(new ByteArrayResource(bytes)); } - private static byte[] drainBody(ResponseEntity response) - throws java.io.IOException { + private static byte[] drainBody(ResponseEntity response) throws java.io.IOException { java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); - response.getBody().writeTo(baos); + try (java.io.InputStream __in = response.getBody().getInputStream()) { + __in.transferTo(baos); + } return baos.toByteArray(); } @@ -122,8 +124,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); @@ -156,8 +157,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); } @@ -187,8 +187,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); } @@ -218,8 +217,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); } @@ -249,8 +247,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); } } @@ -386,8 +383,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(multiPagePdf)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); assertTrue(drainBody(response).length > 0); } @@ -428,8 +424,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); } @@ -456,8 +451,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); } @@ -487,8 +481,7 @@ class WatermarkControllerTest { when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer(inv -> Loader.loadPDF(simplePdfBytes)); - ResponseEntity response = - watermarkController.addWatermark(request); + ResponseEntity response = watermarkController.addWatermark(request); assertNotNull(response.getBody()); } } diff --git a/app/core/src/test/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdownTest.java b/app/core/src/test/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdownTest.java index b3edb4e45..b63e58b52 100644 --- a/app/core/src/test/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdownTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/model/api/converters/ConvertPDFToMarkdownTest.java @@ -3,7 +3,6 @@ package stirling.software.SPDF.model.api.converters; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -13,16 +12,16 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.MockedConstruction; import org.mockito.Mockito; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import stirling.software.common.util.PDFToFile; @@ -37,11 +36,11 @@ class ConvertPDFToMarkdownTest { @RestControllerAdvice static class GlobalErrorHandler { @ExceptionHandler(Exception.class) - ResponseEntity handle(Exception ex) { + ResponseEntity handle(Exception ex) { String message = ex.getMessage(); byte[] body = message != null ? message.getBytes(StandardCharsets.UTF_8) : new byte[0]; - StreamingResponseBody stream = out -> out.write(body); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(stream); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ByteArrayResource(body)); } } @@ -53,13 +52,12 @@ class ConvertPDFToMarkdownTest { Mockito.mockConstruction( PDFToFile.class, (mock, ctx) -> { - StreamingResponseBody stream = out -> out.write(md); when(mock.processPdfToMarkdown(any(MultipartFile.class))) .thenAnswer( inv -> ResponseEntity.ok() .header("Content-Type", "text/markdown") - .body(stream)); + .body(new ByteArrayResource(md))); })) { MockMvc mvc = mockMvc(); @@ -71,12 +69,10 @@ class ConvertPDFToMarkdownTest { "application/pdf", new byte[] {1, 2, 3}); - MvcResult asyncResult = - mvc.perform(multipart("/api/v1/convert/pdf/markdown").file(file)) - .andExpect(request().asyncStarted()) - .andReturn(); - - mvc.perform(asyncDispatch(asyncResult)) + // ResponseEntity is written synchronously on the request thread, + // so there is no async dispatch to wait for (unlike the old StreamingResponseBody + // path). + mvc.perform(multipart("/api/v1/convert/pdf/markdown").file(file)) .andExpect(status().isOk()) .andExpect(header().string("Content-Type", "text/markdown")) .andExpect(content().bytes(md)); diff --git a/app/core/src/test/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingServiceTest.java b/app/core/src/test/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingServiceTest.java index c0661c007..e5a6d2b3f 100644 --- a/app/core/src/test/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingServiceTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingServiceTest.java @@ -5,6 +5,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @@ -52,7 +54,11 @@ class PdfLazyLoadingServiceTest { Exception.class, () -> service.extractDocumentMetadata( - null, "job1", new HashMap<>(), new HashMap<>())); + null, + "job1", + new HashMap<>(), + new HashMap<>(), + new ByteArrayOutputStream())); } @Test @@ -89,7 +95,8 @@ class PdfLazyLoadingServiceTest { page -> null, cos -> cos, (doc, pageNum) -> new java.util.ArrayList<>(), - (doc, pageNum) -> new java.util.ArrayList<>())); + (doc, pageNum) -> new java.util.ArrayList<>(), + new ByteArrayOutputStream())); } @Test @@ -108,15 +115,26 @@ class PdfLazyLoadingServiceTest { metadata.setNumberOfPages(1); when(metadataService.extractMetadata(any())).thenReturn(metadata); when(metadataService.extractXmpMetadata(any())).thenReturn(null); - when(objectMapper.writeValueAsBytes(any())).thenReturn(new byte[] {'{', '}'}); + // Service now writes directly to the OutputStream using writeValue, not writeValueAsBytes. + // ObjectMapper.writeValue(OutputStream, Object) — the OutputStream is argument 0. + doAnswer( + inv -> { + OutputStream os = inv.getArgument(0, OutputStream.class); + os.write(new byte[] {'{', '}'}); + return null; + }) + .when(objectMapper) + .writeValue(any(OutputStream.class), any()); when(taskManager.addNote(any(), any())).thenReturn(true); Map fonts = new HashMap<>(); Map> pageFontResources = new HashMap<>(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] result = service.extractDocumentMetadata(file, "job1", fonts, pageFontResources); + service.extractDocumentMetadata(file, "job1", fonts, pageFontResources, out); - assertNotNull(result); + assertNotNull(out.toByteArray()); + assertEquals(2, out.toByteArray().length); verify(metadataService).extractMetadata(any()); } } diff --git a/testing/smoke_resource_endpoints.sh b/testing/smoke_resource_endpoints.sh new file mode 100644 index 000000000..c18047939 --- /dev/null +++ b/testing/smoke_resource_endpoints.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# Smoke test for endpoints converted from StreamingResponseBody to Resource. +# Verifies that: +# - Response status is 200 +# - Content-Length header matches actual body size +# - Body is a valid PDF (starts with %PDF-) +# - Multiple sequential requests all complete cleanly (no silent truncation) +# +# Run against a local bootRun on port 8088. + +set -u + +BASE_URL="${BASE_URL:-http://localhost:8088}" +TEST_PDF="${TEST_PDF:-testing/test_pdf_1.pdf}" +ITERATIONS="${ITERATIONS:-5}" +PASS=0 +FAIL=0 + +if [ ! -f "$TEST_PDF" ]; then + echo "ERROR: Test PDF not found at $TEST_PDF (run from repo root)" + exit 1 +fi + +# hit_endpoint