secubox-deb/packages/secubox-torrent/lxc/app/stream.js
CyberMind-FR d45803db0c fix(torrent): stream.js Critical/Important review fixes (ref #917)
Critical: Change file.stream({start,end}) → file.createReadStream({start,end})
to match real webtorrent ^2.5.1 API (WHATWG ReadableStream vs Node Readable).

Important #1: RFC 7233 compliance — clamp end >= total to total-1 instead
of rejecting with 416. Only reject when start >= total or start > end.

Important #2: Attach stream error handlers to prevent unhandled crashes
on peer disconnect or missing pieces.

Minor: Add tests for range clamping (bytes=50-999999 on 100-byte file)
and out-of-range fileIdx. All 6 tests passing (green).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 14:51:18 +02:00

45 lines
1.9 KiB
JavaScript

// SPDX-License-Identifier: LicenseRef-CMSD-1.0
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
// Source-Disclosed License — All rights reserved except as expressly granted.
// See LICENCE-CMSD-1.0.md for terms.
/**
* SecuBox-Deb :: secubox-torrent HTTP Range streaming handler
* CyberMind — https://cybermind.fr
*/
const MIME = { mp4: 'video/mp4', webm: 'video/webm', mkv: 'video/x-matroska',
m4v: 'video/mp4', mp3: 'audio/mpeg', m4a: 'audio/mp4', ogg: 'audio/ogg',
flac: 'audio/flac', wav: 'audio/wav' };
export async function handleStream(engine, req, res) {
const t = engine.get(req.params.infohash);
const idx = Number(req.params.fileIdx);
const file = t && t.files && t.files[idx];
if (!file) { res.writeHead(404); res.end('not found'); return; }
const type = mimeOf(file.name);
const total = file.length;
const range = req.headers.range;
if (!range) {
res.writeHead(200, { 'Content-Length': total, 'Content-Type': type, 'Accept-Ranges': 'bytes' });
const stream = file.createReadStream({ start: 0, end: total - 1 });
stream.on('error', () => { try { res.destroy(); } catch {} });
stream.pipe(res);
return;
}
const m = /bytes=(\d+)-(\d*)/.exec(range);
let start = m ? Number(m[1]) : 0;
let end = m && m[2] ? Number(m[2]) : total - 1;
if (end >= total) end = total - 1;
if (start >= total || start > end) {
res.writeHead(416, { 'Content-Range': `bytes */${total}` }); res.end(); return;
}
res.writeHead(206, { 'Content-Range': `bytes ${start}-${end}/${total}`,
'Content-Length': end - start + 1, 'Content-Type': type, 'Accept-Ranges': 'bytes' });
const stream = file.createReadStream({ start, end });
stream.on('error', () => { try { res.destroy(); } catch {} });
stream.pipe(res);
}
function mimeOf(name) { const e = (/\.([A-Za-z0-9]+)$/.exec(name) || [])[1]; return MIME[(e || '').toLowerCase()] || 'application/octet-stream'; }