From 0b5c8857503dce99bcec85750ba25bda4b56c62d Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:03:21 +0000 Subject: [PATCH] utf8Fix --- .../software/common/util/GeneralUtils.java | 36 +++++++++++++++++++ .../software/common/util/PDFToFile.java | 12 +++++-- .../converters/ConvertOfficeController.java | 8 +++-- docker/embedded/Dockerfile | 11 ++++++ docker/embedded/Dockerfile.fat | 11 ++++++ docker/embedded/Dockerfile.ultra-lite | 8 +++-- 6 files changed, 79 insertions(+), 7 deletions(-) diff --git a/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java b/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java index 5f9d2a14fd..ea76485abf 100644 --- a/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java @@ -242,6 +242,42 @@ public class GeneralUtils { return safeName; } + /** + * Sanitizes a filename to ASCII-safe characters for use with external processes. Replaces + * non-ASCII characters and special characters with underscores to prevent InvalidPathException + * and encoding issues when filenames are passed to external programs like LibreOffice, + * Ghostscript, etc. + * + *

This method is specifically designed for temporary filenames used in ProcessExecutor calls + * where non-ASCII characters (e.g., Polish "ł", Chinese characters, etc.) can cause path + * encoding issues. + * + * @param filename the filename to sanitize, may be null + * @return ASCII-safe filename with only alphanumeric, dot, dash, and underscore characters, or + * "file" if input is null/empty + */ + public String sanitizeFilenameForProcess(String filename) { + if (filename == null || filename.isBlank()) { + return "file"; + } + + // Replace non-ASCII and non-alphanumeric characters (except dot, dash, underscore) with + // underscore + String sanitized = filename.replaceAll("[^a-zA-Z0-9._-]", "_"); + + // Handle cases where the entire filename became underscores + if (sanitized.isBlank() || sanitized.matches("^[._-]+$")) { + return "file"; + } + + // Limit length to prevent excessively long filenames + if (sanitized.length() > 200) { + sanitized = sanitized.substring(0, 200); + } + + return sanitized; + } + // Get resources from a location pattern public Resource[] getResourcesFromLocationPattern( String locationPattern, ResourceLoader resourceLoader) throws Exception { 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 b00cdae868..00fd6f6af9 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 @@ -81,6 +81,8 @@ public class PDFToFile { if (originalPdfFileName.contains(".")) { pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.')); } + // Sanitize to ASCII-safe characters for external process compatibility + String sanitizedBaseName = GeneralUtils.sanitizeFilenameForProcess(pdfBaseName); byte[] fileBytes; String fileName; @@ -97,7 +99,7 @@ public class PDFToFile { "-noframes", "-c", tempInputFile.getAbsolutePath(), - pdfBaseName)); + sanitizedBaseName)); ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML) @@ -169,6 +171,8 @@ public class PDFToFile { if (originalPdfFileName.contains(".")) { pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.')); } + // Sanitize to ASCII-safe characters for external process compatibility + String sanitizedBaseName = GeneralUtils.sanitizeFilenameForProcess(pdfBaseName); byte[] fileBytes; String fileName; @@ -186,7 +190,7 @@ public class PDFToFile { List command = new ArrayList<>( Arrays.asList( - "pdftohtml", "-c", tempInputFile.toString(), pdfBaseName)); + "pdftohtml", "-c", tempInputFile.toString(), sanitizedBaseName)); ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML) @@ -238,6 +242,8 @@ public class PDFToFile { if (originalPdfFileName.contains(".")) { pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.')); } + // Sanitize to ASCII-safe characters for external process compatibility + String sanitizedBaseName = GeneralUtils.sanitizeFilenameForProcess(pdfBaseName); // Validate output format List allowedFormats = Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "xml", "txt:Text"); @@ -256,7 +262,7 @@ public class PDFToFile { Path tempOutputDir = outputDirTemp.getPath(); Path unoOutputFile = tempOutputDir.resolve( - pdfBaseName + "." + resolvePrimaryExtension(outputFormat)); + sanitizedBaseName + "." + resolvePrimaryExtension(outputFormat)); // Save the uploaded file to a temporary location inputFile.transferTo(tempInputFile); 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 f62efeb759..62d0abc7d8 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 @@ -73,10 +73,14 @@ public class ConvertOfficeController { baseName = "input"; } + // Sanitize baseName to ASCII-safe characters to prevent InvalidPathException + // with non-ASCII characters (e.g., Polish, Chinese, etc.) when creating temp file paths + String sanitizedBaseName = GeneralUtils.sanitizeFilenameForProcess(baseName); + // create temporary working directory Path workDir = Files.createTempDirectory("office2pdf_"); - Path inputPath = workDir.resolve(baseName + "." + extensionLower); - Path outputPath = workDir.resolve(baseName + ".pdf"); + Path inputPath = workDir.resolve(sanitizedBaseName + "." + extensionLower); + Path outputPath = workDir.resolve(sanitizedBaseName + ".pdf"); // Check if the file is HTML and apply sanitization if needed if ("html".equals(extensionLower) || "htm".equals(extensionLower)) { diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile index dd2124b18f..c8855bf2b8 100644 --- a/docker/embedded/Dockerfile +++ b/docker/embedded/Dockerfile @@ -66,6 +66,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ xvfb x11-utils coreutils \ # Temporary packages only needed for Calibre installer xz-utils gpgv curl xdg-utils \ + # UTF-8 locale support for handling international filenames + locales \ \ # Install Calibre from official installer script && curl -fsSL https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin \ @@ -79,6 +81,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN ln -sf /opt/calibre/ebook-convert /usr/bin/ebook-convert \ && /opt/calibre/ebook-convert --version +# Configure UTF-8 locale to support international characters in filenames +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \ + && locale-gen en_US.UTF-8 + +# Set UTF-8 locale environment variables +ENV LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANGUAGE=en_US:en + # ============================================================================== # Create non-root user (stirlingpdfuser) with configurable UID/GID # ============================================================================== diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat index 00f0e259de..4da088372e 100644 --- a/docker/embedded/Dockerfile.fat +++ b/docker/embedded/Dockerfile.fat @@ -69,6 +69,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ xvfb x11-utils coreutils \ # Temporary packages only needed for Calibre installer xz-utils gpgv curl xdg-utils \ + # UTF-8 locale support for handling international filenames + locales \ \ # Install Calibre from official installer script && curl -fsSL https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin \ @@ -82,6 +84,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN ln -sf /opt/calibre/ebook-convert /usr/bin/ebook-convert \ && /opt/calibre/ebook-convert --version +# Configure UTF-8 locale to support international characters in filenames +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \ + && locale-gen en_US.UTF-8 + +# Set UTF-8 locale environment variables +ENV LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANGUAGE=en_US:en + # ============================================================================== # Create non-root user (stirlingpdfuser) with configurable UID/GID # ============================================================================== diff --git a/docker/embedded/Dockerfile.ultra-lite b/docker/embedded/Dockerfile.ultra-lite index 8556d4585e..5f6681f100 100644 --- a/docker/embedded/Dockerfile.ultra-lite +++ b/docker/embedded/Dockerfile.ultra-lite @@ -73,7 +73,10 @@ ENV VERSION_TAG=$VERSION_TAG \ TMPDIR=/tmp/stirling-pdf \ TEMP=/tmp/stirling-pdf \ TMP=/tmp/stirling-pdf \ - ENDPOINTS_GROUPS_TO_REMOVE=CLI + ENDPOINTS_GROUPS_TO_REMOVE=CLI \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANGUAGE=en_US:en # Install minimal dependencies RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ @@ -88,7 +91,8 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a curl \ shadow \ su-exec \ - openjdk21-jre && \ + openjdk21-jre \ + musl-locales musl-locales-lang && \ mkdir -p $HOME /configs /configs/heap_dumps /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \ mkdir -p /usr/share/fonts/opentype/noto && \ chmod +x /scripts/*.sh && \