Created
January 27, 2025 19:22
-
-
Save developer-commit/9e76536902a1497c5af248ff6c995f32 to your computer and use it in GitHub Desktop.
This Rust code provides a client implementation for HTTPS communication over the Tor network
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
***warning*** | |
I have not checked with tools like Wireshark whether it was transmitted in an encrypted form. | |
arti-client = "0.24.0" | |
tokio = { version = "1", features = ["full"] } | |
tor-rtcompat = "0.24.0" | |
tokio-rustls = "0.26.0" | |
webpki-roots = "=0.26.7" | |
webpki = "0.22.4" | |
rustls-pki-types = "1.10.0" | |
serde = { version = "1.0", features = ["derive"] } | |
serde_json = "1.0" | |
url = "2.5.4" | |
*/ | |
use arti_client::{DataStream, TorClient, TorClientConfig}; | |
use serde::Serialize; | |
use tokio::io::{AsyncReadExt, AsyncWriteExt}; | |
use tokio_rustls::client::TlsStream; | |
use std::collections::HashMap; | |
use std::sync::Arc; | |
use tokio_rustls::rustls::{ClientConfig, RootCertStore}; | |
use tokio_rustls::TlsConnector; | |
use rustls_pki_types::ServerName; | |
use tor_rtcompat::PreferredRuntime; | |
struct TorStream{ | |
stream : TlsStream<DataStream> | |
} | |
pub struct Client { | |
tls_connector : TlsConnector, // rustls lib | |
tor_connector : TorClient<PreferredRuntime>, | |
stream_map : HashMap<(String, u16), TorStream> // for recycling of connection | |
} | |
impl Client { | |
pub async fn new() -> Result<Self, Box<dyn std::error::Error>>{ | |
let mut root_cert_store = RootCertStore::empty(); | |
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); | |
let config = ClientConfig::builder() | |
.with_root_certificates(root_cert_store) | |
.with_no_client_auth(); | |
let tls_connector = TlsConnector::from(Arc::new(config)); | |
let config = TorClientConfig::default(); | |
let tor_connector = TorClient::create_bootstrapped(config).await?; | |
Ok( | |
Self { | |
tls_connector, | |
tor_connector, | |
stream_map: HashMap::new() | |
} | |
) | |
} | |
//TLS Connect | |
async fn make_stream( | |
&mut self, | |
key: &(String, u16) | |
) -> Result<TorStream, Box<dyn std::error::Error>> { | |
let (host, port) = (&key.0, key.1); | |
let dnsname = ServerName::try_from(host.clone()) | |
.map_err(|e| format!("Invalid DNS name: {}", e))?; | |
let tor_stream = self.tor_connector.connect((host.clone(), port)).await?; | |
let stream = match port { | |
443 => { | |
let tls_stream = self.tls_connector.connect(dnsname, tor_stream).await | |
.map_err(|e| format!("TLS connection failed: {}", e))?; | |
tls_stream | |
}, | |
_ => return Err(format!("Unsupported port: {}", port).into()), | |
}; | |
Ok(TorStream { | |
stream, | |
}) | |
} | |
/* connection recycling */ | |
async fn load_stream( | |
&mut self, | |
key: &(String, u16) | |
) -> Result<&mut TorStream, Box<dyn std::error::Error>>{ | |
if !self.stream_map.contains_key(key) { | |
let stream = self.make_stream(key).await?; | |
self.stream_map.insert(key.clone(), stream); | |
} | |
Ok(self.stream_map.get_mut(key).ok_or("err")?) | |
} | |
// impl http GET | |
pub async fn get( | |
&mut self, | |
url: &str | |
) -> Result<String, Box<dyn std::error::Error>> | |
{ | |
let (host, port, path) = Self::parse_url(url).await?; | |
let key = (host.clone(), port); | |
let tor_stream = self.load_stream(&key).await? ; | |
let request = format!( | |
"GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", | |
path, host | |
); | |
tor_stream.stream.write_all(request.as_bytes()).await?; | |
tor_stream.stream.flush().await?; | |
let mut response = Vec::new(); | |
tor_stream.stream.read_to_end(&mut response).await?; | |
let res = String::from_utf8(response)?; | |
Ok(res) | |
} | |
// impl http POST | |
pub async fn post<T: Serialize>( | |
&mut self, | |
url: &str, | |
json_body: &T, | |
) -> Result<String, Box<dyn std::error::Error>> | |
{ | |
let (host, port, path) = Self::parse_url(url).await?; | |
let key = (host.clone(), port); | |
let tor_stream = self.load_stream(&key).await? ; | |
let body = serde_json::to_string(json_body)?; | |
let content_length = body.len(); | |
let request = format!( | |
"POST {} HTTP/1.1\r\nHost: {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", | |
path, host, content_length, body | |
); | |
tor_stream.stream.write_all(request.as_bytes()).await?; | |
tor_stream.stream.flush().await?; | |
let mut response = Vec::new(); | |
tor_stream.stream.read_to_end(&mut response).await?; | |
let res = String::from_utf8(response)?; | |
Ok(res) | |
} | |
pub async fn close(&mut self, key: &(String, u16)) -> Result<(), Box<dyn std::error::Error>>{ | |
let stream = self.load_stream(&key).await?; | |
stream.stream.shutdown().await?; | |
Ok(()) | |
} | |
/// Parses a URL into host, port, and path components. | |
pub async fn parse_url(url: &str) -> Result<(String, u16, String), Box<dyn std::error::Error>> { | |
let url = url::Url::parse(url)?; | |
let host = url.host_str().ok_or("Invalid URL: No host found")?.to_string(); | |
let port = url.port_or_known_default().ok_or("Invalid URL: No port found")?; | |
let path = if url.path().is_empty() { | |
"/".to_string() | |
} else { | |
url.path().to_string() | |
}; | |
Ok((host, port, path)) | |
} | |
} | |
//main.rs | |
#[tokio::main] | |
async fn main() -> Result<(), Box<dyn std::error::Error>> { | |
let mut cli = tor_client::client::Client::new().await?; | |
let res = cli.get("https://api.ip.pe.kr/").await?; | |
print!("{}", res ); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment