diff --git a/packages/app-lib/src/api/minecraft_auth.rs b/packages/app-lib/src/api/minecraft_auth.rs index 744111162e..931b8e75bb 100644 --- a/packages/app-lib/src/api/minecraft_auth.rs +++ b/packages/app-lib/src/api/minecraft_auth.rs @@ -4,11 +4,11 @@ use reqwest::StatusCode; use crate::State; use crate::state::{Credentials, MinecraftLoginFlow}; -use crate::util::fetch::REQWEST_CLIENT; +use crate::util::fetch::INSECURE_REQWEST_CLIENT; #[tracing::instrument] pub async fn check_reachable() -> crate::Result<()> { - let resp = REQWEST_CLIENT + let resp = INSECURE_REQWEST_CLIENT .get("https://sessionserver.mojang.com/session/minecraft/hasJoined") .send() .await?; diff --git a/packages/app-lib/src/api/minecraft_skins/png_util.rs b/packages/app-lib/src/api/minecraft_skins/png_util.rs index e4ae3dcba5..11376e570d 100644 --- a/packages/app-lib/src/api/minecraft_skins/png_util.rs +++ b/packages/app-lib/src/api/minecraft_skins/png_util.rs @@ -14,7 +14,7 @@ use tokio_util::compat::FuturesAsyncReadCompatExt; use url::Url; use crate::{ - ErrorKind, minecraft_skins::UrlOrBlob, util::fetch::REQWEST_CLIENT, + ErrorKind, minecraft_skins::UrlOrBlob, util::fetch::INSECURE_REQWEST_CLIENT, }; pub async fn url_to_data_stream( @@ -25,7 +25,7 @@ pub async fn url_to_data_stream( Ok(Either::Left(stream::once(async { Ok(data) }))) } else { - let response = REQWEST_CLIENT + let response = INSECURE_REQWEST_CLIENT .get(url.as_str()) .header("Accept", "image/png") .send() diff --git a/packages/app-lib/src/api/profile/mod.rs b/packages/app-lib/src/api/profile/mod.rs index 313fe59ae8..7a2f3c731f 100644 --- a/packages/app-lib/src/api/profile/mod.rs +++ b/packages/app-lib/src/api/profile/mod.rs @@ -863,7 +863,7 @@ async fn run_credentials( if !project_id.trim().is_empty() { let server_id = uuid::Uuid::new_v4().to_string(); - let join_result = fetch::REQWEST_CLIENT + let join_result = fetch::INSECURE_REQWEST_CLIENT .post("https://sessionserver.mojang.com/session/minecraft/join") .json(&json!({ "accessToken": &credentials.access_token, diff --git a/packages/app-lib/src/state/minecraft_auth.rs b/packages/app-lib/src/state/minecraft_auth.rs index a0b8db9ef2..d28938506e 100644 --- a/packages/app-lib/src/state/minecraft_auth.rs +++ b/packages/app-lib/src/state/minecraft_auth.rs @@ -1,5 +1,5 @@ use crate::ErrorKind; -use crate::util::fetch::REQWEST_CLIENT; +use crate::util::fetch::INSECURE_REQWEST_CLIENT; use base64::Engine; use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD}; use chrono::{DateTime, Duration, TimeZone, Utc}; @@ -855,7 +855,7 @@ async fn oauth_token( query.insert("scope", REQUESTED_SCOPE); let res = auth_retry(|| { - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .post("https://login.live.com/oauth20_token.srf") .header("Accept", "application/json") .form(&query) @@ -903,7 +903,7 @@ async fn oauth_refresh( query.insert("scope", REQUESTED_SCOPE); let res = auth_retry(|| { - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .post("https://login.live.com/oauth20_token.srf") .header("Accept", "application/json") .form(&query) @@ -1048,7 +1048,7 @@ async fn minecraft_token( let token = token.token; let res = auth_retry(|| { - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .post("https://api.minecraftservices.com/launcher/login") .header("Accept", "application/json") .json(&json!({ @@ -1276,7 +1276,7 @@ async fn minecraft_profile( token: &str, ) -> Result { let res = auth_retry(|| { - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .get("https://api.minecraftservices.com/minecraft/profile") .header("Accept", "application/json") .bearer_auth(token) @@ -1327,7 +1327,7 @@ async fn minecraft_entitlements( token: &str, ) -> Result { let res = auth_retry(|| { - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .get(format!("https://api.minecraftservices.com/entitlements/license?requestId={}", Uuid::new_v4())) .header("Accept", "application/json") .bearer_auth(token) @@ -1471,7 +1471,7 @@ async fn send_signed_request( let signature = BASE64_STANDARD.encode(&sig_buffer); let res = auth_retry(|| { - let mut request = REQWEST_CLIENT + let mut request = INSECURE_REQWEST_CLIENT .post(url) .header("Content-Type", "application/json; charset=utf-8") .header("Accept", "application/json") diff --git a/packages/app-lib/src/state/minecraft_skins/mojang_api.rs b/packages/app-lib/src/state/minecraft_skins/mojang_api.rs index 49b5249ed6..4e25711f1c 100644 --- a/packages/app-lib/src/state/minecraft_skins/mojang_api.rs +++ b/packages/app-lib/src/state/minecraft_skins/mojang_api.rs @@ -11,7 +11,7 @@ use crate::{ ErrorKind, data::Credentials, state::{MinecraftProfile, PROFILE_CACHE, ProfileCacheEntry}, - util::fetch::REQWEST_CLIENT, + util::fetch::INSECURE_REQWEST_CLIENT, }; /// Provides operations for interacting with capes on a Minecraft player profile. @@ -23,7 +23,7 @@ impl MinecraftCapeOperation { cape_id: Uuid, ) -> crate::Result<()> { update_profile_cache_from_response( - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .put("https://api.minecraftservices.com/minecraft/profile/capes/active") .header("Content-Type", "application/json; charset=utf-8") .header("Accept", "application/json") @@ -42,7 +42,7 @@ impl MinecraftCapeOperation { pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> { update_profile_cache_from_response( - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .delete("https://api.minecraftservices.com/minecraft/profile/capes/active") .header("Accept", "application/json") .bearer_auth(&credentials.access_token) @@ -92,7 +92,7 @@ impl MinecraftSkinOperation { ); update_profile_cache_from_response( - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .post( "https://api.minecraftservices.com/minecraft/profile/skins", ) @@ -110,7 +110,7 @@ impl MinecraftSkinOperation { pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> { update_profile_cache_from_response( - REQWEST_CLIENT + INSECURE_REQWEST_CLIENT .delete("https://api.minecraftservices.com/minecraft/profile/skins/active") .header("Accept", "application/json") .bearer_auth(&credentials.access_token) diff --git a/packages/app-lib/src/util/fetch.rs b/packages/app-lib/src/util/fetch.rs index e6c8bec544..627485965d 100644 --- a/packages/app-lib/src/util/fetch.rs +++ b/packages/app-lib/src/util/fetch.rs @@ -130,18 +130,24 @@ static GLOBAL_FETCH_FENCE: LazyLock = inner: Mutex::new(FenceInner::new()), }); -pub static REQWEST_CLIENT: LazyLock = LazyLock::new(|| { - let mut headers = reqwest::header::HeaderMap::new(); - - let header = - reqwest::header::HeaderValue::from_str(&crate::launcher_user_agent()) - .unwrap(); - headers.insert(reqwest::header::USER_AGENT, header); +fn reqwest_client_builder() -> reqwest::ClientBuilder { reqwest::Client::builder() .tcp_keepalive(Some(time::Duration::from_secs(10))) - .default_headers(headers) + .user_agent(crate::launcher_user_agent()) +} + +pub static INSECURE_REQWEST_CLIENT: LazyLock = + LazyLock::new(|| { + reqwest_client_builder() + .build() + .expect("client configuration should be valid") + }); + +pub static REQWEST_CLIENT: LazyLock = LazyLock::new(|| { + reqwest_client_builder() + .https_only(true) .build() - .expect("Reqwest Client Building Failed") + .expect("client configuration should be valid") }); const FETCH_ATTEMPTS: usize = 2; @@ -157,6 +163,28 @@ pub async fn fetch( .await } +#[tracing::instrument(skip(semaphore))] +pub async fn fetch_with_client( + url: &str, + sha1: Option<&str>, + semaphore: &FetchSemaphore, + exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, + client: &reqwest::Client, +) -> crate::Result { + fetch_advanced_with_client( + Method::GET, + url, + sha1, + None, + None, + None, + semaphore, + exec, + client, + ) + .await +} + #[tracing::instrument(skip(json_body, semaphore))] pub async fn fetch_json( method: Method, @@ -177,7 +205,8 @@ where Ok(value) } -/// Downloads a file with retry and checksum functionality +/// Downloads a file with retry and checksum functionality, and a specific +/// [`reqwest::Client`]. #[tracing::instrument(skip(json_body, semaphore))] #[allow(clippy::too_many_arguments)] pub async fn fetch_advanced( @@ -189,6 +218,34 @@ pub async fn fetch_advanced( loading_bar: Option<(&LoadingBarId, f64)>, semaphore: &FetchSemaphore, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, +) -> crate::Result { + fetch_advanced_with_client( + method, + url, + sha1, + json_body, + header, + loading_bar, + semaphore, + exec, + &INSECURE_REQWEST_CLIENT, + ) + .await +} + +/// Downloads a file with retry and checksum functionality +#[tracing::instrument(skip(json_body, semaphore))] +#[allow(clippy::too_many_arguments)] +pub async fn fetch_advanced_with_client( + method: Method, + url: &str, + sha1: Option<&str>, + json_body: Option, + header: Option<(&str, &str)>, + loading_bar: Option<(&LoadingBarId, f64)>, + semaphore: &FetchSemaphore, + exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, + client: &reqwest::Client, ) -> crate::Result { let _permit = semaphore.0.acquire().await?; @@ -210,7 +267,7 @@ pub async fn fetch_advanced( return Err(ErrorKind::ApiIsDownError.into()); } - let mut req = REQWEST_CLIENT.request(method.clone(), url); + let mut req = INSECURE_REQWEST_CLIENT.request(method.clone(), url); if let Some(body) = json_body.clone() { req = req.json(&body); @@ -328,7 +385,9 @@ pub async fn fetch_mirrors( } for (index, mirror) in mirrors.iter().enumerate() { - let result = fetch(mirror, sha1, semaphore, exec).await; + let result = + fetch_with_client(mirror, sha1, semaphore, exec, &REQWEST_CLIENT) + .await; if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) { return result; @@ -348,7 +407,7 @@ pub async fn post_json( ) -> crate::Result<()> { let _permit = semaphore.0.acquire().await?; - let mut req = REQWEST_CLIENT.post(url).json(&json_body); + let mut req = INSECURE_REQWEST_CLIENT.post(url).json(&json_body); if let Some(creds) = crate::state::ModrinthCredentials::get_active(exec).await?