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;
|
||||||
use crate::state::{Credentials, MinecraftLoginFlow};
|
use crate::state::{Credentials, MinecraftLoginFlow};
|
||||||
use crate::util::fetch::REQWEST_CLIENT;
|
use crate::util::fetch::INSECURE_REQWEST_CLIENT;
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn check_reachable() -> crate::Result<()> {
|
pub async fn check_reachable() -> crate::Result<()> {
|
||||||
let resp = REQWEST_CLIENT
|
let resp = INSECURE_REQWEST_CLIENT
|
||||||
.get("https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
.get("https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use tokio_util::compat::FuturesAsyncReadCompatExt;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
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(
|
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) })))
|
Ok(Either::Left(stream::once(async { Ok(data) })))
|
||||||
} else {
|
} else {
|
||||||
let response = REQWEST_CLIENT
|
let response = INSECURE_REQWEST_CLIENT
|
||||||
.get(url.as_str())
|
.get(url.as_str())
|
||||||
.header("Accept", "image/png")
|
.header("Accept", "image/png")
|
||||||
.send()
|
.send()
|
||||||
|
|||||||
@@ -863,7 +863,7 @@ async fn run_credentials(
|
|||||||
if !project_id.trim().is_empty() {
|
if !project_id.trim().is_empty() {
|
||||||
let server_id = uuid::Uuid::new_v4().to_string();
|
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")
|
.post("https://sessionserver.mojang.com/session/minecraft/join")
|
||||||
.json(&json!({
|
.json(&json!({
|
||||||
"accessToken": &credentials.access_token,
|
"accessToken": &credentials.access_token,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::ErrorKind;
|
use crate::ErrorKind;
|
||||||
use crate::util::fetch::REQWEST_CLIENT;
|
use crate::util::fetch::INSECURE_REQWEST_CLIENT;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
|
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
|
||||||
use chrono::{DateTime, Duration, TimeZone, Utc};
|
use chrono::{DateTime, Duration, TimeZone, Utc};
|
||||||
@@ -855,7 +855,7 @@ async fn oauth_token(
|
|||||||
query.insert("scope", REQUESTED_SCOPE);
|
query.insert("scope", REQUESTED_SCOPE);
|
||||||
|
|
||||||
let res = auth_retry(|| {
|
let res = auth_retry(|| {
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.post("https://login.live.com/oauth20_token.srf")
|
.post("https://login.live.com/oauth20_token.srf")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.form(&query)
|
.form(&query)
|
||||||
@@ -903,7 +903,7 @@ async fn oauth_refresh(
|
|||||||
query.insert("scope", REQUESTED_SCOPE);
|
query.insert("scope", REQUESTED_SCOPE);
|
||||||
|
|
||||||
let res = auth_retry(|| {
|
let res = auth_retry(|| {
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.post("https://login.live.com/oauth20_token.srf")
|
.post("https://login.live.com/oauth20_token.srf")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.form(&query)
|
.form(&query)
|
||||||
@@ -1048,7 +1048,7 @@ async fn minecraft_token(
|
|||||||
let token = token.token;
|
let token = token.token;
|
||||||
|
|
||||||
let res = auth_retry(|| {
|
let res = auth_retry(|| {
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.post("https://api.minecraftservices.com/launcher/login")
|
.post("https://api.minecraftservices.com/launcher/login")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.json(&json!({
|
.json(&json!({
|
||||||
@@ -1276,7 +1276,7 @@ async fn minecraft_profile(
|
|||||||
token: &str,
|
token: &str,
|
||||||
) -> Result<MinecraftProfile, MinecraftAuthenticationError> {
|
) -> Result<MinecraftProfile, MinecraftAuthenticationError> {
|
||||||
let res = auth_retry(|| {
|
let res = auth_retry(|| {
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.get("https://api.minecraftservices.com/minecraft/profile")
|
.get("https://api.minecraftservices.com/minecraft/profile")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.bearer_auth(token)
|
.bearer_auth(token)
|
||||||
@@ -1327,7 +1327,7 @@ async fn minecraft_entitlements(
|
|||||||
token: &str,
|
token: &str,
|
||||||
) -> Result<MinecraftEntitlements, MinecraftAuthenticationError> {
|
) -> Result<MinecraftEntitlements, MinecraftAuthenticationError> {
|
||||||
let res = auth_retry(|| {
|
let res = auth_retry(|| {
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.get(format!("https://api.minecraftservices.com/entitlements/license?requestId={}", Uuid::new_v4()))
|
.get(format!("https://api.minecraftservices.com/entitlements/license?requestId={}", Uuid::new_v4()))
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.bearer_auth(token)
|
.bearer_auth(token)
|
||||||
@@ -1471,7 +1471,7 @@ async fn send_signed_request<T: DeserializeOwned>(
|
|||||||
let signature = BASE64_STANDARD.encode(&sig_buffer);
|
let signature = BASE64_STANDARD.encode(&sig_buffer);
|
||||||
|
|
||||||
let res = auth_retry(|| {
|
let res = auth_retry(|| {
|
||||||
let mut request = REQWEST_CLIENT
|
let mut request = INSECURE_REQWEST_CLIENT
|
||||||
.post(url)
|
.post(url)
|
||||||
.header("Content-Type", "application/json; charset=utf-8")
|
.header("Content-Type", "application/json; charset=utf-8")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
ErrorKind,
|
ErrorKind,
|
||||||
data::Credentials,
|
data::Credentials,
|
||||||
state::{MinecraftProfile, PROFILE_CACHE, ProfileCacheEntry},
|
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.
|
/// Provides operations for interacting with capes on a Minecraft player profile.
|
||||||
@@ -23,7 +23,7 @@ impl MinecraftCapeOperation {
|
|||||||
cape_id: Uuid,
|
cape_id: Uuid,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
update_profile_cache_from_response(
|
update_profile_cache_from_response(
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.put("https://api.minecraftservices.com/minecraft/profile/capes/active")
|
.put("https://api.minecraftservices.com/minecraft/profile/capes/active")
|
||||||
.header("Content-Type", "application/json; charset=utf-8")
|
.header("Content-Type", "application/json; charset=utf-8")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
@@ -42,7 +42,7 @@ impl MinecraftCapeOperation {
|
|||||||
|
|
||||||
pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> {
|
pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> {
|
||||||
update_profile_cache_from_response(
|
update_profile_cache_from_response(
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.delete("https://api.minecraftservices.com/minecraft/profile/capes/active")
|
.delete("https://api.minecraftservices.com/minecraft/profile/capes/active")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.bearer_auth(&credentials.access_token)
|
.bearer_auth(&credentials.access_token)
|
||||||
@@ -92,7 +92,7 @@ impl MinecraftSkinOperation {
|
|||||||
);
|
);
|
||||||
|
|
||||||
update_profile_cache_from_response(
|
update_profile_cache_from_response(
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.post(
|
.post(
|
||||||
"https://api.minecraftservices.com/minecraft/profile/skins",
|
"https://api.minecraftservices.com/minecraft/profile/skins",
|
||||||
)
|
)
|
||||||
@@ -110,7 +110,7 @@ impl MinecraftSkinOperation {
|
|||||||
|
|
||||||
pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> {
|
pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> {
|
||||||
update_profile_cache_from_response(
|
update_profile_cache_from_response(
|
||||||
REQWEST_CLIENT
|
INSECURE_REQWEST_CLIENT
|
||||||
.delete("https://api.minecraftservices.com/minecraft/profile/skins/active")
|
.delete("https://api.minecraftservices.com/minecraft/profile/skins/active")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.bearer_auth(&credentials.access_token)
|
.bearer_auth(&credentials.access_token)
|
||||||
|
|||||||
@@ -130,18 +130,24 @@ static GLOBAL_FETCH_FENCE: LazyLock<FetchFence> =
|
|||||||
inner: Mutex::new(FenceInner::new()),
|
inner: Mutex::new(FenceInner::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
|
fn reqwest_client_builder() -> reqwest::ClientBuilder {
|
||||||
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);
|
|
||||||
reqwest::Client::builder()
|
reqwest::Client::builder()
|
||||||
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
.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()
|
.build()
|
||||||
.expect("Reqwest Client Building Failed")
|
.expect("client configuration should be valid")
|
||||||
});
|
});
|
||||||
|
|
||||||
const FETCH_ATTEMPTS: usize = 2;
|
const FETCH_ATTEMPTS: usize = 2;
|
||||||
@@ -157,6 +163,28 @@ pub async fn fetch(
|
|||||||
.await
|
.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))]
|
#[tracing::instrument(skip(json_body, semaphore))]
|
||||||
pub async fn fetch_json<T>(
|
pub async fn fetch_json<T>(
|
||||||
method: Method,
|
method: Method,
|
||||||
@@ -177,7 +205,8 @@ where
|
|||||||
Ok(value)
|
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))]
|
#[tracing::instrument(skip(json_body, semaphore))]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn fetch_advanced(
|
pub async fn fetch_advanced(
|
||||||
@@ -189,6 +218,34 @@ pub async fn fetch_advanced(
|
|||||||
loading_bar: Option<(&LoadingBarId, f64)>,
|
loading_bar: Option<(&LoadingBarId, f64)>,
|
||||||
semaphore: &FetchSemaphore,
|
semaphore: &FetchSemaphore,
|
||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
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> {
|
) -> crate::Result<Bytes> {
|
||||||
let _permit = semaphore.0.acquire().await?;
|
let _permit = semaphore.0.acquire().await?;
|
||||||
|
|
||||||
@@ -210,7 +267,7 @@ pub async fn fetch_advanced(
|
|||||||
return Err(ErrorKind::ApiIsDownError.into());
|
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() {
|
if let Some(body) = json_body.clone() {
|
||||||
req = req.json(&body);
|
req = req.json(&body);
|
||||||
@@ -328,7 +385,9 @@ pub async fn fetch_mirrors(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (index, mirror) in mirrors.iter().enumerate() {
|
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)) {
|
if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) {
|
||||||
return result;
|
return result;
|
||||||
@@ -348,7 +407,7 @@ pub async fn post_json(
|
|||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let _permit = semaphore.0.acquire().await?;
|
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) =
|
if let Some(creds) =
|
||||||
crate::state::ModrinthCredentials::get_active(exec).await?
|
crate::state::ModrinthCredentials::get_active(exec).await?
|
||||||
|
|||||||
Reference in New Issue
Block a user