error fixing and reports

This commit is contained in:
Anthony Stirling 2025-07-02 00:04:23 +01:00
parent d79b6e29e0
commit b249607e9c
9 changed files with 107 additions and 9 deletions

View File

@ -359,6 +359,15 @@ public class CustomPDFDocumentFactory {
return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache); return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache);
} catch (IOException e) { } catch (IOException e) {
if (PdfErrorUtils.isCorruptedPdfError(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 new IOException(PdfErrorUtils.getCorruptedPdfMessage(""), e);
} }
throw e; throw e;
@ -379,6 +388,15 @@ public class CustomPDFDocumentFactory {
return Loader.loadPDF(bytes, "", null, null, cache); return Loader.loadPDF(bytes, "", null, null, cache);
} catch (IOException e) { } catch (IOException e) {
if (PdfErrorUtils.isCorruptedPdfError(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 new IOException(PdfErrorUtils.getCorruptedPdfMessage(""), e);
} }
throw e; throw e;

View File

@ -2,6 +2,9 @@ package stirling.software.common.util;
import java.io.IOException; import java.io.IOException;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/** /**
* Utility class for detecting and handling PDF-related errors. * 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") * @param context additional context about where the error occurred (e.g., "during merge", "during processing")
* @return a user-friendly error message * @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) { public static String getCorruptedPdfMessage(String context) {
String baseMessage = "PDF file appears to be corrupted or damaged. " + 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."; "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 * @return a user-friendly error message for multiple file operations
*/ */

View File

@ -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.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -47,6 +48,7 @@ import stirling.software.common.util.WebResponseUtils;
public class MergeController { public class MergeController {
private final CustomPDFDocumentFactory pdfDocumentFactory; private final CustomPDFDocumentFactory pdfDocumentFactory;
private final MessageSource messageSource;
// Merges a list of PDDocument objects into a single PDDocument // Merges a list of PDDocument objects into a single PDDocument
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException { public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
@ -195,7 +197,7 @@ public class MergeController {
pdfDocumentFactory.getStreamCacheFunction(totalSize)); // Merge the documents pdfDocumentFactory.getStreamCacheFunction(totalSize)); // Merge the documents
} catch (IOException e) { } catch (IOException e) {
if (PdfErrorUtils.isCorruptedPdfError(e)) { if (PdfErrorUtils.isCorruptedPdfError(e)) {
throw new IOException(PdfErrorUtils.getCorruptedPdfMessageForMultipleFiles(), e); throw new IOException(PdfErrorUtils.getCorruptedPdfMessageForMultipleFiles(messageSource), e);
} }
throw e; throw e;
} }

View File

@ -24,6 +24,7 @@ import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.context.MessageSource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -53,6 +54,7 @@ import stirling.software.common.util.WebResponseUtils;
public class ExtractImagesController { public class ExtractImagesController {
private final CustomPDFDocumentFactory pdfDocumentFactory; private final CustomPDFDocumentFactory pdfDocumentFactory;
private final MessageSource messageSource;
@PostMapping(consumes = "multipart/form-data", value = "/extract-images") @PostMapping(consumes = "multipart/form-data", value = "/extract-images")
@Operation( @Operation(
@ -213,7 +215,7 @@ public class ExtractImagesController {
} }
} catch (IOException e) { } catch (IOException e) {
if (PdfErrorUtils.isCorruptedPdfError(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; throw e;
} }

View File

@ -170,6 +170,13 @@ sizes.medium=Medium
sizes.large=Large sizes.large=Large
sizes.x-large=X-Large sizes.x-large=X-Large
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect 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 delete=Delete
username=Username username=Username
password=Password password=Password

View File

@ -125,6 +125,16 @@ export class DecryptFile {
} else if (error.code === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { } else if (error.code === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
return {isEncrypted: true, requiresPassword: false}; 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); console.error('Error checking encryption:', error);

View File

@ -193,13 +193,13 @@
if (error.name === 'PasswordException' && error.code === 1) { if (error.name === 'PasswordException' && error.code === 1) {
console.log(`PDF requires password: ${file.name}`, error); console.log(`PDF requires password: ${file.name}`, error);
console.log(`Attempting to remove password from PDF: ${file.name} with password.`); 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) { if (!password) {
console.error(`No password provided for encrypted PDF: ${file.name}`); console.error(`No password provided for encrypted PDF: ${file.name}`);
showErrorBanner( showErrorBanner(
`${window.translations.decrypt.noPassword.replace('{0}', file.name)}`, `${window.decrypt.noPassword.replace('{0}', file.name)}`,
`${window.translations.decrypt.unexpectedError}` `${window.decrypt.unexpectedError}`
); );
throw error; throw error;
} }
@ -233,11 +233,20 @@
} catch (decryptError) { } catch (decryptError) {
console.error(`Failed to decrypt PDF: ${file.name}`, decryptError); console.error(`Failed to decrypt PDF: ${file.name}`, decryptError);
showErrorBanner( showErrorBanner(
`${window.translations.invalidPasswordHeader.replace('{0}', file.name)}`, `${window.decrypt.invalidPasswordHeader.replace('{0}', file.name)}`,
`${window.translations.invalidPassword}` `${window.decrypt.invalidPassword}`
); );
throw decryptError; 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 { } else {
console.log(`Error loading PDF: ${file.name}`, error); console.log(`Error loading PDF: ${file.name}`, error);
throw error; throw error;

View File

@ -235,6 +235,13 @@ function setupFileInput(chooser) {
} catch (error) { } catch (error) {
console.error(`Error decrypting file: ${file.name}`, 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(); if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
return file; return file;
} }

View File

@ -160,6 +160,11 @@
success: '[[#{decrypt.success}]]', success: '[[#{decrypt.success}]]',
} }
window.corrupt = {
pdfInvalid: '[[#{corrupt.pdfInvalid}]]',
tryRepair: '[[#{corrupt.tryRepair}]]',
}
const csvInput = document.getElementById("csv-input"); const csvInput = document.getElementById("csv-input");
csvInput.addEventListener("keydown", function (event) { csvInput.addEventListener("keydown", function (event) {
if (event.key === "Enter") { if (event.key === "Enter") {