orinium_browser/platform/network/
core.rs1use super::{HostKey, HttpSender, NetworkConfig, NetworkError, SenderPool};
5
6use http_body_util::{BodyExt, Empty};
7use hyper::{
8 Method, Request, Uri,
9 body::{Bytes, Incoming},
10 client::conn,
11 http::uri::Scheme,
12};
13use hyper_util::rt::TokioIo;
14use rustls::{ClientConfig, RootCertStore};
15use rustls_native_certs::load_native_certs;
16use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18use tokio::{net::TcpStream, runtime::Runtime, task::LocalSet};
19use tokio_rustls::TlsConnector;
20
21pub(super) struct AsyncNetworkCore {
22 local: LocalSet,
23 rt: Runtime,
24 inner: NetworkInner,
25}
26
27impl AsyncNetworkCore {
28 pub fn new() -> Self {
29 let rt = tokio::runtime::Builder::new_current_thread()
30 .enable_all()
31 .build()
32 .expect("failed to build tokio runtime");
33
34 let local = LocalSet::new();
35
36 Self {
37 rt,
38 local,
39 inner: NetworkInner::new(),
40 }
41 }
42
43 pub fn set_network_config(&mut self, config: NetworkConfig) {
44 self.inner.set_network_config(config)
45 }
46
47 pub fn fetch_blocking(&self, url: &str) -> Result<Response, NetworkError> {
49 self.local
51 .block_on(&self.rt, async { self.inner.fetch_url(url).await })
52 }
53}
54
55#[derive(Deserialize, Serialize)]
56pub struct StatusCode(u16);
57
58impl From<hyper::StatusCode> for StatusCode {
59 fn from(value: hyper::StatusCode) -> Self {
60 Self(value.as_u16())
61 }
62}
63
64impl StatusCode {
65 pub fn as_u16(&self) -> u16 {
66 self.0
67 }
68
69 pub fn is_success(&self) -> bool {
70 (200..300).contains(&self.0)
71 }
72
73 pub fn is_redirection(&self) -> bool {
74 (300..400).contains(&self.0)
75 }
76
77 pub fn canonical_reason(&self) -> Option<&'static str> {
78 let hyper_code: hyper::StatusCode = self.as_u16().try_into().ok()?;
79 hyper_code.canonical_reason()
80 }
81}
82
83#[derive(Deserialize, Serialize)]
85pub struct Response {
86 pub url: String,
87 pub status: StatusCode,
88 pub reason_phrase: String,
89 pub headers: Vec<(String, String)>,
90 pub body: Vec<u8>,
91}
92
93pub(super) struct NetworkInner {
94 sender_pool: Arc<std::sync::RwLock<SenderPool>>,
95 tls_config: Arc<ClientConfig>,
96 network_config: Arc<NetworkConfig>,
97}
98
99impl NetworkInner {
100 pub fn new() -> Self {
101 Self {
102 sender_pool: Arc::new(std::sync::RwLock::new(SenderPool::new())),
103 tls_config: Arc::new(Self::build_tls_config()),
104 network_config: Arc::new(NetworkConfig::default()),
105 }
106 }
107
108 pub fn set_network_config(&mut self, confing: NetworkConfig) {
109 self.network_config = Arc::new(confing)
110 }
111
112 fn build_tls_config() -> ClientConfig {
113 let mut roots = RootCertStore::empty();
114 let result = load_native_certs();
115
116 for cert in result.certs {
117 let _ = roots.add(cert);
118 }
119
120 ClientConfig::builder()
121 .with_root_certificates(roots)
122 .with_no_client_auth()
123 }
124
125 pub async fn fetch_url(&self, url: &str) -> Result<Response, NetworkError> {
126 let mut current: Uri = url.parse().map_err(|_| NetworkError::InvalidUri)?;
127 let mut redirects = 0usize;
128
129 loop {
130 let resp = self.send_request(¤t).await?;
131
132 if self.network_config.follow_redirects
133 && hyper::StatusCode::try_from(resp.status.0)
134 .map_err(|_| NetworkError::InvalidIpcStatusCode)?
135 .is_redirection()
136 {
137 if redirects >= 10 {
138 return Err(NetworkError::TooManyRedirects);
139 }
140
141 if let Some(loc) = resp
142 .headers
143 .iter()
144 .find(|(k, _)| k.eq_ignore_ascii_case("location"))
145 .map(|(_, v)| v)
146 {
147 current = resolve_redirect(¤t, loc)?;
148 redirects += 1;
149 continue;
150 }
151 }
152
153 return Ok(resp);
154 }
155 }
156
157 async fn send_request(&self, uri: &Uri) -> Result<Response, NetworkError> {
158 let host = uri.host().ok_or(NetworkError::MissingHost)?;
159 let scheme = uri.scheme().unwrap_or(&Scheme::HTTP);
160 let port = uri
161 .port_u16()
162 .unwrap_or(if scheme == &Scheme::HTTPS { 443 } else { 80 });
163
164 let key = HostKey {
165 scheme: scheme.clone(),
166 host: host.to_string(),
167 port,
168 };
169
170 let mut sender = self.get_or_create_sender(&key).await?;
171
172 let req = Request::builder()
173 .method(Method::GET)
174 .uri(uri.path_and_query().map_or("/", |p| p.as_str()))
175 .header("Host", host)
176 .header("User-Agent", self.network_config.user_agent.as_str())
177 .body(Empty::<Bytes>::new())
178 .map_err(|_| NetworkError::HttpRequestFailed)?;
179
180 let mut res = match &mut sender {
181 HttpSender::Http1(s) => s
182 .send_request(req)
183 .await
184 .map_err(|_| NetworkError::HttpRequestFailed)?,
185 _ => {
186 return Err(NetworkError::UnsupportedHttpVersion);
187 }
188 };
189
190 let response = Self::collect_response(uri.to_string(), &mut res).await?;
191
192 self.sender_pool
193 .write()
194 .unwrap()
195 .add_connection(key, sender);
196
197 Ok(response)
198 }
199
200 async fn collect_response(
201 url: String,
202 res: &mut hyper::Response<Incoming>,
203 ) -> Result<Response, NetworkError> {
204 let status = res.status();
205 let reason_phrase = status.canonical_reason().unwrap_or("").to_string();
206
207 let headers = res
208 .headers()
209 .iter()
210 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
211 .collect();
212
213 let mut body = Vec::new();
214 while let Some(frame) = res.frame().await {
215 let frame = frame.map_err(|_| NetworkError::HttpResponseFailed)?;
216 if let Some(chunk) = frame.data_ref() {
217 body.extend_from_slice(chunk);
218 }
219 }
220
221 Ok(Response {
222 url,
223 status: status.into(),
224 reason_phrase,
225 headers,
226 body,
227 })
228 }
229
230 async fn get_or_create_sender(&self, key: &HostKey) -> Result<HttpSender, NetworkError> {
231 if let Some(s) = self.sender_pool.write().unwrap().get_connection(key) {
232 return Ok(s);
233 }
234
235 self.create_connection(key).await
236 }
237
238 async fn create_connection(&self, key: &HostKey) -> Result<HttpSender, NetworkError> {
239 let addr = format!("{}:{}", key.host, key.port);
240 let stream = TcpStream::connect(addr)
241 .await
242 .map_err(|_| NetworkError::ConnectionFailed)?;
243
244 if key.scheme == Scheme::HTTPS {
245 let tls = TlsConnector::from(Arc::clone(&self.tls_config));
246 let key = key.clone();
247 let domain = rustls::pki_types::ServerName::try_from(key.host.clone())
248 .map_err(|_| NetworkError::InvalidDnsName)?;
249
250 let stream = tls
251 .connect(domain, stream)
252 .await
253 .map_err(|_| NetworkError::TlsFailed)?;
254
255 let (sender, conn) = conn::http1::handshake(TokioIo::new(stream))
256 .await
257 .map_err(|_| NetworkError::HttpHandshakeFailed)?;
258
259 self.spawn_connection_task(conn, key);
260 Ok(HttpSender::Http1(sender))
261 } else {
262 let (sender, conn) = conn::http1::handshake(TokioIo::new(stream))
263 .await
264 .map_err(|_| NetworkError::HttpHandshakeFailed)?;
265
266 self.spawn_connection_task(conn, key.clone());
267 Ok(HttpSender::Http1(sender))
268 }
269 }
270
271 fn spawn_connection_task(
272 &self,
273 conn: conn::http1::Connection<
274 TokioIo<impl tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static>,
275 Empty<Bytes>,
276 >,
277 key: HostKey,
278 ) {
279 let pool = Arc::clone(&self.sender_pool);
280 tokio::task::spawn_local(async move {
281 let _ = conn.await;
282 pool.write().unwrap().remove_connection(&key);
283 });
284 }
285}
286
287fn resolve_redirect(base: &Uri, location: &str) -> Result<Uri, NetworkError> {
288 if location.starts_with("http://") || location.starts_with("https://") {
289 return location.parse().map_err(|_| NetworkError::InvalidUri);
290 }
291
292 let scheme = base.scheme_str().unwrap_or("https");
293 let authority = base.authority().ok_or(NetworkError::InvalidUri)?;
294
295 let next = if location.starts_with("//") {
296 format!("{scheme}:{location}")
297 } else if location.starts_with('/') {
298 format!("{scheme}://{}{location}", authority)
299 } else {
300 let base_path = base.path();
301 let prefix = base_path.rsplit_once('/').map_or("", |x| x.0);
302 format!("{scheme}://{}{prefix}/{location}", authority)
303 };
304
305 next.parse().map_err(|_| NetworkError::InvalidUri)
306}