54 lines
2.6 KiB
JavaScript
54 lines
2.6 KiB
JavaScript
/* ============================================================================
|
|
* p2p-admit.js — the ONLY way a content id enters p2p_content
|
|
* (docs/p2p-architecture.md, "Security rules").
|
|
*
|
|
* Callers must already have: the complete file on the server's own disk, its
|
|
* SHA-256 computed BY THE SERVER, and validateMedia() passed. This adds the
|
|
* optional malware scan (P2P_MALWARE_SCAN=1, off by default) and writes the
|
|
* row. The scan command gets the path as its last argument: exit 0 = clean,
|
|
* 1 = infected (rejected), anything else = scanner error (not admitted now).
|
|
* ========================================================================== */
|
|
import { spawn } from 'node:child_process';
|
|
|
|
export function scanFile(path, cmd) {
|
|
const parts = String(cmd).split(/\s+/).filter(Boolean);
|
|
return new Promise((resolve) => {
|
|
let child;
|
|
try { child = spawn(parts[0], [...parts.slice(1), path], { stdio: ['ignore', 'pipe', 'pipe'] }); }
|
|
catch (e) { resolve({ result: 'error', detail: e.message }); return; }
|
|
let out = '';
|
|
child.stdout.on('data', (d) => { out = (out + d).slice(-2000); });
|
|
child.stderr.on('data', (d) => { out = (out + d).slice(-2000); });
|
|
child.on('error', (e) => resolve({ result: 'error', detail: e.message }));
|
|
child.on('close', (code) => resolve(
|
|
code === 0 ? { result: 'clean' } : code === 1 ? { result: 'infected', detail: out.trim() } : { result: 'error', detail: out.trim() || 'exit ' + code },
|
|
));
|
|
});
|
|
}
|
|
|
|
const CID_RE = /^[0-9a-f]{64}$/;
|
|
|
|
// info: { path, cid, videoId, size, height, vcodec, acodec, duration, meta, origin }
|
|
// deps: { cfg (P2P config), upsertContent, scan = scanFile, now = Date.now, log = console }
|
|
// → { ok: true, scan } | { ok: false, reason }
|
|
export async function admitFile(info, deps) {
|
|
const { cfg, upsertContent, scan = scanFile, now = Date.now, log = console } = deps;
|
|
if (!cfg.enabled) return { ok: false, reason: 'p2p disabled' };
|
|
if (!CID_RE.test(String(info.cid || ''))) return { ok: false, reason: 'bad cid' };
|
|
let scanResult = 'skipped';
|
|
if (cfg.malwareScan) {
|
|
const r = await scan(info.path, cfg.scanCmd);
|
|
if (r.result !== 'clean') {
|
|
log.warn?.(`[p2p] ${info.videoId} ${info.cid.slice(0, 12)} not admitted: scan ${r.result} ${r.detail || ''}`);
|
|
return { ok: false, reason: 'scan ' + r.result };
|
|
}
|
|
scanResult = 'clean';
|
|
}
|
|
await upsertContent({
|
|
cid: info.cid, videoId: info.videoId, size: info.size, height: info.height, vcodec: info.vcodec,
|
|
acodec: info.acodec, duration: info.duration, meta: info.meta || {}, origin: info.origin,
|
|
scan: scanResult, now: now(),
|
|
});
|
|
return { ok: true, scan: scanResult };
|
|
}
|