1use std::env;
2use std::time::Duration;
3use once_cell::sync::Lazy;
4use reqwest::Client;
5use tokio::fs;
6use thiserror::Error;
7
8pub static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
10 Client::builder()
11 .pool_max_idle_per_host(100)
12 .pool_idle_timeout(Some(Duration::from_secs(90)))
13
14 .http2_initial_stream_window_size(Some(2 * 1024 * 1024))
15 .http2_initial_connection_window_size(Some(4 * 1024 * 1024))
16 .http2_adaptive_window(true)
17 .http2_max_frame_size(Some(16 * 1024))
18
19 .tcp_keepalive(Some(Duration::from_secs(60)))
20 .tcp_nodelay(true)
21
22 .timeout(Duration::from_secs(60))
23 .connect_timeout(Duration::from_secs(5))
24
25 .zstd(true)
26 .gzip(true)
27 .brotli(true)
28
29 .build()
30 .expect("Failed to build HTTP client with default configuration - this should never fail")
31});
32
33
34#[cfg(target_os = "windows")]
35const HOSTS_PATH: &str = "System32\\drivers\\etc\\hosts";
36
37#[cfg(not(target_os = "windows"))]
38const HOSTS_PATH: &str = "etc/hosts";
39
40const HOSTS: [&str; 3] = [
42 "mojang.com",
43 "minecraft.net",
44 "lightylauncher.fr",
45];
46
47#[derive(Debug, Error)]
49pub enum HostsError {
50 #[error("Failed to read hosts file at {0}")]
51 HostsReadError(String),
52
53 #[error("Hosts file contains blocked entries: {0}")]
54 HostsBlocked(String),
55
56 #[error("I/O error: {0}")]
57 IoError(#[from] std::io::Error),
58}
59
60pub type HostsResult<T> = std::result::Result<T, HostsError>;
61
62pub async fn check_hosts_file() -> HostsResult<()> {
64 let hosts_path = if cfg!(target_os = "windows") {
65 let system_drive = env::var("SystemDrive").unwrap_or("C:".to_string());
66 format!("{}\\{}", system_drive, HOSTS_PATH)
67 } else {
68 format!("/{}", HOSTS_PATH)
69 };
70
71 if !fs::try_exists(&hosts_path).await? {
72 return Ok(());
73 }
74
75 let hosts_file = fs::read_to_string(&hosts_path)
76 .await
77 .map_err(|_| HostsError::HostsReadError(hosts_path.clone()))?;
78
79 let flagged_entries: Vec<_> = hosts_file
80 .lines()
81 .filter(|line| !line.trim_start().starts_with('#'))
82 .flat_map(|line| {
83 let mut parts = line.split_whitespace();
84 let _ip = parts.next();
85 parts.filter(|domain| HOSTS.iter().any(|&entry| domain.contains(entry)))
86 })
87 .map(|s| s.to_string())
88 .collect();
89
90 if !flagged_entries.is_empty() {
91 return Err(HostsError::HostsBlocked(flagged_entries.join("\n")));
92 }
93
94 Ok(())
95}