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 && \