mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-12-17 17:56:38 +00:00
feat: unlock pdf init
This commit is contained in:
parent
d25f7ca557
commit
94c1acd7ce
146
.idea/workspace.xml
generated
146
.idea/workspace.xml
generated
@ -4,8 +4,15 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: qr code generation init">
|
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: unlock pdf init">
|
||||||
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/unlock-pdf/index.tsx" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/unlock-pdf/meta.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/unlock-pdf/service.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/unlock-pdf/types.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/lib/ghostscript/background-worker.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/ghostscript/background-worker.js" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/lib/ghostscript/worker-init.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/ghostscript/worker-init.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/index.ts" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -22,7 +29,7 @@
|
|||||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
<option name="PUSH_AUTO_UPDATE" value="true" />
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
<map>
|
<map>
|
||||||
<entry key="$PROJECT_DIR$" value="76d615ec7c369b7342e0f276392a4cba9c531aef" />
|
<entry key="$PROJECT_DIR$" value="main" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
@ -199,56 +206,57 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent">{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||||
"Docker.Dockerfile build.executor": "Run",
|
"Docker.Dockerfile build.executor": "Run",
|
||||||
"Docker.Dockerfile.executor": "Run",
|
"Docker.Dockerfile.executor": "Run",
|
||||||
"Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
|
"Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
|
||||||
"Playwright.JoinText Component.executor": "Run",
|
"Playwright.JoinText Component.executor": "Run",
|
||||||
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
||||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"Vitest.compute function (1).executor": "Run",
|
"Vitest.compute function (1).executor": "Run",
|
||||||
"Vitest.compute function.executor": "Run",
|
"Vitest.compute function.executor": "Run",
|
||||||
"Vitest.mergeText.executor": "Run",
|
"Vitest.mergeText.executor": "Run",
|
||||||
"Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
|
"Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
|
||||||
"Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
|
"Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
|
||||||
"Vitest.parsePageRanges.executor": "Run",
|
"Vitest.parsePageRanges.executor": "Run",
|
||||||
"Vitest.removeDuplicateLines function.executor": "Run",
|
"Vitest.removeDuplicateLines function.executor": "Run",
|
||||||
"Vitest.removeDuplicateLines function.newlines option.executor": "Run",
|
"Vitest.removeDuplicateLines function.newlines option.executor": "Run",
|
||||||
"Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
|
"Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
|
||||||
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||||
"Vitest.replaceText function.executor": "Run",
|
"Vitest.replaceText function.executor": "Run",
|
||||||
"Vitest.timeBetweenDates.executor": "Run",
|
"Vitest.timeBetweenDates.executor": "Run",
|
||||||
"git-widget-placeholder": "main",
|
"git-widget-placeholder": "unlock-pdf",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"kotlin-language-version-configured": "true",
|
"kotlin-language-version-configured": "true",
|
||||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/pages/tools/json",
|
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/pages/tools/pdf",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"npm.build.executor": "Run",
|
"npm.build.executor": "Run",
|
||||||
"npm.dev.executor": "Run",
|
"npm.dev.executor": "Run",
|
||||||
"npm.lint.executor": "Run",
|
"npm.lint.executor": "Run",
|
||||||
"npm.prebuild.executor": "Run",
|
"npm.prebuild.executor": "Run",
|
||||||
"npm.script:create:tool.executor": "Run",
|
"npm.script:create:tool.executor": "Run",
|
||||||
"npm.test.executor": "Run",
|
"npm.test.executor": "Run",
|
||||||
"npm.test:e2e.executor": "Run",
|
"npm.test:e2e.executor": "Run",
|
||||||
"npm.test:e2e:run.executor": "Run",
|
"npm.test:e2e:run.executor": "Run",
|
||||||
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
|
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
|
||||||
"project.structure.last.edited": "Problems",
|
"project.structure.last.edited": "Problems",
|
||||||
"project.structure.proportion": "0.0",
|
"project.structure.proportion": "0.0",
|
||||||
"project.structure.side.proportion": "0.2",
|
"project.structure.side.proportion": "0.2",
|
||||||
"settings.editor.selected.configurable": "refactai_advanced_settings",
|
"settings.editor.selected.configurable": "preferences.pluginManager",
|
||||||
"ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
"ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"ts.rename.search.for.js.occurrences": "false",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
}
|
}
|
||||||
}</component>
|
}]]></component>
|
||||||
<component name="ReactDesignerToolWindowState">
|
<component name="ReactDesignerToolWindowState">
|
||||||
<option name="myId2Visible">
|
<option name="myId2Visible">
|
||||||
<map>
|
<map>
|
||||||
@ -260,11 +268,11 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools\pdf" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools\json" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools\json" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\components\input" />
|
|
||||||
</key>
|
</key>
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\lib\ghostscript" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\lib\ghostscript" />
|
||||||
@ -353,11 +361,11 @@
|
|||||||
</list>
|
</list>
|
||||||
<recent_temporary>
|
<recent_temporary>
|
||||||
<list>
|
<list>
|
||||||
|
<item itemvalue="npm.dev" />
|
||||||
<item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" />
|
<item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" />
|
||||||
<item itemvalue="Vitest.parsePageRanges" />
|
<item itemvalue="Vitest.parsePageRanges" />
|
||||||
<item itemvalue="Vitest.timeBetweenDates" />
|
<item itemvalue="Vitest.timeBetweenDates" />
|
||||||
<item itemvalue="Vitest.calculateTimeBetweenDates" />
|
<item itemvalue="Vitest.calculateTimeBetweenDates" />
|
||||||
<item itemvalue="npm.dev" />
|
|
||||||
</list>
|
</list>
|
||||||
</recent_temporary>
|
</recent_temporary>
|
||||||
</component>
|
</component>
|
||||||
@ -462,14 +470,12 @@
|
|||||||
<workItem from="1748026506667" duration="2536000" />
|
<workItem from="1748026506667" duration="2536000" />
|
||||||
<workItem from="1748282636141" duration="478000" />
|
<workItem from="1748282636141" duration="478000" />
|
||||||
<workItem from="1749047510481" duration="879000" />
|
<workItem from="1749047510481" duration="879000" />
|
||||||
</task>
|
<workItem from="1749214774130" duration="1093000" />
|
||||||
<task id="LOCAL-00152" summary="feat: crop png">
|
<workItem from="1749348736119" duration="1186000" />
|
||||||
<option name="closed" value="true" />
|
<workItem from="1749471084697" duration="7000" />
|
||||||
<created>1741492688761</created>
|
<workItem from="1749568940505" duration="21000" />
|
||||||
<option name="number" value="00152" />
|
<workItem from="1749774497094" duration="1313000" />
|
||||||
<option name="presentableId" value="LOCAL-00152" />
|
<workItem from="1749775850632" duration="6240000" />
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1741492688761</updated>
|
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00153" summary="chore: remove unnecessary files">
|
<task id="LOCAL-00153" summary="chore: remove unnecessary files">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@ -855,7 +861,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1749147227565</updated>
|
<updated>1749147227565</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="201" />
|
<task id="LOCAL-00201" summary="docs: readme">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1749348977357</created>
|
||||||
|
<option name="number" value="00201" />
|
||||||
|
<option name="presentableId" value="LOCAL-00201" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1749348977357</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="202" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@ -902,8 +916,6 @@
|
|||||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||||
<option name="CHECK_NEW_TODO" value="false" />
|
<option name="CHECK_NEW_TODO" value="false" />
|
||||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||||
<MESSAGE value="fix: gif speed" />
|
|
||||||
<MESSAGE value="fix: tsc" />
|
|
||||||
<MESSAGE value="fix: background color" />
|
<MESSAGE value="fix: background color" />
|
||||||
<MESSAGE value="docs: github trendings" />
|
<MESSAGE value="docs: github trendings" />
|
||||||
<MESSAGE value="docs: optimize" />
|
<MESSAGE value="docs: optimize" />
|
||||||
@ -927,7 +939,9 @@
|
|||||||
<MESSAGE value="chore: remove unnecessary prop" />
|
<MESSAGE value="chore: remove unnecessary prop" />
|
||||||
<MESSAGE value="fix: compute flow" />
|
<MESSAGE value="fix: compute flow" />
|
||||||
<MESSAGE value="feat: qr code generation init" />
|
<MESSAGE value="feat: qr code generation init" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="feat: qr code generation init" />
|
<MESSAGE value="docs: readme" />
|
||||||
|
<MESSAGE value="feat: unlock pdf init" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat: unlock pdf init" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { COMPRESS_ACTION, PROTECT_ACTION } from './worker-init';
|
import { COMPRESS_ACTION, PROTECT_ACTION, UNLOCK_ACTION } from './worker-init';
|
||||||
|
|
||||||
function loadScript() {
|
function loadScript() {
|
||||||
import('./gs-worker.js');
|
import('./gs-worker.js');
|
||||||
@ -87,7 +87,7 @@ function protectPdf(dataStruct, responseCallback) {
|
|||||||
|
|
||||||
// Validate password
|
// Validate password
|
||||||
if (!password) {
|
if (!password) {
|
||||||
responseCallback({
|
console.error({
|
||||||
error: 'Password is required for encryption',
|
error: 'Password is required for encryption',
|
||||||
url: dataStruct.url
|
url: dataStruct.url
|
||||||
});
|
});
|
||||||
@ -153,6 +153,178 @@ function protectPdf(dataStruct, responseCallback) {
|
|||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unlockPdf(dataStruct, responseCallback) {
|
||||||
|
const speed = dataStruct.speed || 'normal';
|
||||||
|
let passwordListUrl;
|
||||||
|
console.log('unlockPdf', dataStruct);
|
||||||
|
// Determine which password list to download based on speed
|
||||||
|
if (speed === 'fast') {
|
||||||
|
passwordListUrl =
|
||||||
|
'https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Passwords/Common-Credentials/xato-net-10-million-passwords-1000.txt';
|
||||||
|
} else if (speed === 'normal') {
|
||||||
|
passwordListUrl =
|
||||||
|
'https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Passwords/Common-Credentials/xato-net-10-million-passwords-100000.txt';
|
||||||
|
} else {
|
||||||
|
// Default or handle other speeds
|
||||||
|
passwordListUrl =
|
||||||
|
'https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Passwords/Common-Credentials/xato-net-10-million-passwords-100000.txt';
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 1: Download the password list ---
|
||||||
|
var xhrPasswordList = new XMLHttpRequest();
|
||||||
|
xhrPasswordList.open('GET', passwordListUrl);
|
||||||
|
xhrPasswordList.onload = function () {
|
||||||
|
if (xhrPasswordList.status === 200) {
|
||||||
|
const passwordList = xhrPasswordList.responseText
|
||||||
|
.split('\n')
|
||||||
|
.map((p) => p.trim());
|
||||||
|
|
||||||
|
// --- Step 2: Proceed with PDF processing and dictionary attack ---
|
||||||
|
processPdfWithDictionary(dataStruct, responseCallback, passwordList);
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
'Failed to download password list:',
|
||||||
|
xhrPasswordList.statusText
|
||||||
|
);
|
||||||
|
console.error('Failed to download password list');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhrPasswordList.onerror = function () {
|
||||||
|
console.error('Network error while downloading password list.');
|
||||||
|
console.error('Network error while downloading password list');
|
||||||
|
};
|
||||||
|
xhrPasswordList.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function processPdfWithDictionary(dataStruct, responseCallback, passwordList) {
|
||||||
|
var xhrPdf = new XMLHttpRequest();
|
||||||
|
xhrPdf.open('GET', dataStruct.psDataURL);
|
||||||
|
xhrPdf.responseType = 'arraybuffer';
|
||||||
|
xhrPdf.onload = function () {
|
||||||
|
console.log('PDF onload');
|
||||||
|
self.URL.revokeObjectURL(dataStruct.psDataURL);
|
||||||
|
|
||||||
|
let currentPasswordIndex = 0;
|
||||||
|
const tryNextPassword = () => {
|
||||||
|
if (currentPasswordIndex >= passwordList.length) {
|
||||||
|
console.error('All passwords tried. PDF could not be unlocked.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = passwordList[currentPasswordIndex];
|
||||||
|
console.log(
|
||||||
|
`Attempting with password: "${password}" (${currentPasswordIndex + 1}/${
|
||||||
|
passwordList.length
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
|
||||||
|
Module = {
|
||||||
|
preRun: [
|
||||||
|
function () {
|
||||||
|
self.Module.FS.writeFile(
|
||||||
|
'input.pdf',
|
||||||
|
new Uint8Array(xhrPdf.response)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
postRun: [
|
||||||
|
function () {
|
||||||
|
try {
|
||||||
|
var uarray = self.Module.FS.readFile('output.pdf', {
|
||||||
|
encoding: 'binary'
|
||||||
|
});
|
||||||
|
// If readFile succeeds, it means the password was correct
|
||||||
|
var blob = new Blob([uarray], {
|
||||||
|
type: 'application/octet-stream'
|
||||||
|
});
|
||||||
|
var pdfDataURL = self.URL.createObjectURL(blob);
|
||||||
|
console.log(
|
||||||
|
`PDF unlocked successfully with password: "${password}"`
|
||||||
|
);
|
||||||
|
responseCallback({
|
||||||
|
pdfDataURL: pdfDataURL,
|
||||||
|
url: dataStruct.url,
|
||||||
|
type: PROTECT_ACTION
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
arguments: [
|
||||||
|
'-sDEVICE=pdfwrite',
|
||||||
|
'-dCompatibilityLevel=1.4',
|
||||||
|
`-sPDFPassword=${password}`, // Use the current password from the list
|
||||||
|
'-DNOPAUSE',
|
||||||
|
'-dQUIET',
|
||||||
|
'-dBATCH',
|
||||||
|
'-sOutputFile=output.pdf',
|
||||||
|
'input.pdf'
|
||||||
|
],
|
||||||
|
print: function (text) {
|
||||||
|
if (text.includes('Error: Password did not work')) {
|
||||||
|
try {
|
||||||
|
if (self.Module && typeof self.Module.exit === 'function') {
|
||||||
|
self.Module.exit(1);
|
||||||
|
}
|
||||||
|
// Clean up any files that might have been created
|
||||||
|
if (self.Module && self.Module.FS) {
|
||||||
|
try {
|
||||||
|
if (self.Module.FS.readdir('/').includes('input.pdf')) {
|
||||||
|
self.Module.FS.unlink('input.pdf');
|
||||||
|
}
|
||||||
|
if (self.Module.FS.readdir('/').includes('output.pdf')) {
|
||||||
|
self.Module.FS.unlink('output.pdf');
|
||||||
|
}
|
||||||
|
} catch (fsError) {
|
||||||
|
console.log('Filesystem cleanup error:', fsError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exitError) {
|
||||||
|
console.log('Error during module exit:', exitError);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPasswordIndex++;
|
||||||
|
setTimeout(tryNextPassword, 10); // Small delay to prevent tight loop
|
||||||
|
}
|
||||||
|
},
|
||||||
|
printErr: function (text) {
|
||||||
|
// Ghostscript might print errors here if the password is wrong
|
||||||
|
// We need to parse these to determine if it's a password error or another issue
|
||||||
|
console.error('Ghostscript error:', text);
|
||||||
|
},
|
||||||
|
totalDependencies: 0,
|
||||||
|
noExitRuntime: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!self.Module) {
|
||||||
|
self.Module = Module;
|
||||||
|
loadScript(); // Assuming loadScript() loads the Emscripten-compiled Ghostscript
|
||||||
|
} else {
|
||||||
|
self.Module['calledRun'] = false;
|
||||||
|
self.Module['postRun'] = Module.postRun;
|
||||||
|
self.Module['preRun'] = Module.preRun;
|
||||||
|
self.Module['arguments'] = Module.arguments;
|
||||||
|
self.Module.callMain();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the dictionary attack
|
||||||
|
tryNextPassword();
|
||||||
|
};
|
||||||
|
|
||||||
|
xhrPdf.onerror = function () {
|
||||||
|
console.error('Network error while downloading PDF.');
|
||||||
|
};
|
||||||
|
xhrPdf.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming PROTECT_ACTION and loadScript() are defined elsewhere in your worker/global scope.
|
||||||
|
// For example:
|
||||||
|
// const PROTECT_ACTION = 'pdf_protection_status';
|
||||||
|
// function loadScript() {
|
||||||
|
// importScripts('ghostscript_module.js'); // Or whatever your Emscripten output file is named
|
||||||
|
// }
|
||||||
self.addEventListener('message', function ({ data: e }) {
|
self.addEventListener('message', function ({ data: e }) {
|
||||||
console.log('message', e);
|
console.log('message', e);
|
||||||
// e.data contains the message sent to the worker.
|
// e.data contains the message sent to the worker.
|
||||||
@ -160,13 +332,16 @@ self.addEventListener('message', function ({ data: e }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Message received from main script', e.data);
|
console.log('Message received from main script', e.data);
|
||||||
const responseCallback = ({ pdfDataURL, type }) => {
|
const responseCallback = ({ pdfDataURL, type, log }) => {
|
||||||
self.postMessage(pdfDataURL);
|
self.postMessage(pdfDataURL);
|
||||||
|
console.log(log);
|
||||||
};
|
};
|
||||||
if (e.data.type === COMPRESS_ACTION) {
|
if (e.data.type === COMPRESS_ACTION) {
|
||||||
compressPdf(e.data, responseCallback);
|
compressPdf(e.data, responseCallback);
|
||||||
} else if (e.data.type === PROTECT_ACTION) {
|
} else if (e.data.type === PROTECT_ACTION) {
|
||||||
protectPdf(e.data, responseCallback);
|
protectPdf(e.data, responseCallback);
|
||||||
|
} else if (e.data.type === UNLOCK_ACTION) {
|
||||||
|
unlockPdf(e.data, responseCallback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export const COMPRESS_ACTION = 'compress-pdf';
|
export const COMPRESS_ACTION = 'compress-pdf';
|
||||||
export const PROTECT_ACTION = 'protect-pdf';
|
export const PROTECT_ACTION = 'protect-pdf';
|
||||||
|
export const UNLOCK_ACTION = 'unlock-pdf';
|
||||||
|
|
||||||
export async function compressWithGhostScript(dataStruct: {
|
export async function compressWithGhostScript(dataStruct: {
|
||||||
psDataURL: string;
|
psDataURL: string;
|
||||||
@ -14,6 +15,7 @@ export async function compressWithGhostScript(dataStruct: {
|
|||||||
|
|
||||||
export async function protectWithGhostScript(dataStruct: {
|
export async function protectWithGhostScript(dataStruct: {
|
||||||
psDataURL: string;
|
psDataURL: string;
|
||||||
|
password: string;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const worker = getWorker();
|
const worker = getWorker();
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
@ -23,6 +25,18 @@ export async function protectWithGhostScript(dataStruct: {
|
|||||||
return getListener(worker);
|
return getListener(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function unlockWithGhostScript(dataStruct: {
|
||||||
|
psDataURL: string;
|
||||||
|
speed: 'slow' | 'normal' | 'fast';
|
||||||
|
}): Promise<string> {
|
||||||
|
const worker = getWorker();
|
||||||
|
worker.postMessage({
|
||||||
|
data: { ...dataStruct, type: UNLOCK_ACTION },
|
||||||
|
target: 'wasm'
|
||||||
|
});
|
||||||
|
return getListener(worker);
|
||||||
|
}
|
||||||
|
|
||||||
const getListener = (worker: Worker): Promise<string> => {
|
const getListener = (worker: Worker): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const listener = (e: MessageEvent) => {
|
const listener = (e: MessageEvent) => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { DefinedTool } from '@tools/defineTool';
|
|||||||
import { tool as compressPdfTool } from './compress-pdf/meta';
|
import { tool as compressPdfTool } from './compress-pdf/meta';
|
||||||
import { tool as protectPdfTool } from './protect-pdf/meta';
|
import { tool as protectPdfTool } from './protect-pdf/meta';
|
||||||
import { meta as pdfToEpub } from './pdf-to-epub/meta';
|
import { meta as pdfToEpub } from './pdf-to-epub/meta';
|
||||||
|
import { meta as unlockPdfTool } from './unlock-pdf/meta';
|
||||||
|
|
||||||
export const pdfTools: DefinedTool[] = [
|
export const pdfTools: DefinedTool[] = [
|
||||||
splitPdfMeta,
|
splitPdfMeta,
|
||||||
@ -12,5 +13,6 @@ export const pdfTools: DefinedTool[] = [
|
|||||||
compressPdfTool,
|
compressPdfTool,
|
||||||
protectPdfTool,
|
protectPdfTool,
|
||||||
mergePdf,
|
mergePdf,
|
||||||
pdfToEpub
|
pdfToEpub,
|
||||||
|
unlockPdfTool
|
||||||
];
|
];
|
||||||
|
|||||||
109
src/pages/tools/pdf/unlock-pdf/index.tsx
Normal file
109
src/pages/tools/pdf/unlock-pdf/index.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import ToolContent from '@components/ToolContent';
|
||||||
|
import { ToolComponentProps } from '@tools/defineTool';
|
||||||
|
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||||
|
import ToolFileResult from '@components/result/ToolFileResult';
|
||||||
|
import { InitialValuesType, Speed } from './types';
|
||||||
|
import { unlockPdf } from './service';
|
||||||
|
import { CustomSnackBarContext } from '../../../../contexts/CustomSnackBarContext';
|
||||||
|
import RadioWithTextField from '@components/options/RadioWithTextField';
|
||||||
|
import SimpleRadio from '@components/options/SimpleRadio';
|
||||||
|
|
||||||
|
const initialValues: InitialValuesType = {
|
||||||
|
speed: 'normal'
|
||||||
|
};
|
||||||
|
|
||||||
|
const speeds: {
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
value: Speed;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: 'Slow',
|
||||||
|
value: 'slow',
|
||||||
|
description: 'More probable to unlock the PDF, but may take longer.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Normal',
|
||||||
|
value: 'normal',
|
||||||
|
description: 'Balanced between speed and success rate.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Fast',
|
||||||
|
value: 'fast',
|
||||||
|
description:
|
||||||
|
'Faster unlocking, but less thorough; may fail on complex passwords.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function UnlockPdf({
|
||||||
|
title,
|
||||||
|
longDescription
|
||||||
|
}: ToolComponentProps) {
|
||||||
|
const [input, setInput] = useState<File | null>(null);
|
||||||
|
const [result, setResult] = useState<File | null>(null);
|
||||||
|
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||||
|
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||||
|
|
||||||
|
const compute = async (values: InitialValuesType, input: File | null) => {
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsProcessing(true);
|
||||||
|
const protectedPdf = await unlockPdf(input, values);
|
||||||
|
setResult(protectedPdf);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error protecting PDF:', error);
|
||||||
|
showSnackBar(
|
||||||
|
`Failed to protect PDF: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
setResult(null);
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolContent
|
||||||
|
title={title}
|
||||||
|
input={input}
|
||||||
|
setInput={setInput}
|
||||||
|
initialValues={initialValues}
|
||||||
|
compute={compute}
|
||||||
|
inputComponent={
|
||||||
|
<ToolPdfInput
|
||||||
|
value={input}
|
||||||
|
onChange={setInput}
|
||||||
|
accept={['application/pdf']}
|
||||||
|
title={'Input PDF'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
resultComponent={
|
||||||
|
<ToolFileResult
|
||||||
|
title={'Unlocked PDF'}
|
||||||
|
value={result}
|
||||||
|
extension={'pdf'}
|
||||||
|
loading={isProcessing}
|
||||||
|
loadingText={'Unlocking PDF'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
getGroups={({ values, updateField }) => [
|
||||||
|
{
|
||||||
|
title: 'Speed',
|
||||||
|
component: speeds.map(({ label, description, value }) => (
|
||||||
|
<SimpleRadio
|
||||||
|
key={value}
|
||||||
|
checked={value === values.speed}
|
||||||
|
title={label}
|
||||||
|
description={description}
|
||||||
|
onClick={() => updateField('speed', value)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
src/pages/tools/pdf/unlock-pdf/meta.ts
Normal file
24
src/pages/tools/pdf/unlock-pdf/meta.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { defineTool } from '@tools/defineTool';
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
export const meta = defineTool('pdf', {
|
||||||
|
name: 'Unlock PDF',
|
||||||
|
path: 'unlock-pdf',
|
||||||
|
icon: 'material-symbols:lock_open',
|
||||||
|
description:
|
||||||
|
'Remove password protection from your PDF files securely in your browser',
|
||||||
|
shortDescription: 'Unlock PDF files securely',
|
||||||
|
keywords: [
|
||||||
|
'pdf',
|
||||||
|
'unlock',
|
||||||
|
'remove password',
|
||||||
|
'decrypt',
|
||||||
|
'unprotect',
|
||||||
|
'security',
|
||||||
|
'browser',
|
||||||
|
'decryption'
|
||||||
|
],
|
||||||
|
longDescription:
|
||||||
|
'Remove password protection from your PDF files securely in your browser. Your files never leave your device, ensuring complete privacy while unlocking your documents for easier access and sharing.',
|
||||||
|
component: lazy(() => import('./index'))
|
||||||
|
});
|
||||||
23
src/pages/tools/pdf/unlock-pdf/service.ts
Normal file
23
src/pages/tools/pdf/unlock-pdf/service.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { InitialValuesType } from './types';
|
||||||
|
import { loadPDFData } from '../utils';
|
||||||
|
import { unlockWithGhostScript } from '../../../../lib/ghostscript/worker-init';
|
||||||
|
|
||||||
|
export async function unlockPdf(
|
||||||
|
pdfFile: File,
|
||||||
|
options: InitialValuesType
|
||||||
|
): Promise<File> {
|
||||||
|
// Check if file is a PDF
|
||||||
|
if (pdfFile.type !== 'application/pdf') {
|
||||||
|
throw new Error('The provided file is not a PDF');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataObject = {
|
||||||
|
psDataURL: URL.createObjectURL(pdfFile),
|
||||||
|
speed: options.speed
|
||||||
|
};
|
||||||
|
const protectedFileUrl: string = await unlockWithGhostScript(dataObject);
|
||||||
|
return await loadPDFData(
|
||||||
|
protectedFileUrl,
|
||||||
|
pdfFile.name.replace('.pdf', '-unlocked.pdf')
|
||||||
|
);
|
||||||
|
}
|
||||||
5
src/pages/tools/pdf/unlock-pdf/types.ts
Normal file
5
src/pages/tools/pdf/unlock-pdf/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type Speed = 'fast' | 'normal' | 'slow';
|
||||||
|
|
||||||
|
export type InitialValuesType = {
|
||||||
|
speed: Speed;
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user