diff --git a/Cargo.toml b/Cargo.toml index 9565bf9..421f9a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,7 @@ keywords = ["tor", "privacy", "anonymity"] anyhow = "1.0.75" arti-client = { version = "0.35", features = ["full"] } arti-ureq = "0.35" +tor-rtcompat = "0.35" tor-circmgr = "0.35" tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util"] } +futures = "0.3.14" \ No newline at end of file diff --git a/src/get_request_tests.rs b/src/get_request_tests.rs deleted file mode 100644 index abdeefe..0000000 --- a/src/get_request_tests.rs +++ /dev/null @@ -1,46 +0,0 @@ -#[cfg(test)] -mod tests { - use anyhow::Result; - use crate::ureq_builder::*; - use arti_ureq::ureq::Agent; - use crate::tor_client_builder::{choose_tls_provider, make_tor_client}; - const TEST_URL: &str = "https://check.torproject.org/api/ip"; - const TEST_ONION: &str = "http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion"; - - /// Builds all components - fn builds_ureq_agent() -> Result { - let tls = choose_tls_provider(); - let tor = make_tor_client()?; - let connector = make_connector(tor, tls)?; - let agent = make_ureq_agent(connector, tls)?; - Ok(agent) - } - - /// Test: send get request to url over Tor. - /// This test may take a little while on the first run - #[test] - fn fetches_url_over_tor() -> Result<()> { - let agent = builds_ureq_agent()?; - - let body = fetch_with_agent(&agent, TEST_URL)?; - //sanity check: - assert!(!body.trim().is_empty(), "empty body from {}", TEST_URL); - Ok(()) - } - - // /// Test: send get request to .onion over Tor. - // /// This test may take a little while on the first run - // #[test] - // fn fetches_onion_over_tor() -> Result<()> { - // let tls = choose_tls_provider(); - // let tor = make_tor_client()?; - // let connector = make_connector(tor, tls)?; - // let agent = make_ureq_agent(connector, tls)?; - // - // let body = fetch_with_agent(&agent, TEST_ONION)?; - // //sanity check: - // assert!(!body.trim().is_empty(), "empty body from {}", TEST_ONION); - // eprintln!("Response: {}", body); - // Ok(()) - // } -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 35a9b05..d8d6920 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,3 @@ pub mod ureq_builder; -pub mod get_request_tests; +pub mod tests; pub mod tor_client_builder; \ No newline at end of file diff --git a/src/tests/get_request_tests.rs b/src/tests/get_request_tests.rs new file mode 100644 index 0000000..9b42641 --- /dev/null +++ b/src/tests/get_request_tests.rs @@ -0,0 +1,72 @@ +#[cfg(test)] +mod tests { + use anyhow::Result; + use arti_client::TorClient; + use crate::ureq_builder::*; + use arti_ureq::ureq::Agent; + use crate::tor_client_builder::{choose_tls_provider, fetch_with_client, make_tor_client, make_tor_client_config}; + // We will use ureq for https which does the needed tls + const TEST_URL_HTTPS: &str = "https://check.torproject.org/api/ip"; + // For manual connection using the arti tor client, we will use the following over http + const TEST_URL_HTTP_PLAIN: &str = "check.torproject.org"; + const TEST_URL_PATH_HTTP_PLAIN: &str = "/api/ip"; + // We will use the following onion address with the tor client. + const TEST_ONION: &str = "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion"; + const TEST_ONION_PATH: &str = "/"; + /// Builds all components + fn build_ureq_agent() -> Result { + let tls = choose_tls_provider(); + let tor = make_tor_client()?; + let connector = make_connector(tor, tls)?; + let agent = make_ureq_agent(connector, tls)?; + Ok(agent) + } + + /// Test: send get request to url over Tor using ureq. + /// This test may take a little while on the first run + #[test] + fn fetches_url_over_tor_ureq() -> Result<()> { + let agent = build_ureq_agent()?; + + let body = fetch_with_agent(&agent, TEST_URL_HTTPS)?; + //sanity check: + assert!(!body.trim().is_empty(), "empty body from {}", TEST_URL_HTTPS); + Ok(()) + } + + /// Test: send get request to url over Tor using the tor client. + /// This only works for http since there is no tls provider for https. + /// This test may take a little while on the first run + #[tokio::test(flavor = "multi_thread")] + async fn fetches_url_over_tor_client() -> Result<()> { + let config = make_tor_client_config(); + + let tor_client = TorClient::builder() + .config(config) + .create_unbootstrapped()?; + + let body = fetch_with_client(tor_client, TEST_URL_HTTP_PLAIN, TEST_URL_PATH_HTTP_PLAIN).await?; + //sanity check: + assert!(!body.trim().is_empty(), "empty body from {}", TEST_URL_HTTP_PLAIN); + Ok(()) + } + + /// Test: send get request to onion address over Tor using the tor client. + /// again this is http and no tls + /// warning: this will print out an entire html page + /// This test may take a little while on the first run + #[tokio::test(flavor = "multi_thread")] + async fn fetches_onion_url_over_tor_client() -> Result<()> { + let config = make_tor_client_config(); + + let tor_client = TorClient::builder() + .config(config) + .create_unbootstrapped()?; + + let body = fetch_with_client(tor_client, TEST_ONION, TEST_ONION_PATH).await?; + //sanity check: + assert!(!body.trim().is_empty(), "empty body from {}", TEST_ONION); + Ok(()) + } + +} \ No newline at end of file diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..d9a41cf --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1 @@ +pub mod get_request_tests; \ No newline at end of file diff --git a/src/tor_client_builder.rs b/src/tor_client_builder.rs index dcaf788..1123066 100644 --- a/src/tor_client_builder.rs +++ b/src/tor_client_builder.rs @@ -1,9 +1,12 @@ -use anyhow::Context; +use anyhow::{Context, Result}; +use arti_client::config::BoolOrAuto; +use arti_client::StreamPrefs; use arti_ureq::ureq; +use futures::io::{AsyncReadExt, AsyncWriteExt}; /// Create a Tor client -pub fn make_tor_client() -> anyhow::Result> { - let rt = arti_ureq::tor_rtcompat::PreferredRuntime::create() +pub fn make_tor_client() -> Result> { + let rt = tor_rtcompat::PreferredRuntime::create() .context("Failed to create runtime.")?; let tor_client = arti_client::TorClient::with_runtime(rt) @@ -11,10 +14,6 @@ pub fn make_tor_client() -> anyhow::Result arti_client::config::TorClientConfig { pub fn choose_tls_provider() -> ureq::tls::TlsProvider { arti_ureq::get_default_tls_provider() } + +/// Fetch a URL via the provided tor client, returning the body as String. +pub async fn fetch_with_client(mut tor_client: arti_client::TorClient, url: &str, path: &str) -> Result { + // set `connect_to_onion_services` to `true` so we can send onion urls to the tor client + let mut sp = StreamPrefs::default(); + sp.connect_to_onion_services(BoolOrAuto::Explicit(true)); + tor_client.set_stream_prefs(sp); + + println!("[+] Making connection to: {}", url); + + let mut stream = tor_client.connect((url,80)).await?; + + let req = format!( + "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", + path, + url, + ); + + stream.write_all(req.as_bytes()).await?; + stream.flush().await?; + + let mut buf = Vec::new(); + stream.read_to_end(&mut buf).await?; + + let response = String::from_utf8_lossy(&buf).into_owned(); + println!("{}", String::from_utf8_lossy(&buf)); + + println!("Response: {response}"); + + Ok(response) +} \ No newline at end of file