diff --git a/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java b/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java index 4da2412a8..c7ef8efe2 100644 --- a/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java +++ b/common/src/main/java/stirling/software/common/service/CustomPDFDocumentFactory.java @@ -359,6 +359,15 @@ public class CustomPDFDocumentFactory { return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache); } catch (IOException e) { if (PdfErrorUtils.isCorruptedPdfError(e)) { + try { + org.springframework.context.MessageSource messageSource = + ApplicationContextProvider.getBean(org.springframework.context.MessageSource.class); + if (messageSource != null) { + throw new IOException(PdfErrorUtils.getCorruptedPdfMessage(messageSource), e); + } + } catch (Exception ex) { + // Fall back to non-i18n message if MessageSource is not available + } throw new IOException(PdfErrorUtils.getCorruptedPdfMessage(""), e); } throw e; @@ -379,6 +388,15 @@ public class CustomPDFDocumentFactory { return Loader.loadPDF(bytes, "", null, null, cache); } catch (IOException e) { if (PdfErrorUtils.isCorruptedPdfError(e)) { + try { + org.springframework.context.MessageSource messageSource = + ApplicationContextProvider.getBean(org.springframework.context.MessageSource.class); + if (messageSource != null) { + throw new IOException(PdfErrorUtils.getCorruptedPdfMessage(messageSource), e); + } + } catch (Exception ex) { + // Fall back to non-i18n message if MessageSource is not available + } throw new IOException(PdfErrorUtils.getCorruptedPdfMessage(""), e); } throw e; diff --git a/common/src/main/java/stirling/software/common/util/PdfErrorUtils.java b/common/src/main/java/stirling/software/common/util/PdfErrorUtils.java index 31cc2c00a..210b4d29c 100644 --- a/common/src/main/java/stirling/software/common/util/PdfErrorUtils.java +++ b/common/src/main/java/stirling/software/common/util/PdfErrorUtils.java @@ -2,6 +2,9 @@ package stirling.software.common.util; import java.io.IOException; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; + /** * Utility class for detecting and handling PDF-related errors. */ @@ -32,11 +35,46 @@ public class PdfErrorUtils { } /** - * Creates a user-friendly error message for corrupted PDF files. + * Creates a user-friendly error message for corrupted PDF files using i18n. * + * @param messageSource the Spring MessageSource for i18n + * @return a user-friendly error message + */ + public static String getCorruptedPdfMessage(MessageSource messageSource) { + return messageSource.getMessage("error.pdfCorrupted", null, LocaleContextHolder.getLocale()); + } + + /** + * Creates a user-friendly error message for corrupted PDF files with context using i18n. + * + * @param messageSource the Spring MessageSource for i18n * @param context additional context about where the error occurred (e.g., "during merge", "during processing") * @return a user-friendly error message */ + public static String getCorruptedPdfMessage(MessageSource messageSource, String context) { + if (context != null && !context.isEmpty()) { + return messageSource.getMessage("error.pdfCorruptedDuring", new Object[]{context}, LocaleContextHolder.getLocale()); + } + return getCorruptedPdfMessage(messageSource); + } + + /** + * Creates a user-friendly error message for multiple corrupted PDF files (e.g., during merge) using i18n. + * + * @param messageSource the Spring MessageSource for i18n + * @return a user-friendly error message for multiple file operations + */ + public static String getCorruptedPdfMessageForMultipleFiles(MessageSource messageSource) { + return messageSource.getMessage("error.pdfCorruptedMultiple", null, LocaleContextHolder.getLocale()); + } + + // Fallback methods for backwards compatibility (when MessageSource is not available) + /** + * Creates a user-friendly error message for corrupted PDF files (fallback). + * + * @param context additional context about where the error occurred + * @return a user-friendly error message + */ public static String getCorruptedPdfMessage(String context) { String baseMessage = "PDF file appears to be corrupted or damaged. " + "Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation."; @@ -48,7 +86,7 @@ public class PdfErrorUtils { } /** - * Creates a user-friendly error message for multiple corrupted PDF files (e.g., during merge). + * Creates a user-friendly error message for multiple corrupted PDF files (fallback). * * @return a user-friendly error message for multiple file operations */ diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 277828970..c7dbb509c 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; +import org.springframework.context.MessageSource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -47,6 +48,7 @@ import stirling.software.common.util.WebResponseUtils; public class MergeController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final MessageSource messageSource; // Merges a list of PDDocument objects into a single PDDocument public PDDocument mergeDocuments(List documents) throws IOException { @@ -195,7 +197,7 @@ public class MergeController { pdfDocumentFactory.getStreamCacheFunction(totalSize)); // Merge the documents } catch (IOException e) { if (PdfErrorUtils.isCorruptedPdfError(e)) { - throw new IOException(PdfErrorUtils.getCorruptedPdfMessageForMultipleFiles(), e); + throw new IOException(PdfErrorUtils.getCorruptedPdfMessageForMultipleFiles(messageSource), e); } throw e; } diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java index 5b21ab1a7..c199ea06d 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java @@ -24,6 +24,7 @@ import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.springframework.context.MessageSource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -53,6 +54,7 @@ import stirling.software.common.util.WebResponseUtils; public class ExtractImagesController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final MessageSource messageSource; @PostMapping(consumes = "multipart/form-data", value = "/extract-images") @Operation( @@ -213,7 +215,7 @@ public class ExtractImagesController { } } catch (IOException e) { if (PdfErrorUtils.isCorruptedPdfError(e)) { - throw new IOException(PdfErrorUtils.getCorruptedPdfMessage("during image extraction"), e); + throw new IOException(PdfErrorUtils.getCorruptedPdfMessage(messageSource, "during image extraction"), e); } throw e; } diff --git a/stirling-pdf/src/main/resources/messages_en_GB.properties b/stirling-pdf/src/main/resources/messages_en_GB.properties index d28657b91..ae0f5197f 100644 --- a/stirling-pdf/src/main/resources/messages_en_GB.properties +++ b/stirling-pdf/src/main/resources/messages_en_GB.properties @@ -170,6 +170,13 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +error.pdfCorrupted=PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation. +error.pdfCorruptedMultiple=One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them. +error.pdfCorruptedDuring=Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation. + +# Frontend corruption error messages +corrupt.pdfInvalid=The PDF file "{0}" appears to be corrupted or has an invalid structure. Please try using the 'Repair PDF' feature to fix the file before proceeding. +corrupt.tryRepair=Try using the Repair PDF feature to fix corrupted files. delete=Delete username=Username password=Password diff --git a/stirling-pdf/src/main/resources/static/js/DecryptFiles.js b/stirling-pdf/src/main/resources/static/js/DecryptFiles.js index 6701649c3..340773cb7 100644 --- a/stirling-pdf/src/main/resources/static/js/DecryptFiles.js +++ b/stirling-pdf/src/main/resources/static/js/DecryptFiles.js @@ -125,6 +125,16 @@ export class DecryptFile { } else if (error.code === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { return {isEncrypted: true, requiresPassword: false}; } + } else if (error.name === 'InvalidPDFException' || + (error.message && error.message.includes('Invalid PDF structure'))) { + // Handle corrupted PDF files + console.error('Corrupted PDF detected:', error); + this.showErrorBanner( + `${window.stirlingPDF.pdfCorruptedMessage.replace('{0}', file.name)}`, + error.stack || '', + `${window.stirlingPDF.tryRepairMessage}` + ); + throw new Error('PDF file is corrupted.'); } console.error('Error checking encryption:', error); diff --git a/stirling-pdf/src/main/resources/static/js/downloader.js b/stirling-pdf/src/main/resources/static/js/downloader.js index 7bdcd83d5..c5a1a91db 100644 --- a/stirling-pdf/src/main/resources/static/js/downloader.js +++ b/stirling-pdf/src/main/resources/static/js/downloader.js @@ -193,13 +193,13 @@ if (error.name === 'PasswordException' && error.code === 1) { console.log(`PDF requires password: ${file.name}`, error); console.log(`Attempting to remove password from PDF: ${file.name} with password.`); - const password = prompt(`${window.translations.decrypt.passwordPrompt}`); + const password = prompt(`${window.decrypt.passwordPrompt}`); if (!password) { console.error(`No password provided for encrypted PDF: ${file.name}`); showErrorBanner( - `${window.translations.decrypt.noPassword.replace('{0}', file.name)}`, - `${window.translations.decrypt.unexpectedError}` + `${window.decrypt.noPassword.replace('{0}', file.name)}`, + `${window.decrypt.unexpectedError}` ); throw error; } @@ -233,11 +233,20 @@ } catch (decryptError) { console.error(`Failed to decrypt PDF: ${file.name}`, decryptError); showErrorBanner( - `${window.translations.invalidPasswordHeader.replace('{0}', file.name)}`, - `${window.translations.invalidPassword}` + `${window.decrypt.invalidPasswordHeader.replace('{0}', file.name)}`, + `${window.decrypt.invalidPassword}` ); throw decryptError; } + } else if (error.name === 'InvalidPDFException' || + (error.message && error.message.includes('Invalid PDF structure'))) { + // Handle corrupted PDF files + console.log(`Corrupted PDF detected: ${file.name}`, error); + showErrorBanner( + `${window.stirlingPDF.pdfCorruptedMessage.replace('{0}', file.name)}`, + `${window.stirlingPDF.tryRepairMessage}` + ); + throw error; } else { console.log(`Error loading PDF: ${file.name}`, error); throw error; diff --git a/stirling-pdf/src/main/resources/static/js/fileInput.js b/stirling-pdf/src/main/resources/static/js/fileInput.js index f06371986..4c78f176e 100644 --- a/stirling-pdf/src/main/resources/static/js/fileInput.js +++ b/stirling-pdf/src/main/resources/static/js/fileInput.js @@ -235,6 +235,13 @@ function setupFileInput(chooser) { } catch (error) { console.error(`Error decrypting file: ${file.name}`, error); + + // Check if this is a PDF corruption error + if (error.message && error.message.includes('PDF file is corrupted')) { + // The error banner is already shown by DecryptFiles.js, just continue with the file + console.warn(`Continuing with corrupted PDF file: ${file.name}`); + } + if (!file.uniqueId) file.uniqueId = UUID.uuidv4(); return file; } diff --git a/stirling-pdf/src/main/resources/templates/multi-tool.html b/stirling-pdf/src/main/resources/templates/multi-tool.html index 173b92838..35c324c4d 100644 --- a/stirling-pdf/src/main/resources/templates/multi-tool.html +++ b/stirling-pdf/src/main/resources/templates/multi-tool.html @@ -160,6 +160,11 @@ success: '[[#{decrypt.success}]]', } + window.corrupt = { + pdfInvalid: '[[#{corrupt.pdfInvalid}]]', + tryRepair: '[[#{corrupt.tryRepair}]]', + } + const csvInput = document.getElementById("csv-input"); csvInput.addEventListener("keydown", function (event) { if (event.key === "Enter") {