From 4d017610b80a5c5ff763a97dbd9e5d7472b8bc37 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Fri, 6 Dec 2024 19:08:18 +0000 Subject: [PATCH] PDF decryption --- .../static/js/multitool/DecryptFiles.js | 90 +++++++++++++++++++ .../static/js/multitool/PdfContainer.js | 27 ++++-- 2 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/static/js/multitool/DecryptFiles.js diff --git a/src/main/resources/static/js/multitool/DecryptFiles.js b/src/main/resources/static/js/multitool/DecryptFiles.js new file mode 100644 index 00000000..bd7e0073 --- /dev/null +++ b/src/main/resources/static/js/multitool/DecryptFiles.js @@ -0,0 +1,90 @@ +export class DecryptFile { + async decryptFile(file) { + try { + const password = prompt('This file is password-protected. Please enter the password:'); + + if (password === null) { + // User cancelled + console.error(`Password prompt cancelled for PDF: ${file.name}`); + this.showErrorBanner(`Operation cancelled for PDF: ${file.name}`, 'You cancelled the decryption process.'); + return null; // No file to return + } + + if (!password) { + // No password provided + console.error(`No password provided for encrypted PDF: ${file.name}`); + this.showErrorBanner(`No password provided for encrypted PDF: ${file.name}`, 'Please enter a valid password.'); + return null; // No file to return + } + + const formData = new FormData(); + formData.append('fileInput', file); + formData.append('password', password); + + // Send decryption request + const response = await fetch('/api/v1/security/remove-password', { + method: 'POST', + body: formData, + }); + + if (response.ok) { + const decryptedBlob = await response.blob(); + this.removeErrorBanner(); + return new File([decryptedBlob], file.name, {type: 'application/pdf'}); + } else { + const errorText = await response.text(); + console.error(`Server error while decrypting: ${errorText}`); + this.showErrorBanner( + 'Please try again with the correct password.', + errorText, + `Incorrect password for PDF: ${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( + `Decryption error for PDF: ${file.name}`, + error.message || 'Unexpected error occurred.', + 'There was an error processing the file. Please try again.' + ); + return null; // No file to return + } + } + + async checkFileEncrypted(file) { + try { + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + const arrayBuffer = await file.arrayBuffer(); // Convert file to ArrayBuffer + await pdfjsLib.getDocument({ + data: arrayBuffer, + password: '', + }).promise; + + return false; // File is not encrypted + } catch (error) { + if (error.name === 'PasswordException') { + return true; // File is encrypted + } + 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 = ''; + } +} diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 4eaf43f1..f7fa2536 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -5,6 +5,7 @@ import {SplitAllCommand} from './commands/split.js'; import {UndoManager} from './UndoManager.js'; import {PageBreakCommand} from './commands/page-break.js'; import {AddFilesCommand} from './commands/add-page.js'; +import {DecryptFile} from './DecryptFiles.js'; class PdfContainer { fileName; @@ -40,6 +41,8 @@ class PdfContainer { this.removeAllElements = this.removeAllElements.bind(this); this.resetPages = this.resetPages.bind(this); + this.decryptFile = new DecryptFile(); + this.undoManager = undoManager || new UndoManager(); this.pdfAdapters = pdfAdapters; @@ -165,7 +168,6 @@ class PdfContainer { input.click(); }); } - async addFilesFromFiles(files, nextSiblingElement, pages) { this.fileName = files[0].name; for (var i = 0; i < files.length; i++) { @@ -173,17 +175,27 @@ class PdfContainer { let processingTime, errorMessage = null, pageCount = 0; + try { - const file = files[i]; - if (file.type === 'application/pdf') { - const {renderer, pdfDocument} = await this.loadFile(file); + let decryptedFile = files[i]; + + if (decryptedFile.type === 'application/pdf' && (await this.decryptFile.checkFileEncrypted(decryptedFile))) { + decryptedFile = await this.decryptFile.decryptFile(decryptedFile); + if (!decryptedFile) { + throw new Error('File decryption failed.'); + } + } + + if (decryptedFile.type === 'application/pdf') { + const {renderer, pdfDocument} = await this.loadFile(decryptedFile); pageCount = renderer.pageCount || 0; pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages); - } else if (file.type.startsWith('image/')) { - pages = await this.addImageFile(file, nextSiblingElement, pages); + } else if (decryptedFile.type.startsWith('image/')) { + pages = await this.addImageFile(decryptedFile, nextSiblingElement, pages); } + processingTime = Date.now() - startTime; - this.captureFileProcessingEvent(true, file, processingTime, null, pageCount); + this.captureFileProcessingEvent(true, decryptedFile, processingTime, null, pageCount); } catch (error) { processingTime = Date.now() - startTime; errorMessage = error.message || 'Unknown error'; @@ -194,6 +206,7 @@ class PdfContainer { document.querySelectorAll('.enable-on-file').forEach((element) => { element.disabled = false; }); + return pages; }