fix(settings): harden network controls

This commit is contained in:
NimBold
2026-07-02 15:43:53 +03:30
parent d274e3af17
commit cfb5a5b6e0
9 changed files with 276 additions and 65 deletions
+27 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useDownloadStore } from './useDownloadStore';
import { getProxyArgs, normalizeCustomProxy, useDownloadStore } from './useDownloadStore';
import { useSettingsStore } from './useSettingsStore';
import * as ipc from '../ipc';
@@ -64,6 +64,32 @@ describe('useDownloadStore', () => {
});
});
it('normalizes proxy settings for download dispatch', async () => {
expect(normalizeCustomProxy('127.0.0.1', 8080)).toBe('http://127.0.0.1:8080');
expect(normalizeCustomProxy(' socks5://127.0.0.1 ', 1080)).toBe('socks5://127.0.0.1:1080');
expect(normalizeCustomProxy('http://proxy.local:9000', 8080)).toBe('http://proxy.local:9000');
expect(normalizeCustomProxy('127.0.0.1', NaN)).toBeNull();
expect(await getProxyArgs({
proxyMode: 'none',
proxyHost: '',
proxyPort: 8080
} as ReturnType<typeof useSettingsStore.getState>)).toBe('none');
vi.mocked(ipc.invokeCommand).mockResolvedValueOnce(null);
expect(await getProxyArgs({
proxyMode: 'system',
proxyHost: '',
proxyPort: 8080
} as ReturnType<typeof useSettingsStore.getState>)).toBe('none');
expect(await getProxyArgs({
proxyMode: 'custom',
proxyHost: 'socks5://127.0.0.1',
proxyPort: 1080
} as ReturnType<typeof useSettingsStore.getState>)).toBe('socks5://127.0.0.1:1080');
});
it('Start Queue dispatches exactly once for mixed dispatched/undispatched items', async () => {
useDownloadStore.setState({
downloads: [
+23 -5
View File
@@ -54,7 +54,7 @@ export async function dispatchItem(id: string): Promise<boolean> {
checksum: item.checksum || null,
cookies: item.cookies || null,
mirrors: item.mirrors || null,
user_agent: settings.customUserAgent || null,
user_agent: settings.customUserAgent.trim() || null,
max_tries: settings.maxAutomaticRetries,
proxy: await getProxyArgs(settings),
format_selector: item.mediaFormatSelector || null,
@@ -87,7 +87,25 @@ export async function dispatchItem(id: string): Promise<boolean> {
return promise;
}
const getProxyArgs = async (settings: ReturnType<typeof useSettingsStore.getState>) => {
export const normalizeCustomProxy = (host: string, port: number): string | null => {
const trimmedHost = host.trim();
const normalizedPort = Number.isFinite(port) ? Math.trunc(port) : NaN;
if (!trimmedHost || !Number.isFinite(normalizedPort) || normalizedPort < 1 || normalizedPort > 65535) return null;
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmedHost)) {
try {
const parsed = new URL(trimmedHost);
if (!parsed.port) parsed.port = String(normalizedPort);
return parsed.toString().replace(/\/$/, '');
} catch {
return null;
}
}
return `http://${trimmedHost}:${normalizedPort}`;
};
export const getProxyArgs = async (settings: ReturnType<typeof useSettingsStore.getState>) => {
if (settings.proxyMode === 'system') {
try {
const sysProxy = await invoke('get_system_proxy');
@@ -97,8 +115,8 @@ const getProxyArgs = async (settings: ReturnType<typeof useSettingsStore.getStat
return "none";
}
}
if (settings.proxyMode === 'custom' && settings.proxyHost) {
return `http://${settings.proxyHost}:${settings.proxyPort}`;
if (settings.proxyMode === 'custom') {
return normalizeCustomProxy(settings.proxyHost, settings.proxyPort) ?? "none";
}
if (settings.proxyMode === 'none') {
return "none";
@@ -778,7 +796,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
checksum: item.checksum || null,
cookies: item.cookies || null,
mirrors: item.mirrors || null,
user_agent: settings.customUserAgent || null,
user_agent: settings.customUserAgent.trim() || null,
max_tries: settings.maxAutomaticRetries,
proxy: await getProxyArgs(settings),
format_selector: item.mediaFormatSelector || null,
+5 -1
View File
@@ -274,7 +274,11 @@ export const useSettingsStore = create<SettingsState>()(
setShowMenuBarIcon: (showMenuBarIcon) => set({ showMenuBarIcon }),
setProxyMode: (proxyMode) => set({ proxyMode }),
setProxyHost: (proxyHost) => set({ proxyHost }),
setProxyPort: (proxyPort) => set({ proxyPort }),
setProxyPort: (proxyPort) => set({
proxyPort: Number.isFinite(proxyPort)
? Math.min(65535, Math.max(1, Math.trunc(proxyPort)))
: 8080
}),
setCustomUserAgent: (customUserAgent) => set({ customUserAgent }),
setAskWhereToSaveEachFile: (askWhereToSaveEachFile) => set({ askWhereToSaveEachFile }),
setPreventsSleepWhileDownloading: (preventsSleepWhileDownloading) => {