Update peer.paste

This commit is contained in:
Raven Scott
2026-05-27 23:15:26 -04:00
parent 30153bf958
commit bf911b5cdc
6 changed files with 97 additions and 17 deletions
+2
View File
@@ -23,6 +23,7 @@
"dns-packet": "^5.6.1", "dns-packet": "^5.6.1",
"dotenv": "^16.6.1", "dotenv": "^16.6.1",
"express": "^4.22.2", "express": "^4.22.2",
"highlight.js": "^11.11.1",
"holesail": "^2.4.1", "holesail": "^2.4.1",
"holesail-logger": "^1.1.0", "holesail-logger": "^1.1.0",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
@@ -31,6 +32,7 @@
"hyperschema": "^1.21.0", "hyperschema": "^1.21.0",
"hyperswarm": "^4.17.0", "hyperswarm": "^4.17.0",
"inquirer": "^12.9.4", "inquirer": "^12.9.4",
"marked": "^18.0.4",
"node-forge": "^1.4.0", "node-forge": "^1.4.0",
"pidusage": "^3.0.2", "pidusage": "^3.0.2",
"protomux": "^3.11.0", "protomux": "^3.11.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Peer Paste", "name": "Peer Paste",
"version": "1.1.0", "version": "1.2.0",
"domain": "peer.paste", "domain": "peer.paste",
"enabled": true, "enabled": true,
"description": "Temporary P2P text snippets with expiration and burn-after-read options", "description": "Temporary P2P text snippets with expiration and burn-after-read options",
+70 -7
View File
@@ -1,4 +1,6 @@
const crypto = require('crypto'); const crypto = require('crypto');
const fs = require('fs').promises;
const pathMod = require('path');
const sdk = require('../../includes/plugins/sdk'); const sdk = require('../../includes/plugins/sdk');
const COLLECTION = '@peerpaste/pastes'; const COLLECTION = '@peerpaste/pastes';
@@ -13,6 +15,7 @@ const MAX_ATTACHMENT_BYTES = 256 * 1024;
const MAX_LIST_SCAN = 500; const MAX_LIST_SCAN = 500;
const DEFAULT_PAGE_SIZE = 25; const DEFAULT_PAGE_SIZE = 25;
const ENC_PREFIX = 'ENCv1:'; const ENC_PREFIX = 'ENCv1:';
const PROJECT_ROOT = pathMod.resolve(__dirname, '../..');
let cleanupTimer = null; let cleanupTimer = null;
const metrics = { const metrics = {
@@ -132,6 +135,54 @@ function normalizePasteInput(body = {}, existing = null) {
}; };
} }
const VENDOR_ASSETS = {
'marked.min.js': {
path: pathMod.join(PROJECT_ROOT, 'node_modules', 'marked', 'lib', 'marked.umd.js'),
type: 'application/javascript; charset=utf-8'
},
'highlight-dark.css': {
path: pathMod.join(PROJECT_ROOT, 'node_modules', 'highlight.js', 'styles', 'github-dark.min.css'),
type: 'text/css; charset=utf-8'
},
'highlight/es/core.js': {
path: pathMod.join(PROJECT_ROOT, 'node_modules', 'highlight.js', 'es', 'core.js'),
type: 'application/javascript; charset=utf-8'
},
'highlight/es/languages/javascript.js': {
path: pathMod.join(PROJECT_ROOT, 'node_modules', 'highlight.js', 'es', 'languages', 'javascript.js'),
type: 'application/javascript; charset=utf-8'
},
'highlight/es/languages/json.js': {
path: pathMod.join(PROJECT_ROOT, 'node_modules', 'highlight.js', 'es', 'languages', 'json.js'),
type: 'application/javascript; charset=utf-8'
},
'highlight/es/languages/bash.js': {
path: pathMod.join(PROJECT_ROOT, 'node_modules', 'highlight.js', 'es', 'languages', 'bash.js'),
type: 'application/javascript; charset=utf-8'
},
'highlight/es/languages/markdown.js': {
path: pathMod.join(PROJECT_ROOT, 'node_modules', 'highlight.js', 'es', 'languages', 'markdown.js'),
type: 'application/javascript; charset=utf-8'
}
};
async function serveVendorAsset(res, key) {
const asset = VENDOR_ASSETS[key];
if (!asset) return false;
try {
const content = await fs.readFile(asset.path);
res.writeHead(200, {
'Content-Type': asset.type,
'Cache-Control': 'public, max-age=86400'
});
res.end(content);
return true;
} catch (err) {
sdk.log.warn('peer.paste', `Failed to serve vendor asset ${key}: ${err.message}`);
return sdk.router.error(res, 'Vendor asset unavailable', 500);
}
}
function toPublicPaste(paste, includeOwner = false) { function toPublicPaste(paste, includeOwner = false) {
const t = now(); const t = now();
const out = { const out = {
@@ -613,8 +664,13 @@ function renderPastePage(paste) {
const key = await crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']); const key = await crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']);
const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext); const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
const text = new TextDecoder().decode(plain); const text = new TextDecoder().decode(plain);
if (${JSON.stringify(safePaste.format)} === 'markdown' && window.marked && window.DOMPurify) { if (${JSON.stringify(safePaste.format)} === 'markdown' && window.marked) {
out.innerHTML = DOMPurify.sanitize(marked.parse(text)); const sanitize = (html) => String(html)
.replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, '')
.replace(/\\son\\w+="[^"]*"/gi, '')
.replace(/\\son\\w+='[^']*'/gi, '')
.replace(/javascript:/gi, '');
out.innerHTML = sanitize(marked.parse(text));
} else { } else {
out.textContent = text; out.textContent = text;
} }
@@ -636,8 +692,7 @@ function renderPastePage(paste) {
decryptAndRender(''); decryptAndRender('');
} }
</script> </script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js"></script> <script src="/api/vendor/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script>
</body> </body>
</html>`; </html>`;
} }
@@ -672,12 +727,16 @@ function renderPastePage(paste) {
<p><a href="/">Create another paste</a></p> <p><a href="/">Create another paste</a></p>
</div> </div>
${safePaste.format === 'markdown' ${safePaste.format === 'markdown'
? `<script src="https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js"></script> ? `<script src="/api/vendor/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script>
<script> <script>
const el = document.getElementById('md-render'); const el = document.getElementById('md-render');
const src = el.textContent || ''; const src = el.textContent || '';
el.innerHTML = DOMPurify.sanitize(marked.parse(src)); const sanitize = (html) => String(html)
.replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, '')
.replace(/\\son\\w+="[^"]*"/gi, '')
.replace(/\\son\\w+='[^']*'/gi, '')
.replace(/javascript:/gi, '');
el.innerHTML = sanitize(marked.parse(src));
</script>` </script>`
: ''} : ''}
</body> </body>
@@ -1150,6 +1209,10 @@ async function handler(req, res) {
const baseUrl = `https://${process.env.PLUGIN_DOMAIN || 'peer.paste'}`; const baseUrl = `https://${process.env.PLUGIN_DOMAIN || 'peer.paste'}`;
return sdk.router.json(res, buildOpenApi(baseUrl)); return sdk.router.json(res, buildOpenApi(baseUrl));
} }
if (path.startsWith('api/vendor/') && method === 'GET') {
const key = path.slice('api/vendor/'.length);
return serveVendorAsset(res, key);
}
if (path === 'api/health' && method === 'GET') { if (path === 'api/health' && method === 'GET') {
return sdk.router.json(res, { return sdk.router.json(res, {
plugin: 'peer.paste', plugin: 'peer.paste',
@@ -0,0 +1,12 @@
import hljsCore from '/api/vendor/highlight/es/core.js';
import jsLang from '/api/vendor/highlight/es/languages/javascript.js';
import jsonLang from '/api/vendor/highlight/es/languages/json.js';
import bashLang from '/api/vendor/highlight/es/languages/bash.js';
import mdLang from '/api/vendor/highlight/es/languages/markdown.js';
hljsCore.registerLanguage('javascript', jsLang);
hljsCore.registerLanguage('json', jsonLang);
hljsCore.registerLanguage('bash', bashLang);
hljsCore.registerLanguage('markdown', mdLang);
window.hljs = hljsCore;
+3 -8
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>peer.paste</title> <title>peer.paste</title>
<link rel="stylesheet" href="/styles.css" /> <link rel="stylesheet" href="/styles.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/github-dark.min.css" /> <link rel="stylesheet" href="/api/vendor/highlight-dark.css" />
</head> </head>
<body> <body>
<main class="page"> <main class="page">
@@ -90,13 +90,8 @@
<div id="list-loading" class="list-loading hidden">Loading more...</div> <div id="list-loading" class="list-loading hidden">Loading more...</div>
</section> </section>
</main> </main>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js"></script> <script src="/api/vendor/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script> <script type="module" src="/highlight-loader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/languages/javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/languages/json.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/languages/bash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/languages/markdown.min.js"></script>
<script src="/main.js"></script> <script src="/main.js"></script>
</body> </body>
</html> </html>
+9 -1
View File
@@ -2,6 +2,14 @@ function escapeHtml(s) {
return String(s).replace(/[<>&"]/g, (m) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;' }[m])); return String(s).replace(/[<>&"]/g, (m) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;' }[m]));
} }
function sanitizeRenderedHtml(html) {
return String(html)
.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '')
.replace(/\son\w+="[^"]*"/gi, '')
.replace(/\son\w+='[^']*'/gi, '')
.replace(/javascript:/gi, '');
}
function msToHuman(ms) { function msToHuman(ms) {
if (ms <= 0) return 'expired'; if (ms <= 0) return 'expired';
const h = Math.floor(ms / (1000 * 60 * 60)); const h = Math.floor(ms / (1000 * 60 * 60));
@@ -120,7 +128,7 @@ function renderPreview() {
} }
}); });
const html = marked.parse(text); const html = marked.parse(text);
out.innerHTML = window.DOMPurify ? DOMPurify.sanitize(html) : html; out.innerHTML = sanitizeRenderedHtml(html);
if (window.hljs) out.querySelectorAll('pre code').forEach((b) => hljs.highlightElement(b)); if (window.hljs) out.querySelectorAll('pre code').forEach((b) => hljs.highlightElement(b));
} else { } else {
out.innerHTML = `<pre><code>${escapeHtml(text)}</code></pre>`; out.innerHTML = `<pre><code>${escapeHtml(text)}</code></pre>`;