Skip to content

Commit b1fc3fe

Browse files
committed
Merge branch 'feat/pdf-encrypt' into chore/all-my-stuffs
# Conflicts: # components.d.ts # package.json # pnpm-lock.yaml # src/tools/index.ts
2 parents 3bc667f + 2dac7fd commit b1fc3fe

File tree

7 files changed

+317
-1
lines changed

7 files changed

+317
-1
lines changed

.eslintrc.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
module.exports = {
55
root: true,
66
extends: ['@antfu', './.eslintrc-auto-import.json', '@unocss'],
7+
8+
ignorePatterns: ['src/libs/*'],
79

810
rules: {
911
'curly': ['error', 'all'],

.github/workflows/codeql-analysis.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# For most projects, this workflow file will not need changing; you simply need
2+
# to commit it to your repository.
3+
#
4+
# You may wish to alter this file to override the set of languages analyzed,
5+
# or to provide custom queries or build logic.
6+
#
7+
# ******** NOTE ********
8+
# We have attempted to detect the languages in your repository. Please check
9+
# the `language` matrix defined below to confirm you have the correct set of
10+
# supported CodeQL languages.
11+
#
12+
name: "CodeQL"
13+
14+
on:
15+
push:
16+
branches: [ dev ]
17+
pull_request:
18+
# The branches below must be a subset of the branches above
19+
branches: [ dev ]
20+
21+
jobs:
22+
analyze:
23+
name: Analyze
24+
runs-on: ubuntu-latest
25+
permissions:
26+
actions: read
27+
contents: read
28+
security-events: write
29+
30+
strategy:
31+
fail-fast: false
32+
matrix:
33+
language: [ 'javascript' ]
34+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
35+
# Learn more:
36+
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
37+
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
41+
42+
# Initializes the CodeQL tools for scanning.
43+
- name: Initialize CodeQL
44+
uses: github/codeql-action/init@v3
45+
with:
46+
languages: ${{ matrix.language }}
47+
# If you wish to specify custom queries, you can do so here or in a config file.
48+
# By default, queries listed here will override any specified in a config file.
49+
# Prefix the list here with "+" to use these queries and those in the config file.
50+
# queries: ./path/to/local/query, your-org/your-repo/queries@main
51+
config: |
52+
paths-ignore:
53+
- src/libs
54+
55+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
56+
# If this step fails, then you should remove it and run the build manually (see below)
57+
- name: Autobuild
58+
uses: github/codeql-action/autobuild@v2
59+
60+
# ℹ️ Command-line programs to run using the OS shell.
61+
# 📚 https://git.io/JvXDl
62+
63+
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
64+
# and modify them (or add more) to build your code if your project
65+
# uses a compiled language
66+
67+
#- run: |
68+
# make bootstrap
69+
# make release
70+
71+
- name: Perform CodeQL Analysis
72+
uses: github/codeql-action/analyze@v2

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"@types/arr-diff": "^4.0.3",
7575
"@types/markdown-it": "^13.0.7",
7676
"@types/pdfjs-dist": "^2.10.378",
77+
"@types/emscripten": "^1.39.10",
7778
"@types/figlet": "^1.5.8",
7879
"@types/markdown-it": "^13.0.7",
7980
"@vicons/material": "^0.12.0",
@@ -173,6 +174,7 @@
173174
"pinia": "^2.0.34",
174175
"plausible-tracker": "^0.3.8",
175176
"pretty-ms": "^9.1.0",
177+
"qpdf-wasm-esm-embedded": "^1.1.1",
176178
"qrcode": "^1.5.1",
177179
"randexp": "^0.5.3",
178180
"regex": "^4.3.3",

pnpm-lock.yaml

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ import { tool as uuidGenerator } from './uuid-generator';
145145
import { tool as macAddressLookup } from './mac-address-lookup';
146146
import { tool as xmlFormatter } from './xml-formatter';
147147
import { tool as dockerComposeToDockerRunConverter } from './docker-compose-to-docker-run-converter';
148+
import { tool as pdfEncrypt } from './pdf-encrypt';
148149
import { tool as yamlViewer } from './yaml-viewer';
149150
import { tool as barcodeReader } from './barcode-reader';
150151
import { tool as barcodeGenerator } from './barcode-generator';
@@ -170,6 +171,9 @@ export const toolsByCategory: ToolCategory[] = [
170171
certificateKeyParser,
171172
passwordStrengthAnalyser,
172173
pdfSignatureChecker,
174+
passwordStrengthAnalyser,
175+
pdfSignatureChecker,
176+
pdfEncrypt,
173177
],
174178
},
175179
{

src/tools/pdf-encrypt/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Lock } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Pdf Encrypt',
6+
path: '/pdf-encrypt',
7+
description: 'Encrypt and add protection to a PDF File',
8+
keywords: ['pdf', 'encrypt'],
9+
component: () => import('./pdf-encrypt.vue'),
10+
icon: Lock,
11+
createdAt: new Date('2024-01-09'),
12+
});

src/tools/pdf-encrypt/pdf-encrypt.vue

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
<script setup lang="ts">
2+
import { Base64 } from 'js-base64';
3+
import createQPDFModule from 'qpdf-wasm-esm-embedded';
4+
import { useDownloadFileFromBase64Refs } from '@/composable/downloadBase64';
5+
6+
const status = ref<'idle' | 'done' | 'error' | 'processing'>('idle');
7+
const file = ref<File | null>(null);
8+
9+
const restrictAccessibility = useStorage('pdf-encrypt:accessibility', false);
10+
const restrictAnnotate = useStorage('pdf-encrypt:annotate', false);
11+
const restrictAssemble = useStorage('pdf-encrypt:assemble', false);
12+
const restrictExtract = useStorage('pdf-encrypt:extract', false);
13+
const restrictForm = useStorage('pdf-encrypt:form', false);
14+
const restrictModifyOther = useStorage('pdf-encrypt:modoth', false);
15+
const clearTextMetadata = useStorage('pdf-encrypt:clearmeta', false);
16+
const restrictModify = useStorage('pdf-encrypt:mod', 'all');
17+
const restrictPrint = useStorage('pdf-encrypt:print', 'full');
18+
const userPassword = ref('');
19+
const ownerPassword = ref('');
20+
21+
const base64OutputPDF = ref('');
22+
const logs = ref<string[]>([]);
23+
const fileName = ref('');
24+
const fileExtension = ref('pdf');
25+
const { download } = useDownloadFileFromBase64Refs(
26+
{
27+
source: base64OutputPDF,
28+
filename: fileName,
29+
extension: fileExtension,
30+
});
31+
32+
function onFileUploaded(uploadedFile: File) {
33+
file.value = uploadedFile;
34+
fileName.value = `encrypted_${file.value.name}`;
35+
}
36+
37+
async function onProcessClicked() {
38+
if (!file.value) {
39+
return;
40+
}
41+
const fileBuffer = await file.value.arrayBuffer();
42+
43+
status.value = 'processing';
44+
try {
45+
const options = [
46+
'--verbose',
47+
'--encrypt',
48+
];
49+
options.push(`${userPassword.value}`);
50+
options.push(`${ownerPassword.value}`);
51+
options.push('128');
52+
options.push('--use-aes=y');
53+
options.push(`--accessibility=${(restrictAccessibility.value ? 'n' : 'y')}`);
54+
options.push(`--annotate=${(restrictAnnotate.value ? 'n' : 'y')}`);
55+
options.push(`--assemble=${(restrictAssemble.value ? 'n' : 'y')}`);
56+
options.push(`--extract=${(restrictExtract.value ? 'n' : 'y')}`);
57+
options.push(`--form=${(restrictForm.value ? 'n' : 'y')}`);
58+
options.push(`--modify-other=${(restrictModifyOther.value ? 'n' : 'y')}`);
59+
options.push(`--modify=${(restrictModify.value)}`);
60+
options.push(`--print=${(restrictPrint.value)}`);
61+
if (clearTextMetadata.value) {
62+
options.push('--cleartext-metadata');
63+
}
64+
options.push('--');
65+
options.push('in.pdf');
66+
options.push('out.pdf');
67+
const outPdfBuffer = await callMainWithInOutPdf(fileBuffer,
68+
options, 0);
69+
base64OutputPDF.value = `data:application/pdf;base64,${Base64.fromUint8Array(outPdfBuffer)}`;
70+
status.value = 'done';
71+
72+
download();
73+
}
74+
catch (e) {
75+
status.value = 'error';
76+
}
77+
}
78+
79+
async function callMainWithInOutPdf(data: ArrayBuffer, args: string[], expected_exitcode: number) {
80+
logs.value = [];
81+
const mod = await createQPDFModule({
82+
print(text: string) {
83+
logs.value.push(text);
84+
},
85+
printErr(text: string) {
86+
logs.value.push(text);
87+
},
88+
});
89+
mod.FS.writeFile('in.pdf', new Uint8Array(data));
90+
const ret = mod.callMain(args);
91+
if (expected_exitcode !== ret) {
92+
throw new Error('Process run failed');
93+
}
94+
return mod.FS.readFile('out.pdf');
95+
}
96+
97+
const printRestrictionOptions = [{ value: 'none', label: 'Disallow printing' },
98+
{ value: 'low', label: 'Allow only low-resolution printing' },
99+
{ value: 'full', label: 'Allow full printing' },
100+
];
101+
const modificationRestrictionOptions = [
102+
{ value: 'none', label: 'Allow no modifications' },
103+
{ value: 'assembly', label: 'Allow document assembly only' },
104+
{ value: 'form', label: 'Allow document assembly only + filling in form fields and signing' },
105+
{ value: 'annotate', label: 'Allow document assembly only + filling in form fields and signing + commenting and modifying forms' },
106+
{ value: 'all', label: 'Allow full document modification' },
107+
];
108+
</script>
109+
110+
<template>
111+
<div>
112+
<div style="flex: 0 0 100%">
113+
<div mx-auto max-w-600px>
114+
<c-file-upload
115+
title="Drag and drop a PDF file here, or click to select a file"
116+
accept=".pdf"
117+
@file-upload="onFileUploaded"
118+
/>
119+
<div mt-2 text-center>
120+
<strong>Output file:</strong> {{ fileName }}
121+
</div>
122+
</div>
123+
</div>
124+
125+
<c-card title="Permissions" mb-3 mt-3>
126+
<n-space>
127+
<n-checkbox v-model:checked="restrictAccessibility">
128+
Restrict accessibility (usually ignored)
129+
</n-checkbox>
130+
<n-checkbox v-model:checked="restrictAnnotate">
131+
Restrict commenting/filling form fields
132+
</n-checkbox>
133+
<n-checkbox v-model:checked="restrictAssemble">
134+
Restrict document assembly
135+
</n-checkbox>
136+
<n-checkbox v-model:checked="restrictExtract">
137+
Restrict text/graphic extraction
138+
</n-checkbox>
139+
<n-checkbox v-model:checked="restrictForm">
140+
Restrict filling form fields
141+
</n-checkbox>
142+
<n-checkbox v-model:checked="restrictModifyOther">
143+
Restrict other modifications
144+
</n-checkbox>
145+
<n-checkbox v-model:checked="clearTextMetadata">
146+
Prevent encryption of metadata
147+
</n-checkbox>
148+
</n-space>
149+
<c-select
150+
v-model:value="restrictModify"
151+
:options="modificationRestrictionOptions"
152+
label="Control modify access by level"
153+
mt-3
154+
/>
155+
<c-select
156+
v-model:value="restrictPrint"
157+
:options="printRestrictionOptions"
158+
label="Control printing access"
159+
mt-3
160+
/>
161+
</c-card>
162+
<n-form-item
163+
label="Owner password:"
164+
label-placement="left"
165+
mb-1
166+
>
167+
<n-input
168+
:value="ownerPassword"
169+
type="password"
170+
placeholder="Owner password (optional)"
171+
/>
172+
</n-form-item>
173+
174+
<n-form-item
175+
label="User password:"
176+
label-placement="left"
177+
mb-1
178+
>
179+
<n-input
180+
:value="userPassword"
181+
type="password"
182+
placeholder="User password (optional)"
183+
/>
184+
</n-form-item>
185+
186+
<div mt-3 flex justify-center>
187+
<c-button :disabled="!file" @click="onProcessClicked()">
188+
Encrypt PDF
189+
</c-button>
190+
</div>
191+
192+
<n-divider />
193+
194+
<div mt-3 flex justify-center>
195+
<c-alert v-if="status === 'error'" type="error">
196+
An error occured processing {{ fileName }}
197+
</c-alert>
198+
<n-spin
199+
v-if="status === 'processing'"
200+
size="small"
201+
/>
202+
</div>
203+
204+
<c-card title="Logs">
205+
<pre>{{ logs.join('\n') }}</pre>
206+
</c-card>
207+
</div>
208+
</template>

0 commit comments

Comments
 (0)