Compare commits

...
8 Commits
Author SHA1 Message Date
Anthony Stirling a182676d3a fix auto delete dont use try 2026-01-14 15:26:17 +00:00
Anthony Stirling 73f12f40bb temp file manager 2026-01-14 15:05:54 +00:00
Anthony Stirling 95d8917380 formatting 2026-01-14 14:33:42 +00:00
Anthony Stirling bdcc186b41 remove extra fix due to conflicts in naming so just reverting to
tempfiles
2026-01-14 14:29:47 +00:00
Anthony Stirling 77b813723a version bump 2026-01-14 11:37:00 +00:00
Anthony Stirling 40ac69f101 Merge branch 'main' into utf8 2026-01-14 11:22:38 +00:00
Anthony Stirling e8c67d9e37 formatting 2026-01-14 11:21:51 +00:00
Anthony Stirling 0b5c885750 utf8Fix 2026-01-14 11:03:21 +00:00
6 changed files with 96 additions and 54 deletions
@@ -89,6 +89,11 @@ public class PDFToFile {
TempDirectory tempOutputDir = new TempDirectory(tempFileManager)) {
inputFile.transferTo(tempInputFile.getFile());
// Create unique subdirectory for pdftohtml output (collision-proof even if called
// multiple times)
Path pdftohtmlDir = Files.createTempDirectory(tempOutputDir.getPath(), "pdftohtml_");
String outputBasename = pdftohtmlDir.resolve("output").toString();
List<String> command =
new ArrayList<>(
Arrays.asList(
@@ -97,15 +102,13 @@ public class PDFToFile {
"-noframes",
"-c",
tempInputFile.getAbsolutePath(),
pdfBaseName));
outputBasename));
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
.runCommandWithOutputHandling(
command, tempOutputDir.getPath().toFile());
.runCommandWithOutputHandling(command, pdftohtmlDir.toFile());
// Process HTML files to Markdown
File[] outputFiles =
Objects.requireNonNull(tempOutputDir.getPath().toFile().listFiles());
File[] outputFiles = Objects.requireNonNull(pdftohtmlDir.toFile().listFiles());
List<File> markdownFiles = new ArrayList<>();
// Convert HTML files to Markdown
@@ -115,7 +118,7 @@ public class PDFToFile {
String markdown = htmlToMarkdownConverter.convert(html);
String mdFileName = outputFile.getName().replace(".html", ".md");
File mdFile = new File(tempOutputDir.getPath().toFile(), mdFileName);
File mdFile = new File(pdftohtmlDir.toFile(), mdFileName);
Files.writeString(mdFile.toPath(), markdown);
markdownFiles.add(mdFile);
}
@@ -182,18 +185,22 @@ public class PDFToFile {
// Save the uploaded file to a temporary location
inputFile.transferTo(tempInputFile);
// Run the pdftohtml command with complex output
// Create unique subdirectory for pdftohtml output (collision-proof even if called
// multiple times)
Path pdftohtmlDir = Files.createTempDirectory(tempOutputDir, "pdftohtml_");
String outputBasename = pdftohtmlDir.resolve("output").toString();
List<String> command =
new ArrayList<>(
Arrays.asList(
"pdftohtml", "-c", tempInputFile.toString(), pdfBaseName));
"pdftohtml", "-c", tempInputFile.toString(), outputBasename));
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
.runCommandWithOutputHandling(command, tempOutputDir.toFile());
.runCommandWithOutputHandling(command, pdftohtmlDir.toFile());
// Get output files
File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());
File[] outputFiles = Objects.requireNonNull(pdftohtmlDir.toFile().listFiles());
// Return output files in a ZIP archive
fileName = pdfBaseName + "ToHtml.zip";
@@ -254,9 +261,6 @@ public class PDFToFile {
Path tempInputFile = inputFileTemp.getPath();
Path tempOutputDir = outputDirTemp.getPath();
Path unoOutputFile =
tempOutputDir.resolve(
pdfBaseName + "." + resolvePrimaryExtension(outputFormat));
// Save the uploaded file to a temporary location
inputFile.transferTo(tempInputFile);
@@ -265,8 +269,15 @@ public class PDFToFile {
ProcessExecutorResult returnCode = null;
IOException unoconvertException = null;
Path unoOutputFile = null;
if (isUnoConvertEnabled()) {
try {
// Create output file only for unoconvert (it needs specific path)
unoOutputFile =
Files.createTempFile(
tempOutputDir,
"output_",
"." + resolvePrimaryExtension(outputFormat));
List<String> unoCommand =
buildUnoConvertCommand(
tempInputFile, unoOutputFile, outputFormat, libreOfficeFilter);
@@ -275,6 +286,14 @@ public class PDFToFile {
.runCommandWithOutputHandling(unoCommand);
} catch (IOException e) {
unoconvertException = e;
// Clean up temp file if unoconvert failed, so soffice doesn't see it
if (unoOutputFile != null && Files.exists(unoOutputFile)) {
try {
Files.delete(unoOutputFile);
} catch (IOException deleteException) {
log.debug("Failed to clean up temp file after unoconvert failure");
}
}
log.warn(
"Unoconvert command failed ({}). Falling back to soffice command.",
e.getMessage());
@@ -36,6 +36,8 @@ import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.TempDirectory;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@ConvertApi
@@ -47,6 +49,7 @@ public class ConvertOfficeController {
private final RuntimePathConfig runtimePathConfig;
private final CustomHtmlSanitizer customHtmlSanitizer;
private final EndpointConfiguration endpointConfiguration;
private final TempFileManager tempFileManager;
private boolean isUnoconvertAvailable() {
return endpointConfiguration.isGroupEnabled("Unoconvert")
@@ -68,29 +71,24 @@ public class ConvertOfficeController {
}
String extensionLower = extension.toLowerCase(Locale.ROOT);
String baseName = FilenameUtils.getBaseName(originalFilename);
if (baseName == null || baseName.isBlank()) {
baseName = "input";
}
// create temporary working directory
// Create work directory - caller (processFileToPDF) is responsible for cleanup
Path workDir = Files.createTempDirectory("office2pdf_");
Path inputPath = workDir.resolve(baseName + "." + extensionLower);
Path outputPath = workDir.resolve(baseName + ".pdf");
Path inputPath = Files.createTempFile(workDir, "input_", "." + extensionLower);
Path outputPath = Files.createTempFile(workDir, "output_", ".pdf");
// Check if the file is HTML and apply sanitization if needed
if ("html".equals(extensionLower) || "htm".equals(extensionLower)) {
// Read and sanitize HTML content
String htmlContent = new String(inputFile.getBytes(), StandardCharsets.UTF_8);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlContent);
Files.writeString(inputPath, sanitizedHtml, StandardCharsets.UTF_8);
} else {
// copy file content
Files.copy(inputFile.getInputStream(), inputPath, StandardCopyOption.REPLACE_EXISTING);
}
Path libreOfficeProfile = null;
try {
// Check if the file is HTML and apply sanitization if needed
if ("html".equals(extensionLower) || "htm".equals(extensionLower)) {
// Read and sanitize HTML content
String htmlContent = new String(inputFile.getBytes(), StandardCharsets.UTF_8);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlContent);
Files.writeString(inputPath, sanitizedHtml, StandardCharsets.UTF_8);
} else {
// copy file content
Files.copy(
inputFile.getInputStream(), inputPath, StandardCopyOption.REPLACE_EXISTING);
}
ProcessExecutorResult result;
// Run Unoconvert command
if (isUnoconvertAvailable()) {
@@ -109,21 +107,23 @@ public class ConvertOfficeController {
.runCommandWithOutputHandling(command);
} // Run soffice command
else {
libreOfficeProfile = Files.createTempDirectory("libreoffice_profile_");
List<String> command = new ArrayList<>();
command.add(runtimePathConfig.getSOfficePath());
command.add("-env:UserInstallation=" + libreOfficeProfile.toUri().toString());
command.add("--headless");
command.add("--nologo");
command.add("--convert-to");
command.add("pdf:writer_pdf_Export");
command.add("--outdir");
command.add(workDir.toString());
command.add(inputPath.toString());
try (TempDirectory libreOfficeProfileManager = new TempDirectory(tempFileManager)) {
Path libreOfficeProfile = libreOfficeProfileManager.getPath();
List<String> command = new ArrayList<>();
command.add(runtimePathConfig.getSOfficePath());
command.add("-env:UserInstallation=" + libreOfficeProfile.toUri().toString());
command.add("--headless");
command.add("--nologo");
command.add("--convert-to");
command.add("pdf:writer_pdf_Export");
command.add("--outdir");
command.add(workDir.toString());
command.add(inputPath.toString());
result =
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
.runCommandWithOutputHandling(command);
result =
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
.runCommandWithOutputHandling(command);
}
}
// Check the result
@@ -162,15 +162,12 @@ public class ConvertOfficeController {
return outputPath.toFile();
} finally {
// Clean up the temporary files
// Clean up the temporary input file (output and workDir cleaned by caller)
try {
Files.deleteIfExists(inputPath);
} catch (IOException e) {
log.warn("Failed to delete temp input file: {}", inputPath, e);
}
if (libreOfficeProfile != null) {
FileUtils.deleteQuietly(libreOfficeProfile.toFile());
}
}
}
+1 -1
View File
@@ -62,7 +62,7 @@ repositories {
allprojects {
group = 'stirling.software'
version = '2.2.1'
version = '2.3.0'
configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging'
+11
View File
@@ -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
# ==============================================================================
+11
View File
@@ -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
# ==============================================================================
+6 -2
View File
@@ -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 && \