Make mrpack downloads HTTPS-only (#5882)
* Add set of trusted download hosts for mrpacks * split secure/insecure reqwest client * make fetching https-only * lint fix
This commit is contained in:
@@ -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?;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<MinecraftProfile, MinecraftAuthenticationError> {
|
||||
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<MinecraftEntitlements, MinecraftAuthenticationError> {
|
||||
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<T: DeserializeOwned>(
|
||||
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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -130,18 +130,24 @@ static GLOBAL_FETCH_FENCE: LazyLock<FetchFence> =
|
||||
inner: Mutex::new(FenceInner::new()),
|
||||
});
|
||||
|
||||
pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = 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<reqwest::Client> =
|
||||
LazyLock::new(|| {
|
||||
reqwest_client_builder()
|
||||
.build()
|
||||
.expect("client configuration should be valid")
|
||||
});
|
||||
|
||||
pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = 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<Bytes> {
|
||||
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<T>(
|
||||
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<Bytes> {
|
||||
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<serde_json::Value>,
|
||||
header: Option<(&str, &str)>,
|
||||
loading_bar: Option<(&LoadingBarId, f64)>,
|
||||
semaphore: &FetchSemaphore,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
client: &reqwest::Client,
|
||||
) -> crate::Result<Bytes> {
|
||||
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?
|
||||
|
||||
Reference in New Issue
Block a user