mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
fix(pdf): make encrypt/decrypt/permissions fail honestly instead of silent no-op
This commit is contained in:
@@ -4760,6 +4760,12 @@ ipcMain.on('process-pdf-operation', async (event, data) => {
|
||||
});
|
||||
}
|
||||
});
|
||||
// Reports pdf-lib's encryption capability (Task 27) so the renderer can
|
||||
// disable the password-protection controls instead of letting the user
|
||||
// fill the form only to see the operation fail.
|
||||
ipcMain.handle('get-pdf-capabilities', async () => ({
|
||||
passwordProtection: await PDFOperations.pdfEncryptionSupported,
|
||||
}));
|
||||
ipcMain.on('get-pdf-page-count', async (event, filePath) => {
|
||||
try {
|
||||
const count = await PDFOperations.getPageCount(filePath);
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
* cannot supply (merge takes many inputs in one op; reorder needs each
|
||||
* file's full page order; fillForm's field values differ per file).
|
||||
* - formFields — a read-only query returning data, not a transform.
|
||||
* - encrypt / decrypt / permissions — pdf-lib 1.17.1 (bundled) silently
|
||||
* ignores encryption options in save() and cannot decrypt on load, so a
|
||||
* batch run would either write unprotected files while reporting success
|
||||
* or deterministically fail every file.
|
||||
* - encrypt / decrypt / permissions — pdf-lib 1.17.1 (bundled) lacks
|
||||
* encryption support, so since Task 27 these ops fail honestly with an
|
||||
* "unavailable" result instead of silently writing unprotected files;
|
||||
* a batch run would deterministically fail every file.
|
||||
*
|
||||
* @module PDFBatchOperations
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,30 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
|
||||
|
||||
// pdf-lib 1.17.1 cannot encrypt: SaveOptions has no userPassword/ownerPassword/
|
||||
// permissions fields, so save() silently ignores them and writes an unprotected
|
||||
// file, and PDFDocument.load() cannot open password-protected input (verified
|
||||
// empirically in Task 22's review). Rather than trusting a pinned version
|
||||
// string, probe the installed library once at module load: save a tiny
|
||||
// in-memory document with a userPassword and check the raw bytes for an
|
||||
// /Encrypt dictionary (which an unencrypted document never contains). A library
|
||||
// that supports encryption passes the probe and the password ops re-enable
|
||||
// automatically. Probe errors fail closed (treated as unsupported).
|
||||
const pdfEncryptionSupported = (async () => {
|
||||
try {
|
||||
const probeDoc = await PDFDocument.create();
|
||||
const probeBytes = await probeDoc.save({ userPassword: 'encryption-capability-probe' });
|
||||
return Buffer.from(probeBytes).includes('/Encrypt');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
// Returned by the password ops when the probe reports no encryption support
|
||||
// (Task 27): fail honestly instead of silently writing an unprotected file.
|
||||
const PDF_ENCRYPTION_UNAVAILABLE_MESSAGE =
|
||||
'Password protection is not available in this build (pdf-lib lacks encryption support).';
|
||||
|
||||
function parsePageRanges(rangeString, totalPages) {
|
||||
const pages = [];
|
||||
const ranges = rangeString.split(',').map((r) => r.trim());
|
||||
@@ -301,6 +325,9 @@ async function pdfWatermark(data) {
|
||||
}
|
||||
|
||||
async function pdfEncrypt(data) {
|
||||
if (!(await pdfEncryptionSupported)) {
|
||||
return { success: false, message: PDF_ENCRYPTION_UNAVAILABLE_MESSAGE };
|
||||
}
|
||||
try {
|
||||
const pdfBytes = fs.readFileSync(data.inputPath);
|
||||
const pdf = await PDFDocument.load(pdfBytes);
|
||||
@@ -335,6 +362,9 @@ async function pdfEncrypt(data) {
|
||||
}
|
||||
|
||||
async function pdfDecrypt(data) {
|
||||
if (!(await pdfEncryptionSupported)) {
|
||||
return { success: false, message: PDF_ENCRYPTION_UNAVAILABLE_MESSAGE };
|
||||
}
|
||||
try {
|
||||
const pdfBytes = fs.readFileSync(data.inputPath);
|
||||
const pdf = await PDFDocument.load(pdfBytes, { password: data.password });
|
||||
@@ -352,6 +382,9 @@ async function pdfDecrypt(data) {
|
||||
}
|
||||
|
||||
async function pdfSetPermissions(data) {
|
||||
if (!(await pdfEncryptionSupported)) {
|
||||
return { success: false, message: PDF_ENCRYPTION_UNAVAILABLE_MESSAGE };
|
||||
}
|
||||
try {
|
||||
const pdfBytes = fs.readFileSync(data.inputPath);
|
||||
const loadOptions = data.currentPassword ? { password: data.currentPassword } : {};
|
||||
@@ -701,6 +734,8 @@ async function getPageCount(filePath) {
|
||||
module.exports = {
|
||||
parsePageRanges,
|
||||
hexToRgb,
|
||||
pdfEncryptionSupported,
|
||||
PDF_ENCRYPTION_UNAVAILABLE_MESSAGE,
|
||||
pdfMerge,
|
||||
pdfSplit,
|
||||
pdfCompress,
|
||||
|
||||
@@ -86,6 +86,7 @@ const ALLOWED_SEND_CHANNELS = [
|
||||
// PDF operations
|
||||
'process-pdf-operation',
|
||||
'get-pdf-page-count',
|
||||
'get-pdf-capabilities',
|
||||
'select-pdf-folder',
|
||||
'batch-pdf-operation',
|
||||
|
||||
|
||||
+34
-1
@@ -3922,8 +3922,41 @@ function updateMergeFilesList() {
|
||||
});
|
||||
}
|
||||
|
||||
// Task 27: pdf-lib 1.17.1 cannot encrypt, so encrypt/decrypt/permissions fail
|
||||
// honestly in the main process. Ask main once whether password protection is
|
||||
// available and, when it is not, disable the three sections' controls with an
|
||||
// explanatory hint instead of letting the user fill the form only to see the
|
||||
// operation fail. If the capability query itself fails, leave the controls
|
||||
// enabled — main still fails the operation honestly on submit.
|
||||
async function applyPDFPasswordProtectionAvailability() {
|
||||
let capabilities;
|
||||
try {
|
||||
capabilities = await ipcRenderer.invoke('get-pdf-capabilities');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!capabilities || capabilities.passwordProtection !== false) return;
|
||||
|
||||
const sectionIds = ['pdf-encrypt-section', 'pdf-decrypt-section', 'pdf-permissions-section'];
|
||||
for (const sectionId of sectionIds) {
|
||||
const section = document.getElementById(sectionId);
|
||||
if (!section) continue;
|
||||
section.querySelectorAll('input, select, button').forEach((control) => {
|
||||
control.disabled = true;
|
||||
});
|
||||
const hint = document.createElement('p');
|
||||
hint.className = 'warning-message pdf-unavailable-hint';
|
||||
hint.textContent =
|
||||
'Password protection is not available in this build (pdf-lib lacks encryption support).';
|
||||
section.prepend(hint);
|
||||
}
|
||||
}
|
||||
|
||||
// PDF Editor Event Listeners
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Disable password-protection controls when pdf-lib lacks encryption support
|
||||
applyPDFPasswordProtectionAvailability();
|
||||
|
||||
// Close PDF Editor Dialog
|
||||
const pdfEditorClose = document.getElementById('pdf-editor-dialog-close');
|
||||
if (pdfEditorClose) {
|
||||
@@ -4694,7 +4727,7 @@ ipcRenderer.on('pdf-operation-complete', (event, { success, error, message }) =>
|
||||
}
|
||||
}, 800);
|
||||
} else {
|
||||
showPDFStatus(`Error: ${error || 'PDF operation failed'}`, 'warning');
|
||||
showPDFStatus(`Error: ${error || message || 'PDF operation failed'}`, 'warning');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user