From 3eda85d00ed6b811a22b727ea8eae9de928a1d9e Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Tue, 24 Jun 2025 23:50:35 +0100 Subject: [PATCH] centralise temp-file handling and make path configurable --- Dockerfile | 12 +- Dockerfile.dev | 11 +- Dockerfile.fat | 12 +- Dockerfile.ultra-lite | 12 +- .../common/config/TempFileConfiguration.java | 22 ++- .../common/model/ApplicationProperties.java | 25 ++++ .../service/TempFileCleanupService.java | 96 +++++++++---- .../software/common/util/EmlToPdf.java | 15 +- .../software/common/util/FileToPdf.java | 134 +++++++++--------- .../software/common/util/GeneralUtils.java | 22 ++- .../software/common/util/TempDirectory.java | 44 ++++++ .../software/common/util/TempFile.java | 49 +++++++ .../software/common/util/TempFileManager.java | 46 +++--- .../software/common/util/TempFileUtil.java | 27 ---- .../service/TempFileCleanupServiceTest.java | 28 +++- .../software/common/util/FileToPdfTest.java | 20 ++- .../ee/KeygenLicenseVerifier.java | 3 + scripts/init-without-ocr.sh | 6 +- scripts/init.sh | 5 + .../api/converters/ConvertEmlToPDF.java | 5 +- .../api/converters/ConvertHtmlToPDF.java | 6 +- .../api/converters/ConvertMarkdownToPdf.java | 6 +- .../controller/api/misc/RepairController.java | 6 +- .../controller/api/misc/StampController.java | 7 +- .../src/main/resources/settings.yml.template | 9 ++ testing/test.sh | 10 +- 26 files changed, 434 insertions(+), 204 deletions(-) create mode 100644 common/src/main/java/stirling/software/common/util/TempDirectory.java create mode 100644 common/src/main/java/stirling/software/common/util/TempFile.java diff --git a/Dockerfile b/Dockerfile index cbb20111c..fd02b29f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,11 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \ PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \ UNO_PATH=/usr/lib/libreoffice/program \ URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \ - PATH=$PATH:/opt/venv/bin + PATH=$PATH:/opt/venv/bin \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf # JDK for app @@ -78,17 +82,17 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \ mv /usr/share/tessdata /usr/share/tessdata-original && \ - mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ + mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \ fc-cache -f -v && \ chmod +x /scripts/* && \ chmod +x /scripts/init.sh && \ # User permissions addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \ chown stirlingpdfuser:stirlingpdfgroup /app.jar EXPOSE 8080/tcp # Set user and run command ENTRYPOINT ["tini", "--", "/scripts/init.sh"] -CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"] +CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 37571373e..15de277b9 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -27,7 +27,11 @@ RUN apt-get update && apt-get install -y \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Setze die Environment Variable für setuptools -ENV SETUPTOOLS_USE_DISTUTILS=local +ENV SETUPTOOLS_USE_DISTUTILS=local \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf # Installation der benötigten Python-Pakete RUN python3 -m venv --system-site-packages /opt/venv \ @@ -40,8 +44,9 @@ ENV PATH="/opt/venv/bin:$PATH" COPY . /workspace -RUN adduser --disabled-password --gecos '' devuser \ - && chown -R devuser:devuser /home/devuser /workspace +RUN mkdir -p /tmp/stirling-pdf \ + && adduser --disabled-password --gecos '' devuser \ + && chown -R devuser:devuser /home/devuser /workspace /tmp/stirling-pdf RUN echo "devuser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/devuser \ && chmod 0440 /etc/sudoers.d/devuser diff --git a/Dockerfile.fat b/Dockerfile.fat index 682fac663..666ba98be 100644 --- a/Dockerfile.fat +++ b/Dockerfile.fat @@ -46,7 +46,11 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \ PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \ UNO_PATH=/usr/lib/libreoffice/program \ URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \ - PATH=$PATH:/opt/venv/bin + PATH=$PATH:/opt/venv/bin \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf # JDK for app @@ -92,16 +96,16 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \ mv /usr/share/tessdata /usr/share/tessdata-original && \ - mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ + mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \ fc-cache -f -v && \ chmod +x /scripts/* && \ chmod +x /scripts/init.sh && \ # User permissions addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \ chown stirlingpdfuser:stirlingpdfgroup /app.jar EXPOSE 8080/tcp # Set user and run command ENTRYPOINT ["tini", "--", "/scripts/init.sh"] -CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"] +CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"] diff --git a/Dockerfile.ultra-lite b/Dockerfile.ultra-lite index 83cd5e9c3..c4eb4ba46 100644 --- a/Dockerfile.ultra-lite +++ b/Dockerfile.ultra-lite @@ -11,7 +11,11 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \ JAVA_CUSTOM_OPTS="" \ PUID=1000 \ PGID=1000 \ - UMASK=022 + UMASK=022 \ + STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ + TMPDIR=/tmp/stirling-pdf \ + TEMP=/tmp/stirling-pdf \ + TMP=/tmp/stirling-pdf # Copy necessary files COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh @@ -35,10 +39,10 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et su-exec \ openjdk21-jre && \ # User permissions - mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \ + mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto /tmp/stirling-pdf && \ chmod +x /scripts/*.sh && \ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \ + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline /tmp/stirling-pdf && \ chown stirlingpdfuser:stirlingpdfgroup /app.jar # Set environment variables @@ -48,4 +52,4 @@ EXPOSE 8080/tcp # Run the application ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"] -CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] +CMD ["java", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=/tmp/stirling-pdf", "-jar", "/app.jar"] diff --git a/common/src/main/java/stirling/software/common/config/TempFileConfiguration.java b/common/src/main/java/stirling/software/common/config/TempFileConfiguration.java index 8654959e2..87abdbe60 100644 --- a/common/src/main/java/stirling/software/common/config/TempFileConfiguration.java +++ b/common/src/main/java/stirling/software/common/config/TempFileConfiguration.java @@ -3,16 +3,15 @@ package stirling.software.common.config; import java.nio.file.Files; import java.nio.file.Path; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import stirling.software.common.model.ApplicationProperties; import stirling.software.common.util.TempFileRegistry; /** @@ -21,17 +20,10 @@ import stirling.software.common.util.TempFileRegistry; */ @Slf4j @Configuration +@RequiredArgsConstructor public class TempFileConfiguration { - @Value("${stirling.tempfiles.directory:${java.io.tmpdir}/stirling-pdf}") - private String customTempDirectory; - - @Autowired - @Qualifier("machineType") - private String machineType; - - @Value("${stirling.tempfiles.prefix:stirling-pdf-}") - private String tempFilePrefix; + private final ApplicationProperties applicationProperties; /** * Create the TempFileRegistry bean. @@ -46,6 +38,10 @@ public class TempFileConfiguration { @PostConstruct public void initTempFileConfig() { try { + ApplicationProperties.TempFileManagement tempFiles = + applicationProperties.getSystem().getTempFileManagement(); + String customTempDirectory = tempFiles.getBaseTmpDir(); + // Create the temp directory if it doesn't exist Path tempDir = Path.of(customTempDirectory); if (!Files.exists(tempDir)) { @@ -55,7 +51,7 @@ public class TempFileConfiguration { log.info("Temporary file configuration initialized"); log.info("Using temp directory: {}", customTempDirectory); - log.info("Temp file prefix: {}", tempFilePrefix); + log.info("Temp file prefix: {}", tempFiles.getPrefix()); } catch (Exception e) { log.error("Failed to initialize temporary file configuration", e); } diff --git a/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index f5b67c866..0017fa34a 100644 --- a/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -292,6 +292,7 @@ public class ApplicationProperties { private Boolean enableUrlToPDF; private CustomPaths customPaths = new CustomPaths(); private String fileUploadLimit; + private TempFileManagement tempFileManagement = new TempFileManagement(); public boolean isAnalyticsEnabled() { return this.getEnableAnalytics() != null && this.getEnableAnalytics(); @@ -317,6 +318,30 @@ public class ApplicationProperties { } } + @Data + public static class TempFileManagement { + private String baseTmpDir = ""; + private String libreofficeDir = ""; + private String systemTempDir = ""; + private String prefix = "stirling-pdf-"; + private long maxAgeHours = 24; + private long cleanupIntervalMinutes = 30; + private boolean startupCleanup = true; + private boolean cleanupSystemTemp = false; + + public String getBaseTmpDir() { + return baseTmpDir != null && !baseTmpDir.isEmpty() + ? baseTmpDir + : java.lang.System.getProperty("java.io.tmpdir") + "/stirling-pdf"; + } + + public String getLibreofficeDir() { + return libreofficeDir != null && !libreofficeDir.isEmpty() + ? libreofficeDir + : getBaseTmpDir() + "/libreoffice"; + } + } + @Data public static class Datasource { private boolean enableCustomDatabase; diff --git a/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java b/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java index ab786bf2b..7a4e7d626 100644 --- a/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java +++ b/common/src/main/java/stirling/software/common/service/TempFileCleanupService.java @@ -13,12 +13,15 @@ import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import jakarta.annotation.PostConstruct; + +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import stirling.software.common.model.ApplicationProperties; import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.TempFileManager; import stirling.software.common.util.TempFileRegistry; @@ -29,30 +32,17 @@ import stirling.software.common.util.TempFileRegistry; */ @Slf4j @Service +@RequiredArgsConstructor public class TempFileCleanupService { private final TempFileRegistry registry; private final TempFileManager tempFileManager; - - @Value("${stirling.tempfiles.cleanup-interval-minutes:30}") - private long cleanupIntervalMinutes; - - @Value("${stirling.tempfiles.startup-cleanup:true}") - private boolean performStartupCleanup; + private final ApplicationProperties applicationProperties; @Autowired @Qualifier("machineType") private String machineType; - @Value("${stirling.tempfiles.system-temp-dir:/tmp}") - private String systemTempDir; - - @Value("${stirling.tempfiles.directory:/tmp/stirling-pdf}") - private String customTempDirectory; - - @Value("${stirling.tempfiles.libreoffice-dir:/tmp/stirling-pdf/libreoffice}") - private String libreOfficeTempDir; - // Maximum recursion depth for directory traversal private static final int MAX_RECURSION_DEPTH = 5; @@ -84,18 +74,18 @@ public class TempFileCleanupService { || fileName.startsWith("jetty-") || fileName.equals("proc") || fileName.equals("sys") - || fileName.equals("dev"); - - @Autowired - public TempFileCleanupService(TempFileRegistry registry, TempFileManager tempFileManager) { - this.registry = registry; - this.tempFileManager = tempFileManager; + || fileName.equals("dev") + || fileName.equals("hsperfdata_stirlingpdfuser") + || fileName.startsWith("hsperfdata_") + || fileName.equals(".pdfbox.cache"); + @PostConstruct + public void init() { // Create necessary directories ensureDirectoriesExist(); // Perform startup cleanup if enabled - if (performStartupCleanup) { + if (applicationProperties.getSystem().getTempFileManagement().isStartupCleanup()) { runStartupCleanup(); } } @@ -103,7 +93,11 @@ public class TempFileCleanupService { /** Ensure that all required temp directories exist */ private void ensureDirectoriesExist() { try { - // Create the main temp directory if specified + ApplicationProperties.TempFileManagement tempFiles = + applicationProperties.getSystem().getTempFileManagement(); + + // Create the main temp directory + String customTempDirectory = tempFiles.getBaseTmpDir(); if (customTempDirectory != null && !customTempDirectory.isEmpty()) { Path tempDir = Path.of(customTempDirectory); if (!Files.exists(tempDir)) { @@ -112,7 +106,8 @@ public class TempFileCleanupService { } } - // Create LibreOffice temp directory if specified + // Create LibreOffice temp directory + String libreOfficeTempDir = tempFiles.getLibreofficeDir(); if (libreOfficeTempDir != null && !libreOfficeTempDir.isEmpty()) { Path loTempDir = Path.of(libreOfficeTempDir); if (!Files.exists(loTempDir)) { @@ -127,7 +122,8 @@ public class TempFileCleanupService { /** Scheduled task to clean up old temporary files. Runs at the configured interval. */ @Scheduled( - fixedDelayString = "${stirling.tempfiles.cleanup-interval-minutes:60}", + fixedDelayString = + "#{applicationProperties.system.tempFileManagement.cleanupIntervalMinutes}", timeUnit = TimeUnit.MINUTES) public void scheduledCleanup() { log.info("Running scheduled temporary file cleanup"); @@ -151,6 +147,9 @@ public class TempFileCleanupService { } } + // Clean up PDFBox cache file + cleanupPDFBoxCache(); + // Clean up unregistered temp files based on our cleanup strategy boolean containerMode = isContainerMode(); int unregisteredDeletedCount = cleanupUnregisteredFiles(containerMode, true, maxAgeMillis); @@ -198,11 +197,26 @@ public class TempFileCleanupService { AtomicInteger totalDeletedCount = new AtomicInteger(0); try { - // Get all directories we need to clean - Path systemTempPath = getSystemTempPath(); - Path[] dirsToScan = { - systemTempPath, Path.of(customTempDirectory), Path.of(libreOfficeTempDir) - }; + ApplicationProperties.TempFileManagement tempFiles = + applicationProperties.getSystem().getTempFileManagement(); + Path[] dirsToScan; + if (tempFiles.isCleanupSystemTemp() + && tempFiles.getSystemTempDir() != null + && !tempFiles.getSystemTempDir().isEmpty()) { + Path systemTempPath = getSystemTempPath(); + dirsToScan = + new Path[] { + systemTempPath, + Path.of(tempFiles.getBaseTmpDir()), + Path.of(tempFiles.getLibreofficeDir()) + }; + } else { + dirsToScan = + new Path[] { + Path.of(tempFiles.getBaseTmpDir()), + Path.of(tempFiles.getLibreofficeDir()) + }; + } // Process each directory Arrays.stream(dirsToScan) @@ -254,6 +268,8 @@ public class TempFileCleanupService { /** Get the system temp directory path based on configuration or system property. */ private Path getSystemTempPath() { + String systemTempDir = + applicationProperties.getSystem().getTempFileManagement().getSystemTempDir(); if (systemTempDir != null && !systemTempDir.isEmpty()) { return Path.of(systemTempDir); } else { @@ -412,4 +428,22 @@ public class TempFileCleanupService { log.warn("Failed to clean up LibreOffice temp files", e); } } + + /** + * Clean up PDFBox cache file from user home directory. This cache file can grow large and + * should be periodically cleaned. + */ + private void cleanupPDFBoxCache() { + try { + Path userHome = Path.of(System.getProperty("user.home")); + Path pdfboxCache = userHome.resolve(".pdfbox.cache"); + + if (Files.exists(pdfboxCache)) { + Files.deleteIfExists(pdfboxCache); + log.debug("Cleaned up PDFBox cache file: {}", pdfboxCache); + } + } catch (IOException e) { + log.warn("Failed to clean up PDFBox cache file", e); + } + } } diff --git a/common/src/main/java/stirling/software/common/util/EmlToPdf.java b/common/src/main/java/stirling/software/common/util/EmlToPdf.java index b08bc16a5..11ac09114 100644 --- a/common/src/main/java/stirling/software/common/util/EmlToPdf.java +++ b/common/src/main/java/stirling/software/common/util/EmlToPdf.java @@ -131,7 +131,8 @@ public class EmlToPdf { byte[] emlBytes, String fileName, boolean disableSanitize, - stirling.software.common.service.CustomPDFDocumentFactory pdfDocumentFactory) + stirling.software.common.service.CustomPDFDocumentFactory pdfDocumentFactory, + TempFileManager tempFileManager) throws IOException, InterruptedException { validateEmlInput(emlBytes); @@ -150,7 +151,8 @@ public class EmlToPdf { // Convert HTML to PDF byte[] pdfBytes = - convertHtmlToPdf(weasyprintPath, request, htmlContent, disableSanitize); + convertHtmlToPdf( + weasyprintPath, request, htmlContent, disableSanitize, tempFileManager); // Attach files if available and requested if (shouldAttachFiles(emailContent, request)) { @@ -191,7 +193,8 @@ public class EmlToPdf { String weasyprintPath, EmlToPdfRequest request, String htmlContent, - boolean disableSanitize) + boolean disableSanitize, + TempFileManager tempFileManager) throws IOException, InterruptedException { stirling.software.common.model.api.converters.HTMLToPdfRequest htmlRequest = @@ -203,7 +206,8 @@ public class EmlToPdf { htmlRequest, htmlContent.getBytes(StandardCharsets.UTF_8), "email.html", - disableSanitize); + disableSanitize, + tempFileManager); } catch (IOException | InterruptedException e) { log.warn("Initial HTML to PDF conversion failed, trying with simplified HTML"); String simplifiedHtml = simplifyHtmlContent(htmlContent); @@ -212,7 +216,8 @@ public class EmlToPdf { htmlRequest, simplifiedHtml.getBytes(StandardCharsets.UTF_8), "email.html", - disableSanitize); + disableSanitize, + tempFileManager); } } diff --git a/common/src/main/java/stirling/software/common/util/FileToPdf.java b/common/src/main/java/stirling/software/common/util/FileToPdf.java index 8439b67a2..7b3765084 100644 --- a/common/src/main/java/stirling/software/common/util/FileToPdf.java +++ b/common/src/main/java/stirling/software/common/util/FileToPdf.java @@ -26,88 +26,92 @@ public class FileToPdf { HTMLToPdfRequest request, byte[] fileBytes, String fileName, - boolean disableSanitize) + boolean disableSanitize, + TempFileManager tempFileManager) throws IOException, InterruptedException { - Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - Path tempInputFile = null; - byte[] pdfBytes; - try { - if (fileName.endsWith(".html")) { - tempInputFile = Files.createTempFile("input_", ".html"); - String sanitizedHtml = - sanitizeHtmlContent( - new String(fileBytes, StandardCharsets.UTF_8), disableSanitize); - Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8)); - } else if (fileName.endsWith(".zip")) { - tempInputFile = Files.createTempFile("input_", ".zip"); - Files.write(tempInputFile, fileBytes); - sanitizeHtmlFilesInZip(tempInputFile, disableSanitize); - } else { - throw new IllegalArgumentException("Unsupported file format: " + fileName); - } + try (TempFile tempOutputFile = new TempFile(tempFileManager, ".pdf")) { + try (TempFile tempInputFile = + new TempFile(tempFileManager, fileName.endsWith(".html") ? ".html" : ".zip")) { - List command = new ArrayList<>(); - command.add(weasyprintPath); - command.add("-e"); - command.add("utf-8"); - command.add("-v"); - command.add("--pdf-forms"); - command.add(tempInputFile.toString()); - command.add(tempOutputFile.toString()); + if (fileName.endsWith(".html")) { + String sanitizedHtml = + sanitizeHtmlContent( + new String(fileBytes, StandardCharsets.UTF_8), disableSanitize); + Files.write( + tempInputFile.getPath(), + sanitizedHtml.getBytes(StandardCharsets.UTF_8)); + } else if (fileName.endsWith(".zip")) { + Files.write(tempInputFile.getPath(), fileBytes); + sanitizeHtmlFilesInZip( + tempInputFile.getPath(), disableSanitize, tempFileManager); + } else { + throw new IllegalArgumentException("Unsupported file format: " + fileName); + } - ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) - .runCommandWithOutputHandling(command); + List command = new ArrayList<>(); + command.add(weasyprintPath); + command.add("-e"); + command.add("utf-8"); + command.add("-v"); + command.add("--pdf-forms"); + command.add(tempInputFile.getAbsolutePath()); + command.add(tempOutputFile.getAbsolutePath()); - pdfBytes = Files.readAllBytes(tempOutputFile); - } catch (IOException e) { - pdfBytes = Files.readAllBytes(tempOutputFile); - if (pdfBytes.length < 1) { - throw e; - } - } finally { - Files.deleteIfExists(tempOutputFile); - Files.deleteIfExists(tempInputFile); - } + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) + .runCommandWithOutputHandling(command); - return pdfBytes; + byte[] pdfBytes = Files.readAllBytes(tempOutputFile.getPath()); + try { + return pdfBytes; + } catch (Exception e) { + pdfBytes = Files.readAllBytes(tempOutputFile.getPath()); + if (pdfBytes.length < 1) { + throw e; + } + return pdfBytes; + } + } // tempInputFile auto-closed + } // tempOutputFile auto-closed } private static String sanitizeHtmlContent(String htmlContent, boolean disableSanitize) { return (!disableSanitize) ? CustomHtmlSanitizer.sanitize(htmlContent) : htmlContent; } - private static void sanitizeHtmlFilesInZip(Path zipFilePath, boolean disableSanitize) + private static void sanitizeHtmlFilesInZip( + Path zipFilePath, boolean disableSanitize, TempFileManager tempFileManager) throws IOException { - Path tempUnzippedDir = Files.createTempDirectory("unzipped_"); - try (ZipInputStream zipIn = - ZipSecurity.createHardenedInputStream( - new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) { - ZipEntry entry = zipIn.getNextEntry(); - while (entry != null) { - Path filePath = tempUnzippedDir.resolve(sanitizeZipFilename(entry.getName())); - if (!entry.isDirectory()) { - Files.createDirectories(filePath.getParent()); - if (entry.getName().toLowerCase().endsWith(".html") - || entry.getName().toLowerCase().endsWith(".htm")) { - String content = new String(zipIn.readAllBytes(), StandardCharsets.UTF_8); - String sanitizedContent = sanitizeHtmlContent(content, disableSanitize); - Files.write(filePath, sanitizedContent.getBytes(StandardCharsets.UTF_8)); - } else { - Files.copy(zipIn, filePath); + try (TempDirectory tempUnzippedDir = new TempDirectory(tempFileManager)) { + try (ZipInputStream zipIn = + ZipSecurity.createHardenedInputStream( + new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) { + ZipEntry entry = zipIn.getNextEntry(); + while (entry != null) { + Path filePath = + tempUnzippedDir.getPath().resolve(sanitizeZipFilename(entry.getName())); + if (!entry.isDirectory()) { + Files.createDirectories(filePath.getParent()); + if (entry.getName().toLowerCase().endsWith(".html") + || entry.getName().toLowerCase().endsWith(".htm")) { + String content = + new String(zipIn.readAllBytes(), StandardCharsets.UTF_8); + String sanitizedContent = sanitizeHtmlContent(content, disableSanitize); + Files.write( + filePath, sanitizedContent.getBytes(StandardCharsets.UTF_8)); + } else { + Files.copy(zipIn, filePath); + } } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); } - zipIn.closeEntry(); - entry = zipIn.getNextEntry(); } - } - // Repack the sanitized files - zipDirectory(tempUnzippedDir, zipFilePath); - - // Clean up - deleteDirectory(tempUnzippedDir); + // Repack the sanitized files + zipDirectory(tempUnzippedDir.getPath(), zipFilePath); + } // tempUnzippedDir auto-cleaned } private static void zipDirectory(Path sourceDir, Path zipFilePath) throws IOException { diff --git a/common/src/main/java/stirling/software/common/util/GeneralUtils.java b/common/src/main/java/stirling/software/common/util/GeneralUtils.java index 87496294d..ddbec92e0 100644 --- a/common/src/main/java/stirling/software/common/util/GeneralUtils.java +++ b/common/src/main/java/stirling/software/common/util/GeneralUtils.java @@ -34,7 +34,27 @@ import stirling.software.common.configuration.InstallationPathConfig; public class GeneralUtils { public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { - File tempFile = Files.createTempFile("temp", null).toFile(); + String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY"); + if (customTempDir == null || customTempDir.isEmpty()) { + customTempDir = System.getProperty("stirling.tempfiles.directory"); + } + + File tempFile; + + if (customTempDir != null && !customTempDir.isEmpty()) { + Path tempDir = Path.of(customTempDir); + if (!Files.exists(tempDir)) { + Files.createDirectories(tempDir); + } + tempFile = Files.createTempFile(tempDir, "stirling-pdf-", null).toFile(); + } else { + Path tempDir = Path.of(System.getProperty("java.io.tmpdir"), "stirling-pdf"); + if (!Files.exists(tempDir)) { + Files.createDirectories(tempDir); + } + tempFile = Files.createTempFile(tempDir, "stirling-pdf-", null).toFile(); + } + try (InputStream inputStream = multipartFile.getInputStream(); FileOutputStream outputStream = new FileOutputStream(tempFile)) { diff --git a/common/src/main/java/stirling/software/common/util/TempDirectory.java b/common/src/main/java/stirling/software/common/util/TempDirectory.java new file mode 100644 index 000000000..cd7036d68 --- /dev/null +++ b/common/src/main/java/stirling/software/common/util/TempDirectory.java @@ -0,0 +1,44 @@ +package stirling.software.common.util; + +import java.io.IOException; +import java.nio.file.Path; + +import lombok.extern.slf4j.Slf4j; + +/** + * A wrapper class for a temporary directory that implements AutoCloseable. Can be used with + * try-with-resources for automatic cleanup. + */ +@Slf4j +public class TempDirectory implements AutoCloseable { + + private final TempFileManager manager; + private final Path directory; + + public TempDirectory(TempFileManager manager) throws IOException { + this.manager = manager; + this.directory = manager.createTempDirectory(); + } + + public Path getPath() { + return directory; + } + + public String getAbsolutePath() { + return directory.toAbsolutePath().toString(); + } + + public boolean exists() { + return java.nio.file.Files.exists(directory); + } + + @Override + public void close() { + manager.deleteTempDirectory(directory); + } + + @Override + public String toString() { + return "TempDirectory{" + directory.toAbsolutePath() + "}"; + } +} diff --git a/common/src/main/java/stirling/software/common/util/TempFile.java b/common/src/main/java/stirling/software/common/util/TempFile.java new file mode 100644 index 000000000..db859c431 --- /dev/null +++ b/common/src/main/java/stirling/software/common/util/TempFile.java @@ -0,0 +1,49 @@ +package stirling.software.common.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +import lombok.extern.slf4j.Slf4j; + +/** + * A wrapper class for a temporary file that implements AutoCloseable. Can be used with + * try-with-resources for automatic cleanup. + */ +@Slf4j +public class TempFile implements AutoCloseable { + + private final TempFileManager manager; + private final File file; + + public TempFile(TempFileManager manager, String suffix) throws IOException { + this.manager = manager; + this.file = manager.createTempFile(suffix); + } + + public File getFile() { + return file; + } + + public Path getPath() { + return file.toPath(); + } + + public String getAbsolutePath() { + return file.getAbsolutePath(); + } + + public boolean exists() { + return file.exists(); + } + + @Override + public void close() { + manager.deleteTempFile(file); + } + + @Override + public String toString() { + return "TempFile{" + file.getAbsolutePath() + "}"; + } +} diff --git a/common/src/main/java/stirling/software/common/util/TempFileManager.java b/common/src/main/java/stirling/software/common/util/TempFileManager.java index c20594864..867931f8b 100644 --- a/common/src/main/java/stirling/software/common/util/TempFileManager.java +++ b/common/src/main/java/stirling/software/common/util/TempFileManager.java @@ -8,39 +8,25 @@ import java.time.Duration; import java.util.Set; import java.util.UUID; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import stirling.software.common.model.ApplicationProperties; + /** * Service for managing temporary files in Stirling-PDF. Provides methods for creating, tracking, * and cleaning up temporary files. */ @Slf4j @Service +@RequiredArgsConstructor public class TempFileManager { private final TempFileRegistry registry; - - @Value("${stirling.tempfiles.prefix:stirling-pdf-}") - private String tempFilePrefix; - - @Value("${stirling.tempfiles.directory:}") - private String customTempDirectory; - - @Value("${stirling.tempfiles.libreoffice-dir:}") - private String libreOfficeTempDir; - - @Value("${stirling.tempfiles.max-age-hours:24}") - private long maxAgeHours; - - @Autowired - public TempFileManager(TempFileRegistry registry) { - this.registry = registry; - } + private final ApplicationProperties applicationProperties; /** * Create a temporary file with the Stirling-PDF prefix. The file is automatically registered @@ -51,15 +37,18 @@ public class TempFileManager { * @throws IOException If an I/O error occurs */ public File createTempFile(String suffix) throws IOException { + ApplicationProperties.TempFileManagement tempFiles = + applicationProperties.getSystem().getTempFileManagement(); Path tempFilePath; + String customTempDirectory = tempFiles.getBaseTmpDir(); if (customTempDirectory != null && !customTempDirectory.isEmpty()) { Path tempDir = Path.of(customTempDirectory); if (!Files.exists(tempDir)) { Files.createDirectories(tempDir); } - tempFilePath = Files.createTempFile(tempDir, tempFilePrefix, suffix); + tempFilePath = Files.createTempFile(tempDir, tempFiles.getPrefix(), suffix); } else { - tempFilePath = Files.createTempFile(tempFilePrefix, suffix); + tempFilePath = Files.createTempFile(tempFiles.getPrefix(), suffix); } File tempFile = tempFilePath.toFile(); return registry.register(tempFile); @@ -73,15 +62,18 @@ public class TempFileManager { * @throws IOException If an I/O error occurs */ public Path createTempDirectory() throws IOException { + ApplicationProperties.TempFileManagement tempFiles = + applicationProperties.getSystem().getTempFileManagement(); Path tempDirPath; + String customTempDirectory = tempFiles.getBaseTmpDir(); if (customTempDirectory != null && !customTempDirectory.isEmpty()) { Path tempDir = Path.of(customTempDirectory); if (!Files.exists(tempDir)) { Files.createDirectories(tempDir); } - tempDirPath = Files.createTempDirectory(tempDir, tempFilePrefix); + tempDirPath = Files.createTempDirectory(tempDir, tempFiles.getPrefix()); } else { - tempDirPath = Files.createTempDirectory(tempFilePrefix); + tempDirPath = Files.createTempDirectory(tempFiles.getPrefix()); } return registry.registerDirectory(tempDirPath); } @@ -202,6 +194,8 @@ public class TempFileManager { * @return Maximum age in milliseconds */ public long getMaxAgeMillis() { + long maxAgeHours = + applicationProperties.getSystem().getTempFileManagement().getMaxAgeHours(); return Duration.ofHours(maxAgeHours).toMillis(); } @@ -213,6 +207,8 @@ public class TempFileManager { * @return A unique temporary file name */ public String generateTempFileName(String type, String extension) { + String tempFilePrefix = + applicationProperties.getSystem().getTempFileManagement().getPrefix(); String uuid = UUID.randomUUID().toString().substring(0, 8); return tempFilePrefix + type + "-" + uuid + "." + extension; } @@ -225,7 +221,11 @@ public class TempFileManager { * @throws IOException If directory creation fails */ public Path registerLibreOfficeTempDir() throws IOException { + ApplicationProperties.TempFileManagement tempFiles = + applicationProperties.getSystem().getTempFileManagement(); Path loTempDir; + String libreOfficeTempDir = tempFiles.getLibreofficeDir(); + String customTempDirectory = tempFiles.getBaseTmpDir(); // First check if explicitly configured if (libreOfficeTempDir != null && !libreOfficeTempDir.isEmpty()) { diff --git a/common/src/main/java/stirling/software/common/util/TempFileUtil.java b/common/src/main/java/stirling/software/common/util/TempFileUtil.java index 2a739deda..2588b9ebb 100644 --- a/common/src/main/java/stirling/software/common/util/TempFileUtil.java +++ b/common/src/main/java/stirling/software/common/util/TempFileUtil.java @@ -17,33 +17,6 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class TempFileUtil { - /** - * A wrapper class for a temporary file that implements AutoCloseable. Can be used with - * try-with-resources for automatic cleanup. - */ - public static class TempFile implements AutoCloseable { - private final TempFileManager manager; - private final File file; - - public TempFile(TempFileManager manager, String suffix) throws IOException { - this.manager = manager; - this.file = manager.createTempFile(suffix); - } - - public File getFile() { - return file; - } - - public Path getPath() { - return file.toPath(); - } - - @Override - public void close() { - manager.deleteTempFile(file); - } - } - /** * A collection of temporary files that implements AutoCloseable. All files in the collection * are cleaned up when close() is called. diff --git a/common/src/test/java/stirling/software/common/service/TempFileCleanupServiceTest.java b/common/src/test/java/stirling/software/common/service/TempFileCleanupServiceTest.java index 1e1633874..009c00860 100644 --- a/common/src/test/java/stirling/software/common/service/TempFileCleanupServiceTest.java +++ b/common/src/test/java/stirling/software/common/service/TempFileCleanupServiceTest.java @@ -26,6 +26,7 @@ import org.mockito.MockedStatic; import org.mockito.MockitoAnnotations; import org.springframework.test.util.ReflectionTestUtils; +import stirling.software.common.model.ApplicationProperties; import stirling.software.common.util.TempFileManager; import stirling.software.common.util.TempFileRegistry; @@ -43,6 +44,15 @@ public class TempFileCleanupServiceTest { @Mock private TempFileManager tempFileManager; + @Mock + private ApplicationProperties applicationProperties; + + @Mock + private ApplicationProperties.System system; + + @Mock + private ApplicationProperties.TempFileManagement tempFileManagement; + @InjectMocks private TempFileCleanupService cleanupService; @@ -63,12 +73,18 @@ public class TempFileCleanupServiceTest { Files.createDirectories(customTempDir); Files.createDirectories(libreOfficeTempDir); - // Configure service with our test directories - ReflectionTestUtils.setField(cleanupService, "systemTempDir", systemTempDir.toString()); - ReflectionTestUtils.setField(cleanupService, "customTempDirectory", customTempDir.toString()); - ReflectionTestUtils.setField(cleanupService, "libreOfficeTempDir", libreOfficeTempDir.toString()); - ReflectionTestUtils.setField(cleanupService, "machineType", "Standard"); // Regular mode - ReflectionTestUtils.setField(cleanupService, "performStartupCleanup", false); // Disable auto-startup cleanup + // Configure ApplicationProperties mocks + when(applicationProperties.getSystem()).thenReturn(system); + when(system.getTempFileManagement()).thenReturn(tempFileManagement); + when(tempFileManagement.getBaseTmpDir()).thenReturn(customTempDir.toString()); + when(tempFileManagement.getLibreofficeDir()).thenReturn(libreOfficeTempDir.toString()); + when(tempFileManagement.getSystemTempDir()).thenReturn(systemTempDir.toString()); + when(tempFileManagement.isStartupCleanup()).thenReturn(false); + when(tempFileManagement.isCleanupSystemTemp()).thenReturn(false); + when(tempFileManagement.getCleanupIntervalMinutes()).thenReturn(30L); + + // Set machineType using reflection (still needed for this field) + ReflectionTestUtils.setField(cleanupService, "machineType", "Standard"); when(tempFileManager.getMaxAgeMillis()).thenReturn(3600000L); // 1 hour } diff --git a/common/src/test/java/stirling/software/common/util/FileToPdfTest.java b/common/src/test/java/stirling/software/common/util/FileToPdfTest.java index a897e887b..f1df1cf25 100644 --- a/common/src/test/java/stirling/software/common/util/FileToPdfTest.java +++ b/common/src/test/java/stirling/software/common/util/FileToPdfTest.java @@ -3,7 +3,11 @@ package stirling.software.common.util; 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.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.anyString; +import java.io.File; import java.io.IOException; import org.junit.jupiter.api.Test; @@ -22,14 +26,24 @@ public class FileToPdfTest { byte[] fileBytes = new byte[0]; // Sample file bytes (empty input) String fileName = "test.html"; // Sample file name indicating an HTML file boolean disableSanitize = false; // Flag to control sanitization + TempFileManager tempFileManager = mock(TempFileManager.class); // Mock TempFileManager + + // Mock the temp file creation to return real temp files + try { + when(tempFileManager.createTempFile(anyString())) + .thenReturn(File.createTempFile("test", ".pdf")) + .thenReturn(File.createTempFile("test", ".html")); + } catch (IOException e) { + throw new RuntimeException(e); + } - // Expect an IOException to be thrown due to empty input + // Expect an IOException to be thrown due to empty input or invalid weasyprint path Throwable thrown = assertThrows( - IOException.class, + Exception.class, () -> FileToPdf.convertHtmlToPdf( - "/path/", request, fileBytes, fileName, disableSanitize)); + "/path/", request, fileBytes, fileName, disableSanitize, tempFileManager)); assertNotNull(thrown); } diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java b/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java index 969385a33..8a4dd7d3f 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/KeygenLicenseVerifier.java @@ -65,6 +65,9 @@ public class KeygenLicenseVerifier { } public License verifyLicense(String licenseKeyOrCert) { + if (!applicationProperties.getPremium().isEnabled()) { + return License.NORMAL; + } License license; LicenseContext context = new LicenseContext(); diff --git a/scripts/init-without-ocr.sh b/scripts/init-without-ocr.sh index 934c995a3..aade064bb 100644 --- a/scripts/init-without-ocr.sh +++ b/scripts/init-without-ocr.sh @@ -28,9 +28,11 @@ if [[ -n "$LANGS" ]]; then fi echo "Setting permissions and ownership for necessary directories..." +# Ensure temp directory exists and has correct permissions +mkdir -p /tmp/stirling-pdf || true # Attempt to change ownership of directories and files -if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar; then - chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar || true +if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf /app.jar; then + chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf /app.jar || true # If chown succeeds, execute the command as stirlingpdfuser exec su-exec stirlingpdfuser "$@" else diff --git a/scripts/init.sh b/scripts/init.sh index f839da2bd..4cde7db46 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -28,4 +28,9 @@ if [[ -n "$TESSERACT_LANGS" ]]; then done fi +# Ensure temp directory exists with correct permissions before running main init +mkdir -p /tmp/stirling-pdf || true +chown -R stirlingpdfuser:stirlingpdfgroup /tmp/stirling-pdf || true +chmod -R 755 /tmp/stirling-pdf || true + /scripts/init-without-ocr.sh "$@" \ No newline at end of file diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java index 32aedf57c..33d51a2a1 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEmlToPDF.java @@ -24,6 +24,7 @@ import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.model.api.converters.EmlToPdfRequest; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.EmlToPdf; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -35,6 +36,7 @@ public class ConvertEmlToPDF { private final CustomPDFDocumentFactory pdfDocumentFactory; private final RuntimePathConfig runtimePathConfig; + private final TempFileManager tempFileManager; @PostMapping(consumes = "multipart/form-data", value = "/eml/pdf") @Operation( @@ -102,7 +104,8 @@ public class ConvertEmlToPDF { fileBytes, originalFilename, false, - pdfDocumentFactory); + pdfDocumentFactory, + tempFileManager); if (pdfBytes == null || pdfBytes.length == 0) { log.error("PDF conversion failed - empty output for {}", originalFilename); diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index cdd9bc1a7..4eff3a872 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -18,6 +18,7 @@ import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.api.converters.HTMLToPdfRequest; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.FileToPdf; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -32,6 +33,8 @@ public class ConvertHtmlToPDF { private final RuntimePathConfig runtimePathConfig; + private final TempFileManager tempFileManager; + @PostMapping(consumes = "multipart/form-data", value = "/html/pdf") @Operation( summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", @@ -62,7 +65,8 @@ public class ConvertHtmlToPDF { request, fileInput.getBytes(), originalFilename, - disableSanitize); + disableSanitize, + tempFileManager); pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes); diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java index 98f96fbdb..1bf2d94a8 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java @@ -28,6 +28,7 @@ import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.api.GeneralFile; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.FileToPdf; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -41,6 +42,8 @@ public class ConvertMarkdownToPdf { private final ApplicationProperties applicationProperties; private final RuntimePathConfig runtimePathConfig; + private final TempFileManager tempFileManager; + @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") @Operation( summary = "Convert a Markdown file to PDF", @@ -82,7 +85,8 @@ public class ConvertMarkdownToPdf { null, htmlContent.getBytes(), "converted.html", - disableSanitize); + disableSanitize, + tempFileManager); pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes); String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java index 6847f2222..b8c347ef1 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java @@ -21,8 +21,8 @@ import stirling.software.common.model.api.PDFFile; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.ProcessExecutor; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; +import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; -import stirling.software.common.util.TempFileUtil; import stirling.software.common.util.WebResponseUtils; @RestController @@ -45,8 +45,8 @@ public class RepairController { throws IOException, InterruptedException { MultipartFile inputFile = file.getFileInput(); - // Use TempFileUtil.TempFile with try-with-resources for automatic cleanup - try (TempFileUtil.TempFile tempFile = new TempFileUtil.TempFile(tempFileManager, ".pdf")) { + // Use TempFile with try-with-resources for automatic cleanup + try (TempFile tempFile = new TempFile(tempFileManager, ".pdf")) { // Save the uploaded file to the temporary location inputFile.transferTo(tempFile.getFile()); diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java index 963bd0214..0defd510a 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java @@ -39,8 +39,8 @@ import lombok.RequiredArgsConstructor; import stirling.software.SPDF.model.api.misc.AddStampRequest; import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; -import stirling.software.common.util.TempFileUtil; import stirling.software.common.util.WebResponseUtils; @RestController @@ -191,9 +191,8 @@ public class StampController { ClassPathResource classPathResource = new ClassPathResource(resourceDir); String fileExtension = resourceDir.substring(resourceDir.lastIndexOf(".")); - // Use TempFileUtil.TempFile with try-with-resources for automatic cleanup - try (TempFileUtil.TempFile tempFileWrapper = - new TempFileUtil.TempFile(tempFileManager, fileExtension)) { + // Use TempFile with try-with-resources for automatic cleanup + try (TempFile tempFileWrapper = new TempFile(tempFileManager, fileExtension)) { File tempFile = tempFileWrapper.getFile(); try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) { diff --git a/stirling-pdf/src/main/resources/settings.yml.template b/stirling-pdf/src/main/resources/settings.yml.template index d651eff9f..d45b8482b 100644 --- a/stirling-pdf/src/main/resources/settings.yml.template +++ b/stirling-pdf/src/main/resources/settings.yml.template @@ -125,6 +125,15 @@ system: weasyprint: '' #Defaults to /opt/venv/bin/weasyprint unoconvert: '' #Defaults to /opt/venv/bin/unoconvert fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB". + tempFileManagement: + baseTmpDir: '' # Defaults to java.io.tmpdir/stirling-pdf + libreofficeDir: '' # Defaults to tempFileManagement.baseTmpDir/libreoffice + systemTempDir: '' # Only used if cleanupSystemTemp is true + prefix: stirling-pdf- # Prefix for temp file names + maxAgeHours: 24 # Maximum age in hours before temp files are cleaned up + cleanupIntervalMinutes: 30 # How often to run cleanup (in minutes) + startupCleanup: true # Clean up old temp files on startup + cleanupSystemTemp: false # Whether to clean broader system temp directory ui: appName: '' # application's visible name diff --git a/testing/test.sh b/testing/test.sh index 4658edeb5..94370807b 100644 --- a/testing/test.sh +++ b/testing/test.sh @@ -55,10 +55,12 @@ capture_file_list() { -not -path '/config/*' \ -not -path '/logs/*' \ -not -path '*/home/stirlingpdfuser/.config/libreoffice/*' \ - -not -path '*/tmp/PDFBox*' \ + -not -path '*/home/stirlingpdfuser/.pdfbox.cache' \ + -not -path '*/tmp/stirling-pdf/PDFBox*' \ + -not -path '*/tmp/stirling-pdf/hsperfdata_stirlingpdfuser/*' \ -not -path '*/tmp/hsperfdata_stirlingpdfuser/*' \ - -not -path '*/tmp/lu*' \ - -not -path '*/tmp/tmp*' \ + -not -path '*/tmp/stirling-pdf/lu*' \ + -not -path '*/tmp/stirling-pdf/tmp*' \ 2>/dev/null | xargs -I{} sh -c 'stat -c \"%n %s %Y\" \"{}\" 2>/dev/null || true' | sort" > "$output_file" # Check if the output file has content @@ -74,8 +76,10 @@ capture_file_list() { -not -path '/config/*' \ -not -path '/logs/*' \ -not -path '*/home/stirlingpdfuser/.config/libreoffice/*' \ + -not -path '*/home/stirlingpdfuser/.pdfbox.cache' \ -not -path '*/tmp/PDFBox*' \ -not -path '*/tmp/hsperfdata_stirlingpdfuser/*' \ + -not -path '*/tmp/stirling-pdf/hsperfdata_stirlingpdfuser/*' \ -not -path '*/tmp/lu*' \ -not -path '*/tmp/tmp*' \ 2>/dev/null | sort" > "$output_file"