jQuery(function() { const CDN_URL = 'https://cdn.jsdelivr.net/npm/openpgp@5.11.0/dist/openpgp.min.js'; /** * Aggressive Sanitizer: This fixes the "Error during parsing" by * reconstructing the PGP Armor block from scratch, ensuring the * mandatory blank line after headers is present. */ function sanitizeArmor(text) { const lines = text.trim().split(/\r?\n/); let headerLine = ""; let tailLine = ""; let headers = []; let body = []; let checksum = ""; let section = "START"; for (let i = 0; i < lines.length; i++) { let line = lines[i].trim(); if (!line && section !== "BODY") continue; if (line.startsWith('-----BEGIN PGP')) { headerLine = line; section = "HEADER"; continue; } if (line.startsWith('-----END PGP')) { tailLine = line; section = "END"; continue; } if (section === "HEADER") { // If it looks like a header (contains ': '), keep it. if (line.includes(': ')) { headers.push(line); } else if (/^[A-Za-z0-9+/]/.test(line)) { // If it looks like Base64, the body started early (missing blank line) section = "BODY"; body.push(line); } } else if (section === "BODY") { if (line.startsWith('=')) { checksum = line; } else { body.push(line); } } } // Reassemble: Header -> Attributes -> BLANK LINE -> Body -> Checksum -> Tail let result = [headerLine]; if (headers.length > 0) result.push(...headers); result.push(""); // The mandatory empty line result.push(...body); if (checksum) result.push(checksum); result.push(tailLine); return result.join('\n'); } const readFileAsArrayBuffer = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(reader.error); reader.readAsArrayBuffer(file); }); }; function loadOpenPGP(callback) { if (window.openpgp) { callback(); return; } jQuery.getScript(CDN_URL, function() { callback(); }); } function isBinaryData(uint8) { const header = Array.from(uint8.subarray(0, 4)).map(b => String.fromCharCode(b)).join(''); // Detect Zip (PK), PDF (%PDF), or PNG (0x89) if (header.startsWith('PK') || header.startsWith('%PDF') || uint8[0] === 0x89) return true; return uint8.subarray(0, 1024).some(byte => byte === 0); } const $wikiTextarea = jQuery('#wiki__text'); if ($wikiTextarea.length > 0) { // Build UI const $toolContainer = jQuery('
'); const $infoLabel = jQuery('🔐 PGP Engine:'); const $btnGroup = jQuery('
'); const $encButton = jQuery(''); const $fileButton = jQuery(''); const $fileInput = jQuery(''); $btnGroup.append($encButton).append($fileButton).append($fileInput); $toolContainer.append($infoLabel).append($btnGroup); $wikiTextarea.before($toolContainer); // Build Modal const $modalOverlay = jQuery(`

PGP Cryptography

Enter passphrase:

`); jQuery('body').append($modalOverlay); let activeMacroResolve = null; const handleConfirm = () => { const val = jQuery('#pgpblock-modal-pass').val(); jQuery('#pgpblock-modal-pass').val(''); $modalOverlay.hide(); if (activeMacroResolve) activeMacroResolve(val); }; jQuery('#pgpblock-modal-confirm').on('click', handleConfirm); jQuery('#pgpblock-modal-cancel').on('click', () => { $modalOverlay.hide(); if (activeMacroResolve) activeMacroResolve(null); }); jQuery('#pgpblock-modal-pass').on('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); handleConfirm(); } }); function getPassphrase(title) { return new Promise((res) => { activeMacroResolve = res; jQuery('#pgpblock-modal-title').text(title); $modalOverlay.css('display', 'flex'); setTimeout(() => jQuery('#pgpblock-modal-pass').focus(), 50); }); } // Action: File Encrypt $fileButton.on('click', () => $fileInput.click()); $fileInput.on('change', async function(e) { const file = e.target.files[0]; if (!file) return; const pass = await getPassphrase(`Encrypt File: ${file.name}`); if (!pass) { $fileInput.val(''); return; } $fileButton.prop('disabled', true).text('Encrypting...'); loadOpenPGP(async function() { try { const buf = await readFileAsArrayBuffer(file); const msg = await window.openpgp.createMessage({ binary: new Uint8Array(buf), filename: file.name }); const enc = await window.openpgp.encrypt({ message: msg, passwords: [pass], format: 'armored' }); const tag = `\n${enc.trim()}\n\n`; const el = $wikiTextarea[0], pos = el.selectionStart, text = $wikiTextarea.val(); $wikiTextarea.val(text.substring(0, pos) + tag + text.substring(pos)); } catch (err) { alert(err.message); } finally { $fileButton.prop('disabled', false).text('📁 Encrypt & Insert File'); $fileInput.val(''); } }); }); // Action: Encrypt/Decrypt $encButton.on('click', async function(e) { e.preventDefault(); const el = $wikiTextarea[0], caretPos = el.selectionStart, fullText = $wikiTextarea.val(); const pgpRegex = /<(pgp|gpg|pgpfile)([^>]*)>([\s\S]*?)<\/\1>/gi; let blocks = []; let match; while ((match = pgpRegex.exec(fullText)) !== null) { const start = match.index, end = match.index + match[0].length; let dist = (caretPos < start) ? (start - caretPos) : (caretPos > end ? caretPos - end : 0); let fn = ""; const fnMatch = match[2].match(/filename=["']([^"']+)["']/i); if (fnMatch) fn = fnMatch[1]; blocks.push({ start, end, tag: match[1], body: match[3].trim(), isEnc: match[3].includes('-----BEGIN PGP MESSAGE-----'), filename: fn, dist }); } if (!blocks.length) return alert("No PGP blocks found."); blocks.sort((a, b) => a.dist - b.dist); const target = blocks[0]; let pass = await getPassphrase(target.isEnc ? "Decrypting Block" : "Encrypting Block"); if (!pass) return; $encButton.prop('disabled', true).text('Processing...'); loadOpenPGP(async function() { try { let resultBody = ""; if (target.isEnc) { // SANITIZE before readMessage const armored = sanitizeArmor(target.body); const msg = await window.openpgp.readMessage({ armoredMessage: armored }); const { data } = await window.openpgp.decrypt({ message: msg, passwords: [pass], format: 'binary', config: { allowUnauthenticatedMessages: true } }); if (target.tag === 'pgpfile' || isBinaryData(data)) { const blob = new Blob([data], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); document.body.appendChild(a); a.style.display = 'none'; a.href = url; a.download = target.filename || "decrypted_file.bin"; a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); resultBody = target.body; } else { resultBody = new TextDecoder().decode(data).trim(); } } else { const msg = await window.openpgp.createMessage({ text: target.body }); const enc = await window.openpgp.encrypt({ message: msg, passwords: [pass], format: 'armored' }); resultBody = enc.trim(); } const attr = target.filename ? ` filename="${target.filename}"` : ""; const newBlock = `<${target.tag}${attr}>\n${resultBody}\n`; $wikiTextarea.val(fullText.substring(0, target.start) + newBlock + fullText.substring(target.end)); el.selectionStart = el.selectionEnd = target.start; } catch (err) { alert("PGP Error: " + err.message); console.error(err); } finally { $encButton.prop('disabled', false).text('🔐 Encrypt/Decrypt Closest'); } }); }); } });