mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
fix(player): isolate authenticated vlc launches
This commit is contained in:
1 parent
bdf353a3b8
commit
f5090d623e
4 files changed
+97
-27
No files matched your search
@@ -860,18 +860,17 @@ describe('buildVlcEnqueueCommands', () => {
|
||||
).toEqual(['clear', 'add http://stream.example/a.m3u8']);
|
||||
});
|
||||
|
||||
it('attaches per-input HTTP options inline with the add command', () => {
|
||||
it('attaches non-header input options inline with the add command', () => {
|
||||
const commands = buildVlcEnqueueCommands({
|
||||
url: 'http://stream.example/a.m3u8',
|
||||
title: 'Channel One',
|
||||
userAgent: 'Custom/1.0',
|
||||
referer: 'https://referer.example',
|
||||
headers: { 'X-Token': 'abc' },
|
||||
});
|
||||
|
||||
expect(commands[0]).toBe('clear');
|
||||
expect(commands[1]).toBe(
|
||||
'add http://stream.example/a.m3u8 :http-user-agent=Custom/1.0 :http-referrer=https://referer.example :http-header=X-Token: abc :meta-title=Channel One'
|
||||
'add http://stream.example/a.m3u8 :http-user-agent=Custom/1.0 :http-referrer=https://referer.example :meta-title=Channel One'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -897,13 +896,4 @@ describe('buildVlcEnqueueCommands', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('skips empty header values', () => {
|
||||
const commands = buildVlcEnqueueCommands({
|
||||
url: 'http://stream.example/a.m3u8',
|
||||
headers: { 'X-Empty': ' ', 'X-Real': 'value' },
|
||||
});
|
||||
|
||||
expect(commands[1]).toContain(':http-header=X-Real: value');
|
||||
expect(commands[1]).not.toContain('X-Empty');
|
||||
});
|
||||
});
|
||||
@@ -114,6 +114,7 @@ describe('vlc-session.service process lifecycle', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
spawnMock.mockReset();
|
||||
rcWrites.length = 0;
|
||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
(createServer as unknown as jest.Mock).mockImplementation(() => ({
|
||||
@@ -163,6 +164,77 @@ describe('vlc-session.service process lifecycle', () => {
|
||||
expect(session.status).toBe('opened');
|
||||
});
|
||||
|
||||
it('relaunches instead of sending Authorization and Cookie through RC', async () => {
|
||||
const proc = createMockChildProcess();
|
||||
await openTrackedVlcInstance(proc);
|
||||
installRcSocketMock('ack');
|
||||
|
||||
const freshProc = createMockChildProcess();
|
||||
spawnMock.mockReturnValueOnce(freshProc);
|
||||
const openPromise = openVlcPlayer({
|
||||
title: 'Authenticated',
|
||||
url: 'https://example.com/authenticated.m3u8',
|
||||
mainOwnedHeaders: {
|
||||
Authorization: 'Bearer safe-token',
|
||||
Cookie: 'session=safe-cookie',
|
||||
},
|
||||
});
|
||||
await waitForSpawnCallCount(2);
|
||||
freshProc.emit('spawn');
|
||||
const session = await openPromise;
|
||||
|
||||
expect(proc.kill).toHaveBeenCalled();
|
||||
expect(rcWrites).toEqual([]);
|
||||
expect(spawnMock.mock.calls[1][1]).toEqual([
|
||||
':http-header=Authorization: Bearer safe-token',
|
||||
':http-header=Cookie: session=safe-cookie',
|
||||
'https://example.com/authenticated.m3u8',
|
||||
':meta-title=Authenticated',
|
||||
]);
|
||||
expect(spawnMock.mock.calls[1][2]).toEqual({
|
||||
shell: false,
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
expect(freshProc.unref).toHaveBeenCalled();
|
||||
expect(session.status).toBe('opened');
|
||||
});
|
||||
|
||||
it('keeps malicious header syntax inside argv and out of RC', async () => {
|
||||
const proc = createMockChildProcess();
|
||||
await openTrackedVlcInstance(proc);
|
||||
installRcSocketMock('ack');
|
||||
|
||||
const freshProc = createMockChildProcess();
|
||||
spawnMock.mockReturnValueOnce(freshProc);
|
||||
const openPromise = openVlcPlayer({
|
||||
title: 'Authenticated',
|
||||
url: 'https://example.com/authenticated.m3u8',
|
||||
mainOwnedHeaders: {
|
||||
Authorization:
|
||||
'Bearer safe-token :sout=#duplicate{dst=display}',
|
||||
Cookie: 'session=safe-cookie\n:sout=#display',
|
||||
},
|
||||
});
|
||||
await waitForSpawnCallCount(2);
|
||||
freshProc.emit('spawn');
|
||||
await openPromise;
|
||||
|
||||
expect(proc.kill).toHaveBeenCalled();
|
||||
expect(rcWrites).toEqual([]);
|
||||
expect(spawnMock.mock.calls[1][1]).toContain(
|
||||
':http-header=Authorization: Bearer safe-token :sout=#duplicate{dst=display}'
|
||||
);
|
||||
expect(spawnMock.mock.calls[1][1]).toContain(
|
||||
':http-header=Cookie: session=safe-cookie\n:sout=#display'
|
||||
);
|
||||
expect(spawnMock.mock.calls[1][2]).toEqual({
|
||||
shell: false,
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
});
|
||||
|
||||
it('kills the stale instance and spawns fresh when RC reuse fails', async () => {
|
||||
const proc = createMockChildProcess();
|
||||
await openTrackedVlcInstance(proc);
|
||||
|
||||
@@ -114,14 +114,12 @@ describe('vlc-session.service helpers and launch args', () => {
|
||||
title: 'My Title',
|
||||
userAgent: 'UA/1.0',
|
||||
referer: 'https://ref.example',
|
||||
headers: { 'X-A': ' padded ', 'X-Empty': ' ' },
|
||||
startTime: 12.7,
|
||||
})
|
||||
).toEqual([
|
||||
'clear',
|
||||
'add http://srv/1 :http-user-agent=UA/1.0 ' +
|
||||
':http-referrer=https://ref.example ' +
|
||||
':http-header=X-A: padded :meta-title=My Title',
|
||||
':http-referrer=https://ref.example :meta-title=My Title',
|
||||
'seek 12',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -59,7 +59,6 @@ export function buildVlcEnqueueCommands(options: {
|
||||
userAgent?: string;
|
||||
referer?: string;
|
||||
origin?: string;
|
||||
headers?: Record<string, string>;
|
||||
startTime?: number;
|
||||
}): string[] {
|
||||
const inputOptions: string[] = [];
|
||||
@@ -72,12 +71,6 @@ export function buildVlcEnqueueCommands(options: {
|
||||
} else if (options.origin) {
|
||||
inputOptions.push(`:http-referrer=${options.origin}`);
|
||||
}
|
||||
Object.entries(options.headers ?? {}).forEach(([name, value]) => {
|
||||
if (!name || value === undefined || value === null) return;
|
||||
const trimmedValue = String(value).trim();
|
||||
if (!trimmedValue) return;
|
||||
inputOptions.push(`:http-header=${name}: ${trimmedValue}`);
|
||||
});
|
||||
if (options.title) {
|
||||
inputOptions.push(`:meta-title=${options.title}`);
|
||||
}
|
||||
@@ -329,10 +322,6 @@ export async function openVlcPlayer({
|
||||
);
|
||||
const customVlcArguments = store.get(VLC_PLAYER_ARGUMENTS, '');
|
||||
const requestedReuseInstance = store.get(VLC_REUSE_INSTANCE, false);
|
||||
const reuseInstance = shouldReuseVlcInstance(
|
||||
requestedReuseInstance,
|
||||
isFlatpak
|
||||
);
|
||||
const {
|
||||
mergedHeaders,
|
||||
effectiveOrigin,
|
||||
@@ -346,11 +335,27 @@ export async function openVlcPlayer({
|
||||
headers,
|
||||
mainOwnedHeaders,
|
||||
});
|
||||
const hasHttpHeaders = Object.entries(mergedHeaders).some(
|
||||
([name, value]) =>
|
||||
Boolean(name) &&
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
Boolean(String(value).trim())
|
||||
);
|
||||
// VLC RC has no proven escaping for `add` input options. Header-bearing
|
||||
// streams must use a fresh argv launch; a per-process RC port may still
|
||||
// be opened below solely for playback-position polling.
|
||||
const configuredReuseInstance = shouldReuseVlcInstance(
|
||||
requestedReuseInstance,
|
||||
isFlatpak
|
||||
);
|
||||
const reuseInstance = configuredReuseInstance && !hasHttpHeaders;
|
||||
traceExternalPlayer('open vlc player', {
|
||||
path: vlcLaunchContext.playerPath,
|
||||
launchMode: vlcLaunchContext.mode,
|
||||
requestedReuseInstance,
|
||||
reuseInstance,
|
||||
hasHttpHeaders,
|
||||
stream: maskUrlForLogs(url),
|
||||
hasUserAgent: Boolean(effectiveUserAgent),
|
||||
hasReferer: Boolean(effectiveReferer),
|
||||
@@ -361,6 +366,12 @@ export async function openVlcPlayer({
|
||||
parseExternalPlayerArguments(customVlcArguments).length,
|
||||
});
|
||||
|
||||
if (hasHttpHeaders && vlcProcess) {
|
||||
killStoredVlcProcess(
|
||||
'relaunch vlc instead of sending http headers through rc'
|
||||
);
|
||||
}
|
||||
|
||||
if (reuseInstance && vlcProcess && !vlcProcess.killed && vlcRcPort) {
|
||||
traceExternalPlayer('reuse existing vlc instance', {
|
||||
rcPort: vlcRcPort,
|
||||
@@ -372,7 +383,6 @@ export async function openVlcPlayer({
|
||||
userAgent: effectiveUserAgent,
|
||||
referer: effectiveReferer,
|
||||
origin: effectiveOrigin,
|
||||
headers: mergedHeaders,
|
||||
startTime,
|
||||
});
|
||||
await sendVlcRcCommands(vlcRcPort, enqueueCommands);
|
||||
|
||||
Reference in new issue
Block a user