# Description of Changes This pull request introduces several improvements to enhance error handling, internationalization, and documentation in the codebase. The key changes include the addition of `ExceptionUtils` and `I18nUtils` utility classes for consistent exception handling and internationalized messages, updates to documentation paths, and modifications to existing methods to integrate the new utilities. ### Error Handling Enhancements: * **Added `ExceptionUtils` utility class**: Provides standardized methods for creating and handling exceptions with internationalized error messages, including specific handling for PDF corruption, encryption issues, and other file-related errors. * **Integrated `ExceptionUtils` into `CustomPDFDocumentFactory`**: Updated `loadFromFile` and `loadFromBytes` methods to log and handle exceptions using `ExceptionUtils`, ensuring consistent error handling across PDF operations. [[1]](diffhunk://#diff-10208c1fc2e04631a8cf2a2a99b2a1160e532e75a7b840ad752f3b0130b89851R358-R363) [[2]](diffhunk://#diff-10208c1fc2e04631a8cf2a2a99b2a1160e532e75a7b840ad752f3b0130b89851R375-R381) * **Updated `FileToPdf` to use `ExceptionUtils`**: Replaced direct exception throwing with `ExceptionUtils.createHtmlFileRequiredException` for unsupported file formats. ### Internationalization Improvements: * **Added `I18nUtils` utility class**: Centralized access to Spring's `MessageSource` for retrieving localized messages, enabling consistent internationalization across the application. ### Documentation Updates: * **Updated documentation paths in `CONTRIBUTING.md` and `README.md`**: Changed paths to reference the new `devGuide` folder for developer documentation and translation guides. [[1]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L28-R28) [[2]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L40-R51) [[3]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L171-L174) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: a <a>
161 lines
5.5 KiB
JavaScript
161 lines
5.5 KiB
JavaScript
export class DecryptFile {
|
|
|
|
constructor(){
|
|
this.decryptWorker = null
|
|
}
|
|
|
|
async decryptFile(file, requiresPassword) {
|
|
|
|
try {
|
|
async function getCsrfToken() {
|
|
const cookieValue = document.cookie
|
|
.split('; ')
|
|
.find((row) => row.startsWith('XSRF-TOKEN='))
|
|
?.split('=')[1];
|
|
|
|
if (cookieValue) {
|
|
return cookieValue;
|
|
}
|
|
|
|
const csrfElement = document.querySelector('input[name="_csrf"]');
|
|
return csrfElement ? csrfElement.value : null;
|
|
}
|
|
const csrfToken = await getCsrfToken();
|
|
const formData = new FormData();
|
|
formData.append('fileInput', file);
|
|
if (requiresPassword) {
|
|
const password = prompt(`${window.decrypt.passwordPrompt}`);
|
|
|
|
if (password === null) {
|
|
// User cancelled
|
|
console.error(`Password prompt cancelled for PDF: ${file.name}`);
|
|
return null; // No file to return
|
|
}
|
|
|
|
if (!password) {
|
|
// No password provided
|
|
console.error(`No password provided for encrypted PDF: ${file.name}`);
|
|
this.showErrorBanner(
|
|
`${window.decrypt.noPassword.replace('{0}', file.name)}`,
|
|
'',
|
|
`${window.decrypt.unexpectedError}`
|
|
);
|
|
return null; // No file to return
|
|
}
|
|
|
|
formData.append('password', password);
|
|
}
|
|
// Send decryption request
|
|
const response = await fetch('/api/v1/security/remove-password', {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: csrfToken ? {'X-XSRF-TOKEN': csrfToken} : undefined,
|
|
});
|
|
|
|
if (response.ok) {
|
|
this.removeErrorBanner();
|
|
const decryptedBlob = await response.blob();
|
|
return new File([decryptedBlob], file.name, {
|
|
type: "application/pdf",
|
|
});
|
|
} else {
|
|
const errorText = await response.text();
|
|
console.error(`${window.decrypt.invalidPassword} ${errorText}`);
|
|
this.showErrorBanner(
|
|
`${window.decrypt.invalidPassword}`,
|
|
errorText,
|
|
`${window.decrypt.invalidPasswordHeader.replace('{0}', file.name)}`
|
|
);
|
|
return null; // No file to return
|
|
}
|
|
} catch (error) {
|
|
// Handle network or unexpected errors
|
|
console.error(`Failed to decrypt PDF: ${file.name}`, error);
|
|
this.showErrorBanner(
|
|
`${window.decrypt.unexpectedError.replace('{0}', file.name)}`,
|
|
`${error.message || window.decrypt.unexpectedError}`,
|
|
error
|
|
);
|
|
return null; // No file to return
|
|
}
|
|
}
|
|
|
|
async checkFileEncrypted(file) {
|
|
try {
|
|
if (file.type !== 'application/pdf') {
|
|
return {isEncrypted: false, requiresPassword: false};
|
|
}
|
|
|
|
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
|
|
|
const arrayBuffer = await file.arrayBuffer();
|
|
const arrayBufferForPdfLib = arrayBuffer.slice(0);
|
|
var loadingTask;
|
|
|
|
if(this.decryptWorker == null){
|
|
loadingTask = pdfjsLib.getDocument({
|
|
data: arrayBuffer,
|
|
});
|
|
this.decryptWorker = loadingTask._worker
|
|
|
|
}else {
|
|
loadingTask = pdfjsLib.getDocument({
|
|
data: arrayBuffer,
|
|
worker: this.decryptWorker
|
|
});
|
|
}
|
|
|
|
await loadingTask.promise;
|
|
|
|
try {
|
|
//Uses PDFLib.PDFDocument to check if unpassworded but encrypted
|
|
const pdfDoc = await PDFLib.PDFDocument.load(arrayBufferForPdfLib);
|
|
return {isEncrypted: false, requiresPassword: false};
|
|
} catch (error) {
|
|
if (error.message.includes('Input document to `PDFDocument.load` is encrypted')) {
|
|
return {isEncrypted: true, requiresPassword: false};
|
|
}
|
|
console.error('Error checking encryption:', error);
|
|
throw new Error('Failed to determine if the file is encrypted.');
|
|
}
|
|
} catch (error) {
|
|
if (error.name === 'PasswordException') {
|
|
if (error.code === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
|
|
return {isEncrypted: true, requiresPassword: true};
|
|
} 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);
|
|
throw new Error('Failed to determine if the file is encrypted.');
|
|
}
|
|
}
|
|
|
|
showErrorBanner(message, stackTrace, error) {
|
|
const errorContainer = document.getElementById('errorContainer');
|
|
errorContainer.style.display = 'block'; // Display the banner
|
|
errorContainer.querySelector('.alert-heading').textContent = error;
|
|
errorContainer.querySelector('p').textContent = message;
|
|
document.querySelector('#traceContent').textContent = stackTrace;
|
|
}
|
|
|
|
removeErrorBanner() {
|
|
const errorContainer = document.getElementById('errorContainer');
|
|
errorContainer.style.display = 'none'; // Hide the banner
|
|
errorContainer.querySelector('.alert-heading').textContent = '';
|
|
errorContainer.querySelector('p').textContent = '';
|
|
document.querySelector('#traceContent').textContent = '';
|
|
}
|
|
}
|