diff --git a/src/js/config/pdf-tools.ts b/src/js/config/pdf-tools.ts index 56d0cd3..f43cfaa 100644 --- a/src/js/config/pdf-tools.ts +++ b/src/js/config/pdf-tools.ts @@ -39,6 +39,7 @@ export const singlePdfLoadTools = [ 'remove-blank-pages', 'add-attachments', 'sanitize-pdf', + 'remove-restrictions', ]; export const simpleTools = [ diff --git a/src/js/config/tools.ts b/src/js/config/tools.ts index b845c48..868d936 100644 --- a/src/js/config/tools.ts +++ b/src/js/config/tools.ts @@ -401,6 +401,13 @@ export const categories = [ icon: 'ruler', subtitle: 'Analyze page size, orientation, and units.', }, + { + id: 'remove-restrictions', + name: 'Remove Restrictions', + icon: 'unlink', + subtitle: + 'Remove password protection and security restrictions associated with digitally signed PDF files.', + }, ], }, { @@ -410,7 +417,7 @@ export const categories = [ id: 'encrypt', name: 'Encrypt PDF', icon: 'lock', - subtitle: 'Add a password to protect your PDF.', + subtitle: 'Lock your PDF by adding a password.', }, { id: 'sanitize-pdf', @@ -422,7 +429,7 @@ export const categories = [ id: 'decrypt', name: 'Decrypt PDF', icon: 'unlock', - subtitle: 'Remove password protection from a PDF.', + subtitle: 'Unlock PDF by removing password protection.', }, { id: 'flatten', diff --git a/src/js/handlers/fileHandler.ts b/src/js/handlers/fileHandler.ts index 3c04413..96d796a 100644 --- a/src/js/handlers/fileHandler.ts +++ b/src/js/handlers/fileHandler.ts @@ -33,7 +33,8 @@ async function handleSinglePdfUpload(toolId, file) { if ( state.pdfDoc.isEncrypted && toolId !== 'decrypt' && - toolId !== 'change-permissions' + toolId !== 'change-permissions' && + toolId !== 'remove-restrictions' ) { showAlert( 'Protected PDF', diff --git a/src/js/logic/change-permissions.ts b/src/js/logic/change-permissions.ts index 7a42992..d94dc66 100644 --- a/src/js/logic/change-permissions.ts +++ b/src/js/logic/change-permissions.ts @@ -1,18 +1,22 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js'; +import { + downloadFile, + initializeQpdf, + readFileAsArrayBuffer, +} from '../utils/helpers.js'; import { state } from '../state.js'; export async function changePermissions() { const file = state.files[0]; - const currentPassword = ( - document.getElementById('current-password') as HTMLInputElement - )?.value || ''; - const newUserPassword = ( - document.getElementById('new-user-password') as HTMLInputElement - )?.value || ''; - const newOwnerPassword = ( - document.getElementById('new-owner-password') as HTMLInputElement - )?.value || ''; + const currentPassword = + (document.getElementById('current-password') as HTMLInputElement)?.value || + ''; + const newUserPassword = + (document.getElementById('new-user-password') as HTMLInputElement)?.value || + ''; + const newOwnerPassword = + (document.getElementById('new-owner-password') as HTMLInputElement) + ?.value || ''; const inputPath = '/input.pdf'; const outputPath = '/output.pdf'; @@ -37,21 +41,34 @@ export async function changePermissions() { } const shouldEncrypt = newUserPassword || newOwnerPassword; - + if (shouldEncrypt) { const finalUserPassword = newUserPassword; const finalOwnerPassword = newOwnerPassword; args.push('--encrypt', finalUserPassword, finalOwnerPassword, '256'); - // Get permission checkboxes - const allowPrinting = (document.getElementById('allow-printing') as HTMLInputElement)?.checked; - const allowCopying = (document.getElementById('allow-copying') as HTMLInputElement)?.checked; - const allowModifying = (document.getElementById('allow-modifying') as HTMLInputElement)?.checked; - const allowAnnotating = (document.getElementById('allow-annotating') as HTMLInputElement)?.checked; - const allowFillingForms = (document.getElementById('allow-filling-forms') as HTMLInputElement)?.checked; - const allowDocumentAssembly = (document.getElementById('allow-document-assembly') as HTMLInputElement)?.checked; - const allowPageExtraction = (document.getElementById('allow-page-extraction') as HTMLInputElement)?.checked; + const allowPrinting = ( + document.getElementById('allow-printing') as HTMLInputElement + )?.checked; + const allowCopying = ( + document.getElementById('allow-copying') as HTMLInputElement + )?.checked; + const allowModifying = ( + document.getElementById('allow-modifying') as HTMLInputElement + )?.checked; + const allowAnnotating = ( + document.getElementById('allow-annotating') as HTMLInputElement + )?.checked; + const allowFillingForms = ( + document.getElementById('allow-filling-forms') as HTMLInputElement + )?.checked; + const allowDocumentAssembly = ( + document.getElementById('allow-document-assembly') as HTMLInputElement + )?.checked; + const allowPageExtraction = ( + document.getElementById('allow-page-extraction') as HTMLInputElement + )?.checked; if (finalOwnerPassword) { if (!allowModifying) args.push('--modify=none'); @@ -71,28 +88,28 @@ export async function changePermissions() { } args.push('--', outputPath); - - console.log('qpdf args:', args); - try { qpdf.callMain(args); } catch (qpdfError: any) { console.error('qpdf execution error:', qpdfError); - - // Check for various password-related errors + const errorMsg = qpdfError.message || ''; - - if (errorMsg.includes('invalid password') || - errorMsg.includes('incorrect password') || - errorMsg.includes('password')) { + + if ( + errorMsg.includes('invalid password') || + errorMsg.includes('incorrect password') || + errorMsg.includes('password') + ) { throw new Error('INVALID_PASSWORD'); } - - if (errorMsg.includes('encrypted') || - errorMsg.includes('password required')) { + + if ( + errorMsg.includes('encrypted') || + errorMsg.includes('password required') + ) { throw new Error('PASSWORD_REQUIRED'); } - + throw new Error('Processing failed: ' + errorMsg || 'Unknown error'); } @@ -107,17 +124,18 @@ export async function changePermissions() { downloadFile(blob, `permissions-changed-${file.name}`); hideLoader(); - + let successMessage = 'PDF permissions changed successfully!'; if (!shouldEncrypt) { - successMessage = 'PDF decrypted successfully! All encryption and restrictions removed.'; + successMessage = + 'PDF decrypted successfully! All encryption and restrictions removed.'; } - + showAlert('Success', successMessage); } catch (error: any) { console.error('Error during PDF permission change:', error); hideLoader(); - + if (error.message === 'INVALID_PASSWORD') { showAlert( 'Incorrect Password', @@ -139,17 +157,13 @@ export async function changePermissions() { if (qpdf?.FS) { try { qpdf.FS.unlink(inputPath); - } catch (e) { - - } + } catch (e) {} try { qpdf.FS.unlink(outputPath); - } catch (e) { - - } + } catch (e) {} } } catch (cleanupError) { console.warn('Failed to cleanup WASM FS:', cleanupError); } } -} \ No newline at end of file +} diff --git a/src/js/logic/decrypt.ts b/src/js/logic/decrypt.ts index 21c3cbb..b77e2c5 100644 --- a/src/js/logic/decrypt.ts +++ b/src/js/logic/decrypt.ts @@ -1,5 +1,9 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js'; +import { + downloadFile, + initializeQpdf, + readFileAsArrayBuffer, +} from '../utils/helpers.js'; import { state } from '../state.js'; export async function decrypt() { diff --git a/src/js/logic/encrypt.ts b/src/js/logic/encrypt.ts index a2b5c1f..e6823f8 100644 --- a/src/js/logic/encrypt.ts +++ b/src/js/logic/encrypt.ts @@ -1,5 +1,9 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; -import { downloadFile, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js'; +import { + downloadFile, + initializeQpdf, + readFileAsArrayBuffer, +} from '../utils/helpers.js'; import { state } from '../state.js'; export async function encrypt() { @@ -35,32 +39,24 @@ export async function encrypt() { showLoader('Encrypting PDF with 256-bit AES...'); - const args = [ - inputPath, - '--encrypt', - userPassword, - ownerPassword, - '256', - ]; + const args = [inputPath, '--encrypt', userPassword, ownerPassword, '256']; // Only add restrictions if a distinct owner password was provided if (hasDistinctOwnerPassword) { args.push( '--modify=none', - '--extract=n', - '--print=none', - '--accessibility=n', - '--annotate=n', - '--assemble=n', - '--form=n', - '--modify-other=n', + '--extract=n', + '--print=none', + '--accessibility=n', + '--annotate=n', + '--assemble=n', + '--form=n', + '--modify-other=n' ); } args.push('--', outputPath); - console.log(args); - try { qpdf.callMain(args); } catch (qpdfError: any) { @@ -114,4 +110,4 @@ export async function encrypt() { console.warn('Failed to cleanup WASM FS:', cleanupError); } } -} \ No newline at end of file +} diff --git a/src/js/logic/index.ts b/src/js/logic/index.ts index 261c41e..34d9cf5 100644 --- a/src/js/logic/index.ts +++ b/src/js/logic/index.ts @@ -64,12 +64,14 @@ import { alternateMerge, setupAlternateMergeTool } from './alternate-merge.js'; import { linearizePdf } from './linearize.js'; import { addAttachments, setupAddAttachmentsTool } from './add-attachments.js'; import { sanitizePdf } from './sanitize-pdf.js'; +import { removeRestrictions } from './remove-restrictions.js'; export const toolLogic = { merge: { process: merge, setup: setupMergeTool }, split: { process: split, setup: setupSplitTool }, encrypt, decrypt, + 'remove-restrictions': removeRestrictions, organize, rotate, 'add-page-numbers': addPageNumbers, diff --git a/src/js/logic/remove-restrictions.ts b/src/js/logic/remove-restrictions.ts new file mode 100644 index 0000000..3fe0f76 --- /dev/null +++ b/src/js/logic/remove-restrictions.ts @@ -0,0 +1,99 @@ +import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { + downloadFile, + initializeQpdf, + readFileAsArrayBuffer, +} from '../utils/helpers.js'; +import { state } from '../state.js'; + +export async function removeRestrictions() { + const file = state.files[0]; + const password = + (document.getElementById('owner-password-remove') as HTMLInputElement) + ?.value || ''; + + const inputPath = '/input.pdf'; + const outputPath = '/output.pdf'; + let qpdf: any; + + try { + showLoader('Initializing...'); + qpdf = await initializeQpdf(); + + showLoader('Reading PDF...'); + const fileBuffer = await readFileAsArrayBuffer(file); + const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer); + + qpdf.FS.writeFile(inputPath, uint8Array); + + showLoader('Removing restrictions...'); + + const args = [inputPath]; + + if (password) { + args.push(`--password=${password}`); + } + + args.push('--decrypt', '--remove-restrictions', '--', outputPath); + + try { + qpdf.callMain(args); + } catch (qpdfError: any) { + console.error('qpdf execution error:', qpdfError); + if ( + qpdfError.message?.includes('password') || + qpdfError.message?.includes('encrypt') + ) { + throw new Error( + 'Failed to remove restrictions. The PDF may require the correct owner password.' + ); + } + + throw new Error( + 'Failed to remove restrictions: ' + + (qpdfError.message || 'Unknown error') + ); + } + + showLoader('Preparing download...'); + const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' }); + + if (!outputFile || outputFile.length === 0) { + throw new Error('Operation resulted in an empty file.'); + } + + const blob = new Blob([outputFile], { type: 'application/pdf' }); + downloadFile(blob, `unrestricted-${file.name}`); + + hideLoader(); + + showAlert( + 'Success', + 'PDF restrictions removed successfully! The file is now fully editable and printable.' + ); + } catch (error: any) { + console.error('Error during restriction removal:', error); + hideLoader(); + showAlert( + 'Operation Failed', + `An error occurred: ${error.message || 'The PDF might be corrupted or password-protected.'}` + ); + } finally { + try { + if (qpdf?.FS) { + try { + qpdf.FS.unlink(inputPath); + } catch (e) { + console.warn('Failed to unlink input file:', e); + } + try { + qpdf.FS.unlink(outputPath); + } catch (e) { + console.warn('Failed to unlink output file:', e); + } + } + } catch (cleanupError) { + console.warn('Failed to cleanup WASM FS:', cleanupError); + } + } +} diff --git a/src/js/ui.ts b/src/js/ui.ts index 2ae7894..0bfc1d6 100644 --- a/src/js/ui.ts +++ b/src/js/ui.ts @@ -913,7 +913,7 @@ export const toolTemplates = { `, -'change-permissions': () => ` + 'change-permissions': () => `

Change PDF Permissions

Modify passwords and permissions without losing quality.

${createFileInputHTML()} @@ -936,7 +936,7 @@ export const toolTemplates = {
-

📝 How It Works

+

How It Works

`, + + 'remove-restrictions': () => ` +

Remove PDF Restrictions

+

Remove security restrictions and unlock PDF permissions for editing and printing.

+ ${createFileInputHTML()} +
+ +`, }; diff --git a/src/js/utils/helpers.ts b/src/js/utils/helpers.ts index 3727fec..0998487 100644 --- a/src/js/utils/helpers.ts +++ b/src/js/utils/helpers.ts @@ -176,4 +176,4 @@ export async function initializeQpdf() { } return qpdfInstance; -} \ No newline at end of file +}