diff --git a/src/js/config/pdf-tools.ts b/src/js/config/pdf-tools.ts index 56d0cd32..f43cfaad 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 b845c48d..868d936c 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 3c044137..96d796ab 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 7a429927..d94dc66f 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 21c3cbba..b77e2c54 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 a2b5c1f3..e6823f8f 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 261c41eb..34d9cf57 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 00000000..3fe0f762 --- /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 2ae7894a..0bfc1d63 100644 --- a/src/js/ui.ts +++ b/src/js/ui.ts @@ -913,7 +913,7 @@ export const toolTemplates = { `, -'change-permissions': () => ` + 'change-permissions': () => `
Modify passwords and permissions without losing quality.
${createFileInputHTML()} @@ -936,7 +936,7 @@ export const toolTemplates = {Remove security restrictions and unlock PDF permissions for editing and printing.
+ ${createFileInputHTML()} + +This operation will:
+Enter the owner password if the PDF is password-protected
+This tool is intended for legitimate purposes only, such as:
++ Using this tool to bypass copyright protections, violate intellectual property rights, or access documents without authorization may be illegal in your jurisdiction. We are not liable for any misuse of this tool — if you're unsure, consult legal counsel or the document owner before proceeding. +
+