diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4338117..c50dd06 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1138,7 +1138,9 @@ async fn fetch_metadata( return Err("Too many redirects".to_string()); } - let mut builder = reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()); + let mut builder = reqwest::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .timeout(FILE_METADATA_TIMEOUT); if let Some(proxy) = proxy.as_deref().map(str::trim).filter(|s| !s.is_empty()) { if proxy.eq_ignore_ascii_case("none") { builder = builder.no_proxy(); @@ -1199,6 +1201,20 @@ async fn fetch_metadata( let client = builder.build().map_err(|e| e.to_string())?; + let build_get_range = || { + let mut get_req = client + .get(¤t_url) + .header(reqwest::header::RANGE, "bytes=0-0"); + if should_send_auth { + if let Some(ref user) = username { + if !user.is_empty() { + get_req = get_req.basic_auth(user, password.as_deref()); + } + } + } + get_req + }; + let mut head_req = client.head(¤t_url); if should_send_auth { if let Some(ref user) = username { @@ -1207,7 +1223,14 @@ async fn fetch_metadata( } } } - let mut current_res = head_req.send().await.map_err(|e| e.to_string())?; + let mut current_res = match head_req.send().await { + Ok(response) => response, + Err(head_error) => build_get_range().send().await.map_err(|get_error| { + format!( + "HEAD metadata request failed ({head_error}); ranged GET fallback failed ({get_error})" + ) + })?, + }; let mut needs_fallback = false; if (!current_res.status().is_success() && !current_res.status().is_redirection()) @@ -1217,15 +1240,7 @@ async fn fetch_metadata( } if needs_fallback { - let mut get_req = client.get(¤t_url).header(reqwest::header::RANGE, "bytes=0-0"); - if should_send_auth { - if let Some(ref user) = username { - if !user.is_empty() { - get_req = get_req.basic_auth(user, password.as_deref()); - } - } - } - current_res = get_req.send().await.map_err(|e| e.to_string())?; + current_res = build_get_range().send().await.map_err(|e| e.to_string())?; } if current_res.status().is_redirection() { @@ -1309,6 +1324,7 @@ async fn fetch_metadata( const MEDIA_METADATA_CACHE_TTL: Duration = Duration::from_secs(60); const MEDIA_METADATA_TIMEOUT: Duration = Duration::from_secs(55); const MEDIA_METADATA_CACHE_MAX_ENTRIES: usize = 128; +const FILE_METADATA_TIMEOUT: Duration = Duration::from_secs(20); static MEDIA_METADATA_CACHE: OnceLock>> = OnceLock::new(); @@ -1319,6 +1335,7 @@ static MEDIA_METADATA_LOCKS: OnceLock< fn media_metadata_cache_key( url: &str, cookie_browser: &Option, + user_agent: &Option, username: &Option, password: &Option, headers: &Option, @@ -1328,6 +1345,7 @@ fn media_metadata_cache_key( let mut hasher = std::collections::hash_map::DefaultHasher::new(); url.hash(&mut hasher); cookie_browser.hash(&mut hasher); + user_agent.hash(&mut hasher); username.hash(&mut hasher); password.hash(&mut hasher); headers.hash(&mut hasher); @@ -1375,6 +1393,7 @@ async fn fetch_media_metadata( let cache_key = media_metadata_cache_key( &url, &cookie_browser, + &user_agent, &username, &password, &headers, @@ -4627,6 +4646,7 @@ mod tests { let base = media_metadata_cache_key( "https://example.com/watch?v=1", &Some("firefox".to_string()), + &Some("Custom UA A".to_string()), &None, &None, &Some("User-Agent: Browser A".to_string()), @@ -4636,6 +4656,7 @@ mod tests { let changed_headers = media_metadata_cache_key( "https://example.com/watch?v=1", &Some("firefox".to_string()), + &Some("Custom UA A".to_string()), &None, &None, &Some("User-Agent: Browser B".to_string()), @@ -4645,15 +4666,27 @@ mod tests { let changed_cookies = media_metadata_cache_key( "https://example.com/watch?v=1", &Some("firefox".to_string()), + &Some("Custom UA A".to_string()), &None, &None, &Some("User-Agent: Browser A".to_string()), &Some("session=two".to_string()), &None, ); + let changed_user_agent = media_metadata_cache_key( + "https://example.com/watch?v=1", + &Some("firefox".to_string()), + &Some("Custom UA B".to_string()), + &None, + &None, + &Some("User-Agent: Browser A".to_string()), + &Some("session=one".to_string()), + &None, + ); assert_ne!(base, changed_headers); assert_ne!(base, changed_cookies); + assert_ne!(base, changed_user_agent); } #[test] diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx index 4a89e00..d4cafdb 100644 --- a/src/components/SettingsView.tsx +++ b/src/components/SettingsView.tsx @@ -840,7 +840,7 @@ runEngineChecks(false); {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. Normal file downloads require an HTTP or HTTPS proxy endpoint; media downloads can use SOCKS.`} {settings.proxyMode === 'custom' && (settings.proxyHost - ? 'Downloads use the configured proxy URL for HTTP, HTTPS, and media engines.' + ? 'Downloads use the configured HTTP proxy endpoint for metadata and download engines.' : 'Enter a proxy host and port to enable the custom proxy.')}