1pub use crate::auth;
2pub use crate::errors;
3use crate::is_ok_status;
4use http::Uri;
5use rustls_pki_types::ServerName;
6use std::{net::SocketAddrV4, sync::Arc};
7use tokio::{
8 io::{AsyncReadExt, AsyncWriteExt},
9 net::TcpStream,
10};
11use tokio_rustls::{TlsConnector, client::TlsStream, rustls::ClientConfig};
12
13pub struct HttpsProxy {
14 config: Arc<ClientConfig>,
15}
16
17impl HttpsProxy {
18 pub fn with_client_config(config: Arc<ClientConfig>) -> Self {
22 Self { config }
23 }
24
25 pub async fn tunnel(
33 &self,
34 dest: Uri,
35 proxy_server: SocketAddrV4,
36 auth: Option<auth::Auth>,
37 ) -> crate::Result<TlsStream<TcpStream>> {
38 let mut conn = TcpStream::connect(proxy_server).await?;
39 let connector = TlsConnector::from(self.config.clone());
40 let dnsname =
41 ServerName::try_from(format!("{}", dest.host().ok_or(errors::Error::InvalidURI)?))?;
42
43 let target = format!(
44 "{}:{}",
45 dest.host().ok_or(errors::Error::InvalidURI)?,
46 dest.port().ok_or(errors::Error::InvalidURI)?
47 );
48 let mut packet = format!("CONNECT {target} HTTP/1.1\r\nHost: {target}\r\n");
49 if let Some(auth::Auth::HTTPAuthorizationHeader(header)) = auth {
50 packet.push_str(&format!("{}\r\n", header));
51 }
52
53 packet.push_str("\r\n");
54
55 conn.write_all(packet.as_bytes()).await?;
56 conn.flush().await?;
57
58 let mut response = Vec::new();
59 loop {
60 let mut buf = [0u8; 1024];
61 let n = conn.read(&mut buf).await?;
62
63 if n == 0 {
64 break;
66 }
67
68 response.extend_from_slice(&buf[..n]);
69 if response.ends_with(b"\r\n\r\n") {
70 break;
71 }
72 }
73
74 let response = String::from_utf8_lossy(&response);
75 if !is_ok_status(response.clone()) {
76 return Err(errors::Error::ProxyResponseNotOk(response.into()));
77 }
78
79 let tunnel = connector.connect(dnsname, conn).await?;
80 Ok(tunnel)
81 }
82}