diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0f3580d5a..2725e5f5a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - name: 💬 Discord Server - url: https://discord.gg/Cn8pWhQRxZ + url: https://discord.gg/HYmhKj45pU about: You can join our Discord server for real time discussion and support diff --git a/README.md b/README.md index c5a0260ef..4ec3d3fd6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

Stirling-PDF

[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf) -[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ) +[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU) [![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/) [![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index d738ae795..be6b5019b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -39,10 +39,7 @@ public class PasswordController { } @PostMapping(consumes = "multipart/form-data", value = "/remove-password") - @Operation( - summary = "Remove password from a PDF file", - description = - "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO") + @Operation(summary = "Remove password from a PDF file", description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO") public ResponseEntity removePassword(@ModelAttribute PDFPasswordRequest request) throws IOException { MultipartFile fileInput = request.getFileInput(); @@ -52,15 +49,12 @@ public class PasswordController { return WebResponseUtils.pdfDocToWebResponse( document, Filenames.toSimpleFileName(fileInput.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") + .replaceFirst("[.][^.]+$", "") + "_password_removed.pdf"); } @PostMapping(consumes = "multipart/form-data", value = "/add-password") - @Operation( - summary = "Add password to a PDF file", - description = - "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF") + @Operation(summary = "Add password to a PDF file", description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF") public ResponseEntity addPassword(@ModelAttribute AddPasswordRequest request) throws IOException { MultipartFile fileInput = request.getFileInput(); @@ -98,12 +92,12 @@ public class PasswordController { return WebResponseUtils.pdfDocToWebResponse( document, Filenames.toSimpleFileName(fileInput.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") + .replaceFirst("[.][^.]+$", "") + "_permissions.pdf"); return WebResponseUtils.pdfDocToWebResponse( document, Filenames.toSimpleFileName(fileInput.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") + .replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); } } diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index 4131b49a9..c651620e9 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -42,7 +42,7 @@ event.preventDefault(); firstErrorOccurred = false; const url = this.action; - const files = $('#fileInput-input')[0].files; + let files = $('#fileInput-input')[0].files; const formData = new FormData(this); const submitButton = document.getElementById('submitBtn'); const showGameBtn = document.getElementById('show-game-btn'); @@ -74,6 +74,16 @@ submitButton.textContent = 'Processing...'; submitButton.disabled = true; + if (!url.includes('remove-password')) { + // Check if any PDF files are encrypted and handle decryption if necessary + const decryptedFiles = await checkAndDecryptFiles(url, files); + files = decryptedFiles; + // Append decrypted files to formData + decryptedFiles.forEach((file, index) => { + formData.set(`fileInput`, file); + }); + } + if (remoteCall === true) { if (override === 'multi' || (!multipleInputsForSingleRequest && files.length > 1 && override !== 'single')) { await submitMultiPdfForm(url, files); @@ -133,6 +143,92 @@ } } + async function checkAndDecryptFiles(url, files) { + const decryptedFiles = []; + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + + // Extract the base URL + const baseUrl = new URL(url); + let removePasswordUrl = `${baseUrl.origin}`; + + // Check if there's a path before /api/ + const apiIndex = baseUrl.pathname.indexOf('/api/'); + if (apiIndex > 0) { + removePasswordUrl += baseUrl.pathname.substring(0, apiIndex); + } + + // Append the new endpoint + removePasswordUrl += '/api/v1/security/remove-password'; + + console.log(`Remove password URL: ${removePasswordUrl}`); + + for (const file of files) { + console.log(`Processing file: ${file.name}`); + if (file.type !== 'application/pdf') { + console.log(`Skipping non-PDF file: ${file.name}`); + decryptedFiles.push(file); + continue; + } + try { + const arrayBuffer = await file.arrayBuffer(); + const loadingTask = pdfjsLib.getDocument({data: arrayBuffer}); + + console.log(`Attempting to load PDF: ${file.name}`); + const pdf = await loadingTask.promise; + console.log(`File is not encrypted: ${file.name}`); + decryptedFiles.push(file); // If no error, file is not encrypted + } catch (error) { + 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(`This PDF (${file.name}) is encrypted. Please enter the password:`); + + if (!password) { + console.error(`No password provided for encrypted PDF: ${file.name}`); + showErrorBanner(`No password provided for encrypted PDF: ${file.name}`, 'Please enter a valid password.'); + throw error; + } + + try { + // Prepare FormData for the decryption request + const formData = new FormData(); + formData.append('fileInput', file); + formData.append('password', password); + + // Use handleSingleDownload to send the request + const decryptionResult = await fetch(removePasswordUrl, {method: 'POST', body: formData}); + + if (decryptionResult && decryptionResult.blob) { + const decryptedBlob = await decryptionResult.blob(); + const decryptedFile = new File([decryptedBlob], file.name, {type: 'application/pdf'}); + + /* // Create a link element to download the file + const link = document.createElement('a'); + link.href = URL.createObjectURL(decryptedBlob); + link.download = 'test.pdf'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +*/ + decryptedFiles.push(decryptedFile); + console.log(`Successfully decrypted PDF: ${file.name}`); + } else { + throw new Error('Decryption failed: No valid response from server'); + } + } catch (decryptError) { + console.error(`Failed to decrypt PDF: ${file.name}`, decryptError); + showErrorBanner(`Failed to decrypt PDF: ${file.name}`, 'Incorrect password or unsupported encryption.'); + throw decryptError; + } + } else { + console.log(`Error loading PDF: ${file.name}`, error); + throw error; + } + } + } + return decryptedFiles; + } + async function handleSingleDownload(url, formData, isMulti = false, isZip = false) { const startTime = performance.now(); const file = formData.get('fileInput'); diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 314d28b39..00f22c27b 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -19,7 +19,7 @@

- +
diff --git a/src/main/resources/templates/fragments/errorBanner.html b/src/main/resources/templates/fragments/errorBanner.html index 32fb9019c..d682dcb92 100644 --- a/src/main/resources/templates/fragments/errorBanner.html +++ b/src/main/resources/templates/fragments/errorBanner.html @@ -20,7 +20,7 @@ - +