mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-22 12:13:43 +08:00
92 lines
2.9 KiB
TypeScript
92 lines
2.9 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
OutboundPolicyError,
|
|
assertOutboundUrlAllowed,
|
|
fetchWithPolicy,
|
|
getRelayForwardHeaders,
|
|
sanitizeHeaderMap,
|
|
sanitizeReferer,
|
|
sanitizeUserAgent,
|
|
} from '@/lib/server/outbound-policy';
|
|
|
|
test('assertOutboundUrlAllowed rejects non-http protocols and private IPs by default', async () => {
|
|
await assert.rejects(
|
|
assertOutboundUrlAllowed('ftp://example.com/video.m3u8'),
|
|
(error: unknown) =>
|
|
error instanceof OutboundPolicyError && error.code === 'UNSUPPORTED_OUTBOUND_PROTOCOL',
|
|
);
|
|
|
|
await assert.rejects(
|
|
assertOutboundUrlAllowed('http://127.0.0.1/stream.m3u8'),
|
|
(error: unknown) =>
|
|
error instanceof OutboundPolicyError && error.code === 'PRIVATE_OUTBOUND_TARGET',
|
|
);
|
|
});
|
|
|
|
test('assertOutboundUrlAllowed permits explicitly allowlisted private hosts', async () => {
|
|
const previousAllowlist = process.env.KVIDEO_OUTBOUND_PRIVATE_HOST_ALLOWLIST;
|
|
process.env.KVIDEO_OUTBOUND_PRIVATE_HOST_ALLOWLIST = '127.0.0.1,lan.example';
|
|
|
|
try {
|
|
const url = await assertOutboundUrlAllowed('http://127.0.0.1/live.m3u8');
|
|
assert.equal(url.hostname, '127.0.0.1');
|
|
} finally {
|
|
process.env.KVIDEO_OUTBOUND_PRIVATE_HOST_ALLOWLIST = previousAllowlist;
|
|
}
|
|
});
|
|
|
|
test('fetchWithPolicy blocks redirects into private ranges', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = (async () =>
|
|
new Response(null, {
|
|
status: 302,
|
|
headers: {
|
|
location: 'http://127.0.0.1/private.m3u8',
|
|
},
|
|
})) as typeof fetch;
|
|
|
|
try {
|
|
await assert.rejects(
|
|
fetchWithPolicy('https://1.1.1.1/public.m3u8'),
|
|
(error: unknown) =>
|
|
error instanceof OutboundPolicyError && error.code === 'PRIVATE_OUTBOUND_TARGET',
|
|
);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test('relay header sanitizers keep the safe forwarding surface small', async () => {
|
|
const sanitizedHeaders = sanitizeHeaderMap({
|
|
Range: 'bytes=0-1024',
|
|
Cookie: 'session=secret',
|
|
Referer: 'https://1.1.1.1/watch',
|
|
'X-Forwarded-For': '1.2.3.4',
|
|
});
|
|
|
|
assert.deepEqual(sanitizedHeaders, {
|
|
Range: 'bytes=0-1024',
|
|
Referer: 'https://1.1.1.1/watch',
|
|
});
|
|
|
|
const request = new Request('https://kvideo.example/api/proxy?url=https://1.1.1.1/test', {
|
|
headers: {
|
|
Range: 'bytes=100-200',
|
|
Cookie: 'session=secret',
|
|
},
|
|
});
|
|
|
|
const forwardHeaders = getRelayForwardHeaders(request, {
|
|
Referer: 'https://1.1.1.1/watch',
|
|
'User-Agent': sanitizeUserAgent('KVideo Test Agent'.repeat(40))!,
|
|
});
|
|
|
|
assert.equal(forwardHeaders.get('Range'), 'bytes=100-200');
|
|
assert.equal(forwardHeaders.get('Cookie'), null);
|
|
assert.equal(forwardHeaders.get('Referer'), 'https://1.1.1.1/watch');
|
|
assert.ok((forwardHeaders.get('User-Agent') || '').length <= 512);
|
|
|
|
assert.equal(await sanitizeReferer('https://1.1.1.1/watch'), 'https://1.1.1.1/watch');
|
|
});
|