feat: protect pdf

This commit is contained in:
Ibrahima G. Coulibaly
2025-04-03 18:42:24 +00:00
parent 141a0a3a24
commit 5545f0f344
11 changed files with 369 additions and 58 deletions

View File

@@ -1,10 +1,12 @@
import { COMPRESS_ACTION, PROTECT_ACTION } from './worker-init';
function loadScript() {
import('./gs-worker.js');
}
var Module;
function _GSPS2PDF(dataStruct, responseCallback) {
function compressPdf(dataStruct, responseCallback) {
const compressionLevel = dataStruct.compressionLevel || 'medium';
// Set PDF settings based on compression level
@@ -44,7 +46,11 @@ function _GSPS2PDF(dataStruct, responseCallback) {
});
var blob = new Blob([uarray], { type: 'application/octet-stream' });
var pdfDataURL = self.URL.createObjectURL(blob);
responseCallback({ pdfDataURL: pdfDataURL, url: dataStruct.url });
responseCallback({
pdfDataURL: pdfDataURL,
url: dataStruct.url,
type: COMPRESS_ACTION
});
}
],
arguments: [
@@ -76,6 +82,77 @@ function _GSPS2PDF(dataStruct, responseCallback) {
xhr.send();
}
function protectPdf(dataStruct, responseCallback) {
const password = dataStruct.password || '';
// Validate password
if (!password) {
responseCallback({
error: 'Password is required for encryption',
url: dataStruct.url
});
return;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', dataStruct.psDataURL);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
console.log('onload');
// release the URL
self.URL.revokeObjectURL(dataStruct.psDataURL);
//set up EMScripten environment
Module = {
preRun: [
function () {
self.Module.FS.writeFile('input.pdf', new Uint8Array(xhr.response));
}
],
postRun: [
function () {
var uarray = self.Module.FS.readFile('output.pdf', {
encoding: 'binary'
});
var blob = new Blob([uarray], { type: 'application/octet-stream' });
var pdfDataURL = self.URL.createObjectURL(blob);
responseCallback({
pdfDataURL: pdfDataURL,
url: dataStruct.url,
type: PROTECT_ACTION
});
}
],
arguments: [
'-sDEVICE=pdfwrite',
'-dCompatibilityLevel=1.4',
`-sOwnerPassword=${password}`,
`-sUserPassword=${password}`,
// Permissions (prevent copying/printing/etc)
'-dEncryptionPermissions=-4',
'-DNOPAUSE',
'-dQUIET',
'-dBATCH',
'-sOutputFile=output.pdf',
'input.pdf'
],
print: function (text) {},
printErr: function (text) {},
totalDependencies: 0,
noExitRuntime: 1
};
// Module.setStatus("Loading Ghostscript...");
if (!self.Module) {
self.Module = Module;
loadScript();
} else {
self.Module['calledRun'] = false;
self.Module['postRun'] = Module.postRun;
self.Module['preRun'] = Module.preRun;
self.Module.callMain();
}
};
xhr.send();
}
self.addEventListener('message', function ({ data: e }) {
console.log('message', e);
// e.data contains the message sent to the worker.
@@ -83,7 +160,14 @@ self.addEventListener('message', function ({ data: e }) {
return;
}
console.log('Message received from main script', e.data);
_GSPS2PDF(e.data, ({ pdfDataURL }) => self.postMessage(pdfDataURL));
const responseCallback = ({ pdfDataURL, type }) => {
self.postMessage(pdfDataURL);
};
if (e.data.type === COMPRESS_ACTION) {
compressPdf(e.data, responseCallback);
} else if (e.data.type === PROTECT_ACTION) {
protectPdf(e.data, responseCallback);
}
});
console.log('Worker ready');

View File

@@ -1,15 +0,0 @@
export async function compressWithGhostScript(dataStruct) {
const worker = new Worker(
new URL('./background-worker.js', import.meta.url),
{ type: 'module' }
);
worker.postMessage({ data: dataStruct, target: 'wasm' });
return new Promise((resolve, reject) => {
const listener = (e) => {
resolve(e.data);
worker.removeEventListener('message', listener);
setTimeout(() => worker.terminate(), 0);
};
worker.addEventListener('message', listener);
});
}

View File

@@ -0,0 +1,41 @@
export const COMPRESS_ACTION = 'compress-pdf';
export const PROTECT_ACTION = 'protect-pdf';
export async function compressWithGhostScript(dataStruct: {
psDataURL: string;
}): Promise<string> {
const worker = getWorker();
worker.postMessage({
data: { ...dataStruct, type: COMPRESS_ACTION },
target: 'wasm'
});
return getListener(worker);
}
export async function protectWithGhostScript(dataStruct: {
psDataURL: string;
}): Promise<string> {
const worker = getWorker();
worker.postMessage({
data: { ...dataStruct, type: PROTECT_ACTION },
target: 'wasm'
});
return getListener(worker);
}
const getListener = (worker: Worker): Promise<string> => {
return new Promise((resolve, reject) => {
const listener = (e: MessageEvent) => {
resolve(e.data);
worker.removeEventListener('message', listener);
setTimeout(() => worker.terminate(), 0);
};
worker.addEventListener('message', listener);
});
};
const getWorker = () => {
return new Worker(new URL('./background-worker.js', import.meta.url), {
type: 'module'
});
};