fix: close upload streams and validate cloud auth and ranges

This commit is contained in:
webadderall
2026-09-21 20:33:13 +10:00
parent f43d137ef5
commit 5c04751ea8
4 changed files with 30 additions and 4 deletions
+8 -2
View File
@@ -140,7 +140,9 @@ async function uploadMultipart(options: {
callback(null, chunk);
},
});
const body = createReadStream(options.filePath, { start, end }).pipe(progress);
const source = createReadStream(options.filePath, { start, end });
const body = source.pipe(progress);
source.on("error", (error) => body.destroy(error));
try {
const partResponse = await fetch(
new URL(
@@ -197,6 +199,7 @@ async function uploadMultipart(options: {
}
} finally {
body.destroy();
source.destroy();
}
options.onProgress(confirmedBytes);
@@ -358,7 +361,9 @@ export function registerCloudShareHandlers() {
callback(null, chunk);
},
});
const body = createReadStream(resolvedPath).pipe(progress);
const source = createReadStream(resolvedPath);
const body = source.pipe(progress);
source.on("error", (error) => body.destroy(error));
const uploadHeaders: Record<string, string> = {
"content-type": contentType,
"content-length": String(stat.size),
@@ -383,6 +388,7 @@ export function registerCloudShareHandlers() {
}
} finally {
body.destroy();
source.destroy();
}
}
@@ -1,4 +1,4 @@
-- Require an authenticated Recordly viewer account before posting comments.
-- Optional viewer accounts; posting comments does not require an account.
CREATE TABLE IF NOT EXISTS comment_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
+3 -1
View File
@@ -442,6 +442,7 @@ function dashboardPassword(env) {
async function expectedSessionToken(env) {
const password = dashboardPassword(env);
if (!password) throw new Error('Dashboard sign-in is not configured');
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw', encoder.encode(password), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
@@ -455,6 +456,7 @@ async function isDashboardAuthed(request, env) {
}
async function dashboardCookieAuthed(request, env) {
if (!dashboardPassword(env)) return false;
const cookies = parseCookies(request.headers.get('Cookie') || '');
const sessionToken = cookies['voom_session'];
if (!sessionToken) return false;
@@ -1087,7 +1089,7 @@ async function handleVideoStream(request, env, shareCode) {
const totalSize = object.size; // R2Object.size is the full stored object size
const start = suffix ? Math.max(0, totalSize - r2Range.suffix) : r2Range.offset;
const actualEnd = !suffix && r2Range.length !== undefined ? start + r2Range.length - 1 : totalSize - 1;
const actualEnd = !suffix && r2Range.length !== undefined ? Math.min(totalSize - 1, start + r2Range.length - 1) : totalSize - 1;
return new Response(object.body, {
status: 206,
@@ -544,3 +544,21 @@ it('rejects creating protected shares without a signing secret', async () => {
await response.arrayBuffer();
} finally { lookup.mockRestore(); }
});
it('rejects dashboard cookies without configured secrets instead of crashing', async () => {
const config = { ...env, API_SECRET: undefined, DASHBOARD_PASSWORD: undefined };
const response = await worker.fetch(new Request(`${BASE}/api/videos`, {
headers: { Cookie: 'voom_session=untrusted' },
}), config, {});
expect(response.status).toBe(401);
await response.arrayBuffer();
});
it('clamps a bounded video range to the actual object size', async () => {
const { shareCode } = await createShare();
await completeUpload(shareCode);
const response = await SELF.fetch(`${BASE}/v/${shareCode}`, { headers: { Range: 'bytes=6-200' } });
expect(response.status).toBe(206);
expect(response.headers.get('Content-Range')).toBe('bytes 6-7/8');
expect(response.headers.get('Content-Length')).toBe('2');
expect(Array.from(new Uint8Array(await response.arrayBuffer()))).toEqual([6, 7]);
});