forked from snxraven/p2ns
Update peer.paste
This commit is contained in:
@@ -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,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",
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ function escapeHtml(s) {
|
|||||||
return String(s).replace(/[<>&"]/g, (m) => ({ '<': '<', '>': '>', '&': '&', '"': '"' }[m]));
|
return String(s).replace(/[<>&"]/g, (m) => ({ '<': '<', '>': '>', '&': '&', '"': '"' }[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>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user