mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a182676d3a | ||
|
|
73f12f40bb | ||
|
|
95d8917380 | ||
|
|
bdcc186b41 | ||
|
|
77b813723a | ||
|
|
40ac69f101 | ||
|
|
e8c67d9e37 | ||
|
|
0b5c885750 |
@@ -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());
|
||||
|
||||
+35
-38
@@ -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
@@ -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'
|
||||
|
||||
@@ -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
|
||||
# ==============================================================================
|
||||
|
||||
@@ -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
|
||||
# ==============================================================================
|
||||
|
||||
@@ -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 && \
|
||||
|
||||
Reference in New Issue
Block a user