nodebb-plugin-pdf-secure 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,14 +34,38 @@ Controllers.servePdfBinary = async function (req, res) {
34
34
  pdfBuffer = await pdfHandler.getSinglePagePdf(data.file);
35
35
  }
36
36
 
37
+ // PARTIAL XOR Encryption: Sadece ilk ve son 8KB'ı şifrele
38
+ // Bu, PDF'yi açılamaz hale getirir ama performans etkisi minimal
39
+ const encryptionKey = Buffer.from(nonce.slice(0, 8));
40
+ const encryptedBuffer = Buffer.from(pdfBuffer); // Copy
41
+ const chunkSize = 8192; // 8KB
42
+
43
+ // İlk 8KB'ı şifrele (PDF header - CRITICAL)
44
+ const headerEnd = Math.min(chunkSize, pdfBuffer.length);
45
+ for (let i = 0; i < headerEnd; i++) {
46
+ encryptedBuffer[i] = pdfBuffer[i] ^ encryptionKey[i % encryptionKey.length];
47
+ }
48
+
49
+ // Son 8KB'ı şifrele (PDF xref table - CRITICAL)
50
+ const footerStart = Math.max(0, pdfBuffer.length - chunkSize);
51
+ for (let i = footerStart; i < pdfBuffer.length; i++) {
52
+ encryptedBuffer[i] = pdfBuffer[i] ^ encryptionKey[i % encryptionKey.length];
53
+ }
54
+
37
55
  res.set({
38
56
  'Content-Type': 'application/octet-stream',
39
- 'Cache-Control': 'no-store, no-cache, must-revalidate, private',
57
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, private, max-age=0',
58
+ 'Pragma': 'no-cache',
59
+ 'Expires': '0',
40
60
  'X-Content-Type-Options': 'nosniff',
61
+ 'X-Download-Options': 'noopen',
62
+ 'X-Frame-Options': 'SAMEORIGIN',
63
+ 'Content-Security-Policy': "default-src 'none'",
41
64
  'Content-Disposition': 'inline',
65
+ 'X-PDF-Encrypted': '1', // İşaretleyici
42
66
  });
43
67
 
44
- return res.send(pdfBuffer);
68
+ return res.send(encryptedBuffer);
45
69
  } catch (err) {
46
70
  if (err.message === 'File not found') {
47
71
  return res.status(404).json({ error: 'PDF not found' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Secure PDF viewer plugin for NodeBB - prevents downloading, enables canvas-only rendering with Premium group support",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -32,6 +32,7 @@ function interceptPdfLinks() {
32
32
 
33
33
  var container = document.createElement('div');
34
34
  container.className = 'pdf-secure-inline';
35
+ container.setAttribute('tabindex', '0'); // Focus için gerekli
35
36
  container.innerHTML =
36
37
  '<div class="pdf-secure-inline-header">' +
37
38
  '<i class="fa fa-file-pdf-o"></i> ' +
@@ -40,7 +41,10 @@ function interceptPdfLinks() {
40
41
  '<div class="pdf-secure-inline-body">' +
41
42
  '<div class="pdf-secure-loading">Loading PDF...</div>' +
42
43
  '<div class="pdf-secure-error"></div>' +
43
- '<canvas class="pdf-secure-canvas"></canvas>' +
44
+ '<div class="pdf-secure-canvas-wrapper">' +
45
+ '<canvas class="pdf-secure-canvas"></canvas>' +
46
+ '<div class="pdf-secure-overlay"></div>' +
47
+ '</div>' +
44
48
  '</div>' +
45
49
  '<div class="pdf-secure-inline-footer">' +
46
50
  '<button class="pdf-secure-prev" disabled>&#8249; Prev</button>' +
@@ -106,8 +110,29 @@ async function loadPdf(container, filename) {
106
110
  return;
107
111
  }
108
112
 
109
- var pdfArrayBuffer = await pdfRes.arrayBuffer();
110
- console.log('[PDF-Secure][Viewer] Step 2 - PDF loaded:', pdfArrayBuffer.byteLength, 'bytes');
113
+ var encryptedArrayBuffer = await pdfRes.arrayBuffer();
114
+ console.log('[PDF-Secure][Viewer] Step 2 - Encrypted data loaded:', encryptedArrayBuffer.byteLength, 'bytes');
115
+
116
+ // PARTIAL XOR Decryption: Sadece ilk ve son 8KB'ı decrypt et
117
+ var encryptedBytes = new Uint8Array(encryptedArrayBuffer);
118
+ var encryptionKey = new TextEncoder().encode(nonce.slice(0, 8));
119
+ var decryptedBytes = new Uint8Array(encryptedBytes); // Copy
120
+ var chunkSize = 8192; // 8KB
121
+
122
+ // İlk 8KB'ı decrypt et (PDF header)
123
+ var headerEnd = Math.min(chunkSize, encryptedBytes.length);
124
+ for (var i = 0; i < headerEnd; i++) {
125
+ decryptedBytes[i] = encryptedBytes[i] ^ encryptionKey[i % encryptionKey.length];
126
+ }
127
+
128
+ // Son 8KB'ı decrypt et (PDF xref table)
129
+ var footerStart = Math.max(0, encryptedBytes.length - chunkSize);
130
+ for (var i = footerStart; i < encryptedBytes.length; i++) {
131
+ decryptedBytes[i] = encryptedBytes[i] ^ encryptionKey[i % encryptionKey.length];
132
+ }
133
+
134
+ var pdfArrayBuffer = decryptedBytes.buffer;
135
+ console.log('[PDF-Secure][Viewer] Step 2 - Decrypted PDF:', pdfArrayBuffer.byteLength, 'bytes');
111
136
 
112
137
  // Step 3: Render PDF
113
138
  console.log('[PDF-Secure][Viewer] Step 3 - Rendering...');
@@ -122,6 +147,41 @@ async function loadPdf(container, filename) {
122
147
  container.addEventListener('contextmenu', function (e) { e.preventDefault(); });
123
148
  container.addEventListener('dragstart', function (e) { e.preventDefault(); });
124
149
  container.addEventListener('selectstart', function (e) { e.preventDefault(); });
150
+ container.addEventListener('copy', function (e) { e.preventDefault(); });
151
+
152
+ // Keyboard shortcuts engelleme (Ctrl+S, Ctrl+P, Ctrl+C, Print Screen)
153
+ container.addEventListener('keydown', function (e) {
154
+ // Ctrl/Cmd + S (Save)
155
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
156
+ e.preventDefault();
157
+ console.log('[PDF-Secure] Save blocked');
158
+ return false;
159
+ }
160
+ // Ctrl/Cmd + P (Print)
161
+ if ((e.ctrlKey || e.metaKey) && e.key === 'p') {
162
+ e.preventDefault();
163
+ console.log('[PDF-Secure] Print blocked');
164
+ return false;
165
+ }
166
+ // Ctrl/Cmd + C (Copy)
167
+ if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
168
+ e.preventDefault();
169
+ console.log('[PDF-Secure] Copy blocked');
170
+ return false;
171
+ }
172
+ // Print Screen
173
+ if (e.key === 'PrintScreen') {
174
+ e.preventDefault();
175
+ console.log('[PDF-Secure] PrintScreen blocked');
176
+ return false;
177
+ }
178
+ // F12 (DevTools)
179
+ if (e.key === 'F12') {
180
+ e.preventDefault();
181
+ console.log('[PDF-Secure] F12 blocked');
182
+ return false;
183
+ }
184
+ });
125
185
 
126
186
  var ctx = canvas.getContext('2d');
127
187
  var currentPage = 1;
package/static/style.less CHANGED
@@ -136,7 +136,142 @@
136
136
 
137
137
  /* Anti-print */
138
138
  @media print {
139
- .pdf-secure-embed {
139
+ .pdf-secure-embed,
140
+ .pdf-secure-inline {
140
141
  display: none !important;
141
142
  }
143
+ }
144
+
145
+ /* Inline PDF Viewer Styles */
146
+ .pdf-secure-inline {
147
+ margin: 20px 0;
148
+ border: 1px solid #ddd;
149
+ border-radius: 8px;
150
+ overflow: hidden;
151
+ background: #f5f5f5;
152
+ user-select: none;
153
+ -webkit-user-select: none;
154
+ -moz-user-select: none;
155
+ -ms-user-select: none;
156
+ }
157
+
158
+ .pdf-secure-inline-header {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 8px;
162
+ padding: 12px 16px;
163
+ background: linear-gradient(to bottom, #fff, #f8f8f8);
164
+ border-bottom: 1px solid #ddd;
165
+ font-weight: 500;
166
+ color: #333;
167
+ }
168
+
169
+ .pdf-secure-inline-header i {
170
+ color: #e81224;
171
+ font-size: 18px;
172
+ }
173
+
174
+ .pdf-secure-filename {
175
+ flex: 1;
176
+ overflow: hidden;
177
+ text-overflow: ellipsis;
178
+ white-space: nowrap;
179
+ }
180
+
181
+ .pdf-secure-inline-body {
182
+ position: relative;
183
+ min-height: 400px;
184
+ background: #fff;
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ }
189
+
190
+ .pdf-secure-loading {
191
+ position: absolute;
192
+ top: 50%;
193
+ left: 50%;
194
+ transform: translate(-50%, -50%);
195
+ color: #666;
196
+ font-size: 14px;
197
+ }
198
+
199
+ .pdf-secure-error {
200
+ display: none;
201
+ position: absolute;
202
+ top: 50%;
203
+ left: 50%;
204
+ transform: translate(-50%, -50%);
205
+ color: #e81224;
206
+ font-size: 14px;
207
+ text-align: center;
208
+ padding: 20px;
209
+ }
210
+
211
+ .pdf-secure-canvas-wrapper {
212
+ position: relative;
213
+ display: inline-block;
214
+ /* Canvas wrapper'ı güvenlik için kapsayıcı */
215
+ }
216
+
217
+ .pdf-secure-canvas {
218
+ display: none;
219
+ max-width: 100%;
220
+ height: auto;
221
+ /* Canvas üzerinde sağ tık ve sürüklemeyi engelle */
222
+ pointer-events: auto;
223
+ }
224
+
225
+ .pdf-secure-overlay {
226
+ position: absolute;
227
+ top: 0;
228
+ left: 0;
229
+ right: 0;
230
+ bottom: 0;
231
+ background: transparent;
232
+ /* Görünmez koruma katmanı - tüm mouse eventlerini yakalar */
233
+ pointer-events: auto;
234
+ cursor: default;
235
+ z-index: 1;
236
+ }
237
+
238
+ .pdf-secure-inline-footer {
239
+ display: none;
240
+ align-items: center;
241
+ justify-content: center;
242
+ gap: 16px;
243
+ padding: 12px;
244
+ background: #f8f8f8;
245
+ border-top: 1px solid #ddd;
246
+ }
247
+
248
+ .pdf-secure-prev,
249
+ .pdf-secure-next {
250
+ padding: 6px 12px;
251
+ background: #fff;
252
+ border: 1px solid #ddd;
253
+ border-radius: 4px;
254
+ cursor: pointer;
255
+ font-size: 14px;
256
+ color: #333;
257
+ transition: all 0.2s;
258
+ }
259
+
260
+ .pdf-secure-prev:hover:not(:disabled),
261
+ .pdf-secure-next:hover:not(:disabled) {
262
+ background: #f0f0f0;
263
+ border-color: #999;
264
+ }
265
+
266
+ .pdf-secure-prev:disabled,
267
+ .pdf-secure-next:disabled {
268
+ opacity: 0.4;
269
+ cursor: not-allowed;
270
+ }
271
+
272
+ .pdf-secure-page-info {
273
+ font-size: 14px;
274
+ color: #666;
275
+ min-width: 60px;
276
+ text-align: center;
142
277
  }