mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-04 00:18:41 +00:00
fix(settings): harden network controls
This commit is contained in:
@@ -736,8 +736,8 @@ fn build_client(payload: &DownloadPayload) -> Result<(Client, HeaderMap), String
|
||||
{
|
||||
builder = builder.user_agent(user_agent);
|
||||
}
|
||||
if let Some(proxy) = payload.proxy.as_deref().filter(|value| !value.is_empty()) {
|
||||
if proxy == "none" {
|
||||
if let Some(proxy) = payload.proxy.as_deref().map(str::trim).filter(|value| !value.is_empty()) {
|
||||
if proxy.eq_ignore_ascii_case("none") {
|
||||
builder = builder.no_proxy();
|
||||
} else {
|
||||
builder = builder.proxy(
|
||||
|
||||
@@ -904,13 +904,14 @@ async fn fetch_metadata(
|
||||
let mut builder = reqwest::Client::builder().redirect(reqwest::redirect::Policy::none());
|
||||
|
||||
if let Some(ref ua) = user_agent {
|
||||
let ua = ua.trim();
|
||||
if !ua.is_empty() {
|
||||
builder = builder.user_agent(ua);
|
||||
} else {
|
||||
builder = builder.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
builder = builder.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36");
|
||||
}
|
||||
} else {
|
||||
builder = builder.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
builder = builder.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36");
|
||||
}
|
||||
|
||||
let resolved_addr = validate_url_ssrf(¤t_url).await?;
|
||||
@@ -2544,8 +2545,8 @@ pub(crate) async fn start_media_download_internal(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(p) = proxy.as_ref().filter(|s| !s.is_empty()) {
|
||||
if p == "none" {
|
||||
if let Some(p) = proxy.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
|
||||
if p.eq_ignore_ascii_case("none") {
|
||||
cmd = cmd.arg("--proxy").arg("");
|
||||
} else {
|
||||
cmd = cmd.arg("--proxy").arg(p);
|
||||
|
||||
@@ -957,8 +957,10 @@ async fn probe_bounded_range_support(
|
||||
.redirect(reqwest::redirect::Policy::limited(5))
|
||||
.timeout(std::time::Duration::from_secs(10));
|
||||
|
||||
if let Some(proxy) = payload.proxy.as_deref().filter(|value| !value.is_empty()) {
|
||||
if proxy != "none" {
|
||||
if let Some(proxy) = payload.proxy.as_deref().map(str::trim).filter(|value| !value.is_empty()) {
|
||||
if proxy.eq_ignore_ascii_case("none") {
|
||||
builder = builder.no_proxy();
|
||||
} else {
|
||||
builder = builder.proxy(reqwest::Proxy::all(proxy).map_err(|error| error.to_string())?);
|
||||
}
|
||||
}
|
||||
@@ -1129,8 +1131,8 @@ impl SidecarSpawner for ProductionSpawner {
|
||||
if !header_list.is_empty() {
|
||||
options.insert("header".to_string(), serde_json::json!(header_list));
|
||||
}
|
||||
if let Some(prox) = payload.proxy.as_deref().filter(|s| !s.is_empty()) {
|
||||
if prox == "none" {
|
||||
if let Some(prox) = payload.proxy.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
|
||||
if prox.eq_ignore_ascii_case("none") {
|
||||
options.insert("all-proxy".to_string(), serde_json::json!(""));
|
||||
} else {
|
||||
options.insert("all-proxy".to_string(), serde_json::json!(prox));
|
||||
|
||||
@@ -245,7 +245,7 @@ export const AddDownloadsModal = () => {
|
||||
}
|
||||
const meta = await invoke('fetch_metadata', {
|
||||
url: row.sourceUrl,
|
||||
userAgent: settingsStore.customUserAgent || null,
|
||||
userAgent: settingsStore.customUserAgent.trim() || null,
|
||||
username: useAuth ? username.trim() || null : login?.username || null,
|
||||
password: useAuth ? password || null : keychainPassword,
|
||||
headers: headers?.trim() || null,
|
||||
|
||||
@@ -56,6 +56,39 @@ const upsertEngineStatus = (items: EngineStatusItem[], item: EngineStatusItem) =
|
||||
return next;
|
||||
};
|
||||
|
||||
const USER_AGENT_SUGGESTIONS = [
|
||||
{
|
||||
label: 'Chrome (Windows)',
|
||||
detail: 'Windows desktop',
|
||||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36'
|
||||
},
|
||||
{
|
||||
label: 'Chrome (macOS)',
|
||||
detail: 'macOS desktop',
|
||||
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36'
|
||||
},
|
||||
{
|
||||
label: 'Edge (Windows)',
|
||||
detail: 'Windows desktop',
|
||||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0'
|
||||
},
|
||||
{
|
||||
label: 'Firefox (Windows)',
|
||||
detail: 'Windows desktop',
|
||||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0'
|
||||
},
|
||||
{
|
||||
label: 'Firefox (macOS)',
|
||||
detail: 'macOS desktop',
|
||||
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0'
|
||||
},
|
||||
{
|
||||
label: 'Safari (macOS)',
|
||||
detail: 'macOS desktop',
|
||||
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15'
|
||||
}
|
||||
] as const;
|
||||
|
||||
const buildEngineStatusError = (check: EngineCheck, error: unknown): EngineStatusItem => ({
|
||||
name: check.name,
|
||||
kind: check.kind,
|
||||
@@ -200,6 +233,8 @@ export default function SettingsView() {
|
||||
: platform.os === 'linux'
|
||||
? 'Provides quick access from the desktop tray or status area when available.'
|
||||
: 'Provides quick access from the OS tray area when available.';
|
||||
const userAgentMenuRef = useRef<HTMLDivElement>(null);
|
||||
const [isUserAgentMenuOpen, setIsUserAgentMenuOpen] = useState(false);
|
||||
|
||||
// Local state for engine status
|
||||
const [engineStatus, setEngineStatus] = useState<EngineStatusItem[] | null>(null);
|
||||
@@ -702,12 +737,15 @@ runEngineChecks(false);
|
||||
<div className="settings-pane max-w-[720px]">
|
||||
<h2 className="settings-section-title">Proxy</h2>
|
||||
<div className="mac-settings-group">
|
||||
<div className="mac-settings-row settings-choice-row">
|
||||
<span className="text-[13px] text-text-primary pt-0.5">Mode</span>
|
||||
<div className="mac-settings-row settings-network-row settings-choice-row">
|
||||
<div className="settings-row-label">
|
||||
<span>Mode</span>
|
||||
<small>Controls proxy use for new download requests.</small>
|
||||
</div>
|
||||
<div className="settings-radio-group">
|
||||
{[
|
||||
['none', 'No Proxy'],
|
||||
['system', 'Use System Proxy'],
|
||||
['system', 'System Proxy'],
|
||||
['custom', 'Custom Proxy'],
|
||||
].map(([value, label]) => (
|
||||
<label key={value}>
|
||||
@@ -724,64 +762,93 @@ runEngineChecks(false);
|
||||
</div>
|
||||
{settings.proxyMode === 'custom' && (
|
||||
<>
|
||||
<div className="mac-settings-row">
|
||||
<span className="text-[13px] text-text-primary pl-4">Proxy Host</span>
|
||||
<div className="mac-settings-row settings-network-row">
|
||||
<div className="settings-row-label">
|
||||
<span>Proxy host</span>
|
||||
<small>Host name, IP address, or URL with scheme.</small>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.proxyHost}
|
||||
onChange={(e) => settings.setProxyHost(e.target.value)}
|
||||
placeholder="127.0.0.1"
|
||||
className="app-control w-40 font-mono"
|
||||
placeholder="127.0.0.1 or socks5://127.0.0.1"
|
||||
className="app-control settings-network-input font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div className="mac-settings-row">
|
||||
<span className="text-[13px] text-text-primary pl-4">Proxy Port</span>
|
||||
<div className="mac-settings-row settings-network-row">
|
||||
<div className="settings-row-label">
|
||||
<span>Proxy port</span>
|
||||
<small>Valid range is 1 to 65535.</small>
|
||||
</div>
|
||||
<input
|
||||
type="number" min="1" max="65535"
|
||||
value={settings.proxyPort}
|
||||
onChange={(e) => settings.setProxyPort(Number(e.target.value))}
|
||||
onBlur={(e) => {
|
||||
const val = Number(e.target.value);
|
||||
if (val < 1) settings.setProxyPort(1);
|
||||
if (val > 65535) settings.setProxyPort(65535);
|
||||
}}
|
||||
className="app-control w-24 text-center"
|
||||
className="app-control settings-port-input text-center"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<p className="settings-group-footer">
|
||||
{settings.proxyMode === 'none' && 'Downloads ignore configured proxies.'}
|
||||
{settings.proxyMode === 'system' && `Downloads use the detected ${platform.os === 'macos' ? 'macOS' : platform.os === 'windows' ? 'Windows' : 'desktop'} system proxy when available.`}
|
||||
{settings.proxyMode === 'system' && `Downloads use the detected ${platform.os === 'macos' ? 'macOS' : platform.os === 'windows' ? 'Windows' : 'desktop'} system proxy when available, otherwise no proxy.`}
|
||||
{settings.proxyMode === 'custom' && (settings.proxyHost
|
||||
? `Downloads use http://${settings.proxyHost}:${settings.proxyPort}.`
|
||||
? 'Downloads use the configured proxy URL for HTTP, HTTPS, and media engines.'
|
||||
: 'Enter a proxy host and port to enable the custom proxy.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 className="settings-section-title">Identity</h2>
|
||||
<div className="mac-settings-group">
|
||||
<div className="mac-settings-row">
|
||||
<span className="text-[13px] text-text-primary">Custom User Agent</span>
|
||||
<div className="flex-1 ml-4 relative">
|
||||
<div className="mac-settings-row settings-network-row">
|
||||
<div className="settings-row-label">
|
||||
<span>Custom User-Agent</span>
|
||||
<small>Applied to metadata fetches and download engines.</small>
|
||||
</div>
|
||||
<div
|
||||
className="settings-combobox"
|
||||
ref={userAgentMenuRef}
|
||||
onBlur={(event) => {
|
||||
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
|
||||
setIsUserAgentMenuOpen(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
list="user-agents"
|
||||
value={settings.customUserAgent}
|
||||
onChange={(e) => settings.setCustomUserAgent(e.target.value)}
|
||||
placeholder="e.g. Mozilla/5.0..."
|
||||
className="app-control w-full font-mono text-[11px]"
|
||||
onFocus={() => setIsUserAgentMenuOpen(true)}
|
||||
placeholder="Leave blank for Firelink default"
|
||||
className="app-control settings-network-input font-mono"
|
||||
role="combobox"
|
||||
aria-expanded={isUserAgentMenuOpen}
|
||||
aria-controls="user-agent-suggestions"
|
||||
/>
|
||||
<datalist id="user-agents">
|
||||
<option value="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36">Chrome (Windows)</option>
|
||||
<option value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36">Chrome (macOS)</option>
|
||||
<option value="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0">Firefox (Windows)</option>
|
||||
<option value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0">Firefox (macOS)</option>
|
||||
<option value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15">Safari (macOS)</option>
|
||||
</datalist>
|
||||
{isUserAgentMenuOpen && (
|
||||
<div id="user-agent-suggestions" className="settings-combobox-menu" role="listbox">
|
||||
{USER_AGENT_SUGGESTIONS.map(option => (
|
||||
<button
|
||||
key={option.label}
|
||||
type="button"
|
||||
className="settings-combobox-option"
|
||||
role="option"
|
||||
aria-selected={settings.customUserAgent === option.value}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
settings.setCustomUserAgent(option.value);
|
||||
setIsUserAgentMenuOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="settings-combobox-value">{option.value}</span>
|
||||
<span className="settings-combobox-meta">{option.label} · {option.detail}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="settings-group-footer">Spoofs the browser User-Agent to bypass download restrictions. Leave blank for default.</p>
|
||||
<p className="settings-group-footer">Overrides the outbound User-Agent header. Leave blank for Firelink defaults.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+109
-16
@@ -372,14 +372,22 @@ html[data-list-density="relaxed"] {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.app-control {
|
||||
min-height: 28px;
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 6px;
|
||||
background: hsl(var(--bg-input));
|
||||
color: hsl(var(--text-primary));
|
||||
transition: border-color 100ms ease;
|
||||
}
|
||||
.app-control {
|
||||
min-height: 28px;
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 6px;
|
||||
background: hsl(var(--bg-input));
|
||||
color: hsl(var(--text-primary));
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13px;
|
||||
line-height: 1.35;
|
||||
transition: border-color 100ms ease;
|
||||
}
|
||||
|
||||
.app-control::placeholder {
|
||||
color: hsl(var(--text-muted));
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.app-control:hover:not(:disabled) {
|
||||
border-color: hsl(var(--text-muted));
|
||||
@@ -1230,15 +1238,100 @@ html[data-list-density="relaxed"] {
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.settings-radio-group input {
|
||||
accent-color: hsl(var(--accent-color));
|
||||
}
|
||||
.settings-radio-group input {
|
||||
accent-color: hsl(var(--accent-color));
|
||||
}
|
||||
|
||||
.theme-option-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(72px, 1fr));
|
||||
gap: 7px;
|
||||
width: 100%;
|
||||
.settings-network-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(150px, 190px) minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.settings-network-row > .settings-row-label {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.settings-network-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings-port-input {
|
||||
width: 112px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.settings-combobox {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-combobox-menu {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
max-height: 246px;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 8px;
|
||||
background: hsl(var(--surface-overlay));
|
||||
box-shadow:
|
||||
0 18px 42px hsl(0 0% 0% / 0.28),
|
||||
inset 0 1px 0 hsl(0 0% 100% / 0.06);
|
||||
}
|
||||
|
||||
.settings-combobox-option {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid hsl(var(--border-color));
|
||||
background: transparent;
|
||||
color: hsl(var(--text-primary));
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-combobox-option:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.settings-combobox-option:hover,
|
||||
.settings-combobox-option:focus-visible,
|
||||
.settings-combobox-option[aria-selected="true"] {
|
||||
background: hsl(var(--item-hover));
|
||||
}
|
||||
|
||||
.settings-combobox-value {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: "SF Mono", Monaco, "Cascadia Code", "Fira Code", "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.settings-combobox-meta {
|
||||
color: hsl(var(--text-muted));
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.theme-option-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(72px, 1fr));
|
||||
gap: 7px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user