arti_ureq/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46
47use std::sync::{Arc, Mutex};
48
49use arti_client::{IntoTorAddr, TorClient};
50use ureq::{
51    http::{Uri, uri::Scheme},
52    tls::TlsProvider as UreqTlsProvider,
53    unversioned::{
54        resolver::{ArrayVec, ResolvedSocketAddrs, Resolver as UreqResolver},
55        transport::{Buffers, Connector as UreqConnector, LazyBuffers, NextTimeout, Transport},
56    },
57};
58
59use educe::Educe;
60use thiserror::Error;
61use tor_proto::client::stream::{DataReader, DataWriter};
62use tor_rtcompat::{Runtime, ToplevelBlockOn};
63
64#[cfg(feature = "rustls")]
65use ureq::unversioned::transport::RustlsConnector;
66
67#[cfg(feature = "native-tls")]
68use ureq::unversioned::transport::NativeTlsConnector;
69
70use futures::io::{AsyncReadExt, AsyncWriteExt};
71
72/// High-level functionality for accessing the Tor network as a client.
73pub use arti_client;
74
75/// Compatibility between different async runtimes for Arti.
76pub use tor_rtcompat;
77
78/// Underlying HTTP/S client library.
79pub use ureq;
80
81/// **Default usage**: Returns an instance of [`ureq::Agent`] using the default [`Connector`].
82///
83/// Equivalent to `Connector::new()?.agent()`.
84///
85/// # Example
86///
87/// ```rust,no_run
88/// arti_ureq::default_agent()
89///     .expect("Failed to create default agent.")
90///     .get("http://check.torproject.org/api/ip")
91///     .call()
92///     .expect("Failed to make request.");
93/// ```
94///
95/// Warning: This method creates a default [`arti_client::TorClient`]. Using multiple concurrent
96/// instances of `TorClient` is not recommended. Most programs should create a single `TorClient` centrally.
97pub fn default_agent() -> Result<ureq::Agent, Error> {
98    Ok(Connector::new()?.agent())
99}
100
101/// **Main entrypoint**: Object for making HTTP/S requests through Tor.
102///
103/// This type embodies an [`arti_client::TorClient`] and implements [`ureq::unversioned::transport::Connector`],
104/// allowing HTTP/HTTPS requests to be made with `ureq` over Tor.
105///
106/// Also bridges between async I/O (in Arti and Tokio) and sync I/O (in `ureq`).
107///
108/// ## A `Connector` object can be constructed in different ways.
109///
110/// ### 1. Use [`Connector::new`] to create a `Connector` with a default `TorClient`.
111/// ```rust,no_run
112/// let connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
113/// ```
114///
115/// ### 2. Use [`Connector::with_tor_client`] to create a `Connector` with a specific `TorClient`.
116/// ```rust,no_run
117/// let tor_client = arti_client::TorClient::with_runtime(
118///     tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.")
119/// )
120/// .create_unbootstrapped()
121/// .expect("Error creating Tor Client.");
122///
123/// let connector = arti_ureq::Connector::with_tor_client(tor_client);
124/// ```
125///
126/// ### 3. Use [`Connector::builder`] to create a `ConnectorBuilder` and configure a `Connector` with it.
127/// ```rust,no_run
128/// let connector = arti_ureq::Connector::<tor_rtcompat::PreferredRuntime>::builder()
129///    .expect("Failed to create ConnectorBuilder.")
130///    .build()
131///    .expect("Failed to create Connector.");
132/// ```
133///
134///
135/// ## Usage of `Connector`.
136///
137/// A `Connector` can be used to retrieve an [`ureq::Agent`] with [`Connector::agent`] or pass the `Connector`
138/// to [`ureq::Agent::with_parts`] along with a custom [`ureq::config::Config`] and a resolver
139/// obtained from [`Connector::resolver`] to retrieve a more configurable [`ureq::Agent`].
140///
141/// ### Retrieve an `ureq::Agent`.
142/// ```rust,no_run
143/// let connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
144/// let ureq_agent = connector.agent();
145/// ```
146///
147/// ### Pass as argument to `ureq::Agent::with_parts`.
148///
149/// We highly advice only using `Resolver` instead of e.g `ureq`'s [`ureq::unversioned::resolver::DefaultResolver`] to avoid DNS leaks.
150///
151/// ```rust,no_run
152/// let connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
153/// let resolver = connector.resolver();
154///
155/// let ureq_agent = ureq::Agent::with_parts(
156///    ureq::config::Config::default(),
157///    connector,
158///    resolver,
159/// );
160/// ```
161#[derive(Educe)]
162#[educe(Debug)]
163pub struct Connector<R: Runtime> {
164    /// [`arti_client::TorClient`] used to make requests.
165    #[educe(Debug(ignore))]
166    client: TorClient<R>,
167
168    /// Selected [`ureq::tls::TlsProvider`]. Possible options are `Rustls` or `NativeTls`. The default is `Rustls`.
169    tls_provider: UreqTlsProvider,
170}
171
172/// Object for constructing a [`Connector`].
173///
174/// Returned by [`Connector::builder`].
175///
176/// # Example
177///
178/// ```rust,no_run
179/// // `Connector` using `NativeTls` as Tls provider.
180/// let arti_connector = arti_ureq::Connector::<tor_rtcompat::PreferredRuntime>::builder()
181///    .expect("Failed to create ConnectorBuilder.")
182///     .tls_provider(ureq::tls::TlsProvider::NativeTls)
183///     .build()
184///     .expect("Failed to create Connector.");
185///
186/// // Retrieve `ureq::Agent` from the `Connector`.
187/// let ureq_agent = arti_connector.agent();
188/// ```
189pub struct ConnectorBuilder<R: Runtime> {
190    /// Configured [`arti_client::TorClient`] to be used with [`Connector`].
191    client: Option<TorClient<R>>,
192
193    /// Runtime
194    ///
195    /// If `client` is `None`, is used to create one.
196    /// If `client` is `Some`. we discard this in `.build()` in favour of `.client.runtime()`.
197    //
198    // (We could replace `client` and `runtime` with `Either<TorClient<R>, R>` or some such,
199    // but that would probably be more confusing.)
200    runtime: R,
201
202    /// Custom selected TlsProvider. Default is `Rustls`. Possible options are `Rustls` or `NativeTls`.
203    tls_provider: Option<UreqTlsProvider>,
204}
205
206/// Custom [`ureq::unversioned::transport::Transport`] enabling `ureq` to use
207/// [`arti_client::TorClient`] for making requests over Tor.
208#[derive(Educe)]
209#[educe(Debug)]
210struct HttpTransport<R: Runtime> {
211    /// Reader handle to Arti's read stream.
212    // TODO #1859
213    r: Arc<Mutex<DataReader>>,
214
215    /// Writer handle to Arti's write stream.
216    w: Arc<Mutex<DataWriter>>, // TODO #1859
217
218    /// Buffer to store data.
219    #[educe(Debug(ignore))]
220    buffer: LazyBuffers,
221
222    /// Runtime used to bridge between sync (`ureq`) and async I/O (`arti`).
223    rt: R,
224}
225
226/// Resolver implementing trait [`ureq::unversioned::resolver::Resolver`].
227///
228/// Resolves the host to an IP address using [`arti_client::TorClient::resolve`] avoiding DNS leaks.
229///
230/// An instance of [`Resolver`] can easily be retrieved using [`Connector::resolver()`].
231///
232/// This is needed when using `ureq::Agent::with_parts`,
233/// to avoid leaking DNS queries to the public local network.
234/// Usually, use [`Connector::agent`] instead,
235/// in which case you don't need to deal explicitly with a `Resolver`.
236///
237/// # Example
238///
239/// ```rust,no_run
240/// // Retrieve the resolver directly from your `Connector`.
241/// let arti_connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
242/// let arti_resolver = arti_connector.resolver();
243/// let ureq_agent = ureq::Agent::with_parts(
244///     ureq::config::Config::default(),
245///     arti_connector,
246///     arti_resolver,
247/// );
248/// ```
249#[derive(Educe)]
250#[educe(Debug)]
251pub struct Resolver<R: Runtime> {
252    /// [`arti_client::TorClient`] which contains the method [`arti_client::TorClient::resolve`].
253    ///
254    /// Use [`Connector::resolver`] or pass the client from your `Connector` to create an instance of `Resolver`.
255    #[educe(Debug(ignore))]
256    client: TorClient<R>,
257}
258
259/// Error making or using http connection.
260#[derive(Error, Debug)]
261#[non_exhaustive]
262pub enum Error {
263    /// Unsupported URI scheme.
264    #[error("unsupported URI scheme in {uri:?}")]
265    UnsupportedUriScheme {
266        /// URI.
267        uri: Uri,
268    },
269
270    /// Missing hostname.
271    #[error("Missing hostname in {uri:?}")]
272    MissingHostname {
273        /// URI.
274        uri: Uri,
275    },
276
277    /// Tor connection failed.
278    #[error("Tor connection failed")]
279    Arti(#[from] arti_client::Error),
280
281    /// General I/O error.
282    #[error("General I/O error")]
283    Io(#[from] std::io::Error),
284
285    /// TLS configuration mismatch.
286    #[error("TLS provider in config does not match the one in Connector.")]
287    TlsConfigMismatch,
288}
289
290// Map our own error kinds to Arti's error classification.
291impl tor_error::HasKind for Error {
292    #[rustfmt::skip]
293    fn kind(&self) -> tor_error::ErrorKind {
294        use tor_error::ErrorKind as EK;
295        match self {
296            Error::UnsupportedUriScheme{..} => EK::NotImplemented,
297            Error::MissingHostname{..}      => EK::BadApiUsage,
298            Error::Arti(e)                  => e.kind(),
299            Error::Io(..)                   => EK::Other,
300            Error::TlsConfigMismatch        => EK::BadApiUsage,
301        }
302    }
303}
304
305// Convert our own error type to ureq's error type.
306impl std::convert::From<Error> for ureq::Error {
307    fn from(err: Error) -> Self {
308        match err {
309            Error::MissingHostname { uri } => {
310                ureq::Error::BadUri(format!("Missing hostname in {uri:?}"))
311            }
312            Error::UnsupportedUriScheme { uri } => {
313                ureq::Error::BadUri(format!("Unsupported URI scheme in {uri:?}"))
314            }
315            Error::Arti(e) => ureq::Error::Io(std::io::Error::other(e)), // TODO #1858
316            Error::Io(e) => ureq::Error::Io(e),
317            Error::TlsConfigMismatch => {
318                ureq::Error::Tls("TLS provider in config does not match the one in Connector.")
319            }
320        }
321    }
322}
323
324// Implementation of trait [`ureq::unversioned::transport::Transport`] for [`HttpTransport`].
325//
326// Due to this implementation [`Connector`] can have a valid transport to be used with `ureq`.
327//
328// In this implementation we map the `ureq` buffer to the `arti` stream. And map the
329// methods to receive and transmit data between `ureq` and `arti`.
330//
331// Here we also bridge between the sync context `ureq` is usually called from and Arti's async I/O
332// by blocking the provided runtime. Preferably a runtime only used for `arti` should be provided.
333impl<R: Runtime + ToplevelBlockOn> Transport for HttpTransport<R> {
334    // Obtain buffers used by ureq.
335    fn buffers(&mut self) -> &mut dyn Buffers {
336        &mut self.buffer
337    }
338
339    // Write received data from ureq request to arti stream.
340    fn transmit_output(&mut self, amount: usize, _timeout: NextTimeout) -> Result<(), ureq::Error> {
341        let mut writer = self.w.lock().expect("lock poisoned");
342
343        let buffer = self.buffer.output();
344        let data_to_write = &buffer[..amount];
345
346        self.rt.block_on(async {
347            writer.write_all(data_to_write).await?;
348            writer.flush().await?;
349            Ok(())
350        })
351    }
352
353    // Read data from arti stream to ureq buffer.
354    fn await_input(&mut self, _timeout: NextTimeout) -> Result<bool, ureq::Error> {
355        let mut reader = self.r.lock().expect("lock poisoned");
356
357        let buffers = self.buffer.input_append_buf();
358        let size = self.rt.block_on(reader.read(buffers))?;
359        self.buffer.input_appended(size);
360
361        Ok(size > 0)
362    }
363
364    // Check if the connection is open.
365    fn is_open(&mut self) -> bool {
366        // We use `TorClient::connect` without `StreamPrefs::optimistic`,
367        // so `.is_connected()` tells us whether the stream has *ceased to be* open;
368        // i.e., we don't risk returning `false` because the stream isn't open *yet*.
369        self.r.lock().is_ok_and(|guard| {
370            guard
371                .client_stream_ctrl()
372                .is_some_and(|ctrl| ctrl.is_connected())
373        })
374    }
375}
376
377impl ConnectorBuilder<tor_rtcompat::PreferredRuntime> {
378    /// Returns instance of [`ConnectorBuilder`] with default values.
379    pub fn new() -> Result<Self, Error> {
380        Ok(ConnectorBuilder {
381            client: None,
382            runtime: tor_rtcompat::PreferredRuntime::create()?,
383            tls_provider: None,
384        })
385    }
386}
387
388impl<R: Runtime> ConnectorBuilder<R> {
389    /// Creates instance of [`Connector`] from the builder.
390    pub fn build(self) -> Result<Connector<R>, Error> {
391        let client = match self.client {
392            Some(client) => client,
393            None => TorClient::with_runtime(self.runtime).create_unbootstrapped()?,
394        };
395
396        let tls_provider = self.tls_provider.unwrap_or(get_default_tls_provider());
397
398        Ok(Connector {
399            client,
400            tls_provider,
401        })
402    }
403
404    /// Creates new [`Connector`] with an explicitly specified [`tor_rtcompat::Runtime`].
405    ///
406    /// The value `runtime` is only used if no [`arti_client::TorClient`] is configured using [`ConnectorBuilder::tor_client`].
407    pub fn with_runtime(runtime: R) -> Result<ConnectorBuilder<R>, Error> {
408        Ok(ConnectorBuilder {
409            client: None,
410            runtime,
411            tls_provider: None,
412        })
413    }
414
415    /// Configure a custom Tor client to be used with [`Connector`].
416    ///
417    /// Will also cause `client`'s `Runtime` to be used (obtained via [`TorClient::runtime()`]).
418    ///
419    /// If the client isn't `TorClient<PreferredRuntime>`, use [`ConnectorBuilder::with_runtime()`]
420    /// to create a suitable `ConnectorBuilder`.
421    pub fn tor_client(mut self, client: TorClient<R>) -> ConnectorBuilder<R> {
422        self.runtime = client.runtime().clone();
423        self.client = Some(client);
424        self
425    }
426
427    /// Configure the TLS provider to be used with [`Connector`].
428    pub fn tls_provider(mut self, tls_provider: UreqTlsProvider) -> Self {
429        self.tls_provider = Some(tls_provider);
430        self
431    }
432}
433
434// Implementation of trait [`ureq::unversioned::resolver::Resolver`] for [`Resolver`].
435//
436// `Resolver` can be used in [`ureq::Agent::with_parts`] to resolve the host to an IP address.
437//
438// Uses [`arti_client::TorClient::resolve`].
439//
440// We highly advice only using `Resolver` instead of e.g `ureq`'s [`ureq::unversioned::resolver::DefaultResolver`] to avoid DNS leaks.
441impl<R: Runtime + ToplevelBlockOn> UreqResolver for Resolver<R> {
442    /// Method to resolve the host to an IP address using `arti_client::TorClient::resolve`.
443    fn resolve(
444        &self,
445        uri: &Uri,
446        _config: &ureq::config::Config,
447        _timeout: NextTimeout,
448    ) -> Result<ResolvedSocketAddrs, ureq::Error> {
449        // We just retrieve the IP addresses using `arti_client::TorClient::resolve` and output
450        // it in a format that ureq can use.
451        let (host, port) = uri_to_host_port(uri)?;
452        let ips = self
453            .client
454            .runtime()
455            .block_on(async { self.client.resolve(&host).await })
456            .map_err(Error::from)?;
457
458        let mut array_vec: ArrayVec<core::net::SocketAddr, 16> = ArrayVec::from_fn(|_| {
459            core::net::SocketAddr::new(core::net::IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED), 0)
460        });
461
462        for ip in ips {
463            let socket_addr = core::net::SocketAddr::new(ip, port);
464            array_vec.push(socket_addr);
465        }
466
467        Ok(array_vec)
468    }
469}
470
471impl<R: Runtime + ToplevelBlockOn> Connector<R> {
472    /// Creates new instance with the provided [`arti_client::TorClient`].
473    pub fn with_tor_client(client: TorClient<R>) -> Connector<R> {
474        Connector {
475            client,
476            tls_provider: get_default_tls_provider(),
477        }
478    }
479}
480
481impl<R: Runtime + ToplevelBlockOn> UreqConnector<()> for Connector<R> {
482    type Out = Box<dyn Transport>;
483
484    /// Makes a connection using the Tor client.
485    ///
486    /// Returns a `HttpTransport` which implements trait [`ureq::unversioned::transport::Transport`].
487    fn connect(
488        &self,
489        details: &ureq::unversioned::transport::ConnectionDetails,
490        _chained: Option<()>,
491    ) -> Result<Option<Self::Out>, ureq::Error> {
492        // Retrieve host and port from the ConnectionDetails.
493        let (host, port) = uri_to_host_port(details.uri)?;
494
495        // Convert to an address we can use to connect over the Tor network.
496        let addr = (host.as_str(), port)
497            .into_tor_addr()
498            .map_err(|e| Error::Arti(e.into()))?;
499
500        // Retrieve stream from Tor connection.
501        let stream = self
502            .client
503            .runtime()
504            .block_on(async { self.client.connect(addr).await })
505            .map_err(Error::from)?;
506
507        // Return a HttpTransport with a reader and writer to the stream.
508        let (r, w) = stream.split();
509        Ok(Some(Box::new(HttpTransport {
510            r: Arc::new(Mutex::new(r)),
511            w: Arc::new(Mutex::new(w)),
512            buffer: LazyBuffers::new(2048, 2048),
513            rt: self.client.runtime().clone(),
514        })))
515    }
516}
517
518impl Connector<tor_rtcompat::PreferredRuntime> {
519    /// Returns new `Connector` with default values.
520    ///
521    /// To configure a non-default `Connector`,
522    /// use [`ConnectorBuilder`].
523    ///
524    /// Warning: This method creates a default [`arti_client::TorClient`]. Using multiple concurrent
525    /// instances of `TorClient` is not recommended. Most programs should create a single `TorClient` centrally.
526    pub fn new() -> Result<Self, Error> {
527        Self::builder()?.build()
528    }
529}
530
531impl<R: Runtime + ToplevelBlockOn> Connector<R> {
532    /// Returns instance of [`Resolver`] implementing trait [`ureq::unversioned::resolver::Resolver`].
533    pub fn resolver(&self) -> Resolver<R> {
534        Resolver {
535            client: self.client.clone(),
536        }
537    }
538
539    /// Returns instance of [`ureq::Agent`].
540    ///
541    /// Equivalent to using [`ureq::Agent::with_parts`] with the default [`ureq::config::Config`]
542    /// and this `Connector` and the resolver obtained from [`Connector::resolver()`].
543    ///
544    /// # Example
545    ///
546    /// ```rust,no_run
547    /// let ureq_agent = arti_ureq::Connector::new()
548    ///     .expect("Failed to create Connector")
549    ///     .agent();
550    ///
551    /// // Use the agent to make a request.
552    /// ureq_agent
553    ///     .get("https://check.torproject.org/api/ip")
554    ///     .call()
555    ///     .expect("Failed to make request.");
556    /// ```
557    pub fn agent(self) -> ureq::Agent {
558        let resolver = self.resolver();
559
560        let ureq_config = ureq::config::Config::builder()
561            .tls_config(
562                ureq::tls::TlsConfig::builder()
563                    .provider(self.tls_provider)
564                    .build(),
565            )
566            .build();
567
568        ureq::Agent::with_parts(ureq_config, self.connector_chain(), resolver)
569    }
570
571    /// Returns instance of [`ureq::Agent`] using the provided [`ureq::config::Config`].
572    ///
573    /// Equivalent to [`Connector::agent`] but allows the user to provide a custom [`ureq::config::Config`].
574    pub fn agent_with_ureq_config(
575        self,
576        config: ureq::config::Config,
577    ) -> Result<ureq::Agent, Error> {
578        let resolver = self.resolver();
579
580        if self.tls_provider != config.tls_config().provider() {
581            return Err(Error::TlsConfigMismatch);
582        }
583
584        Ok(ureq::Agent::with_parts(
585            config,
586            self.connector_chain(),
587            resolver,
588        ))
589    }
590
591    /// Returns connector chain depending on features flag.
592    fn connector_chain(self) -> impl UreqConnector {
593        let chain = self;
594
595        #[cfg(feature = "rustls")]
596        let chain = chain.chain(RustlsConnector::default());
597
598        #[cfg(feature = "native-tls")]
599        let chain = chain.chain(NativeTlsConnector::default());
600
601        chain
602    }
603}
604
605/// Returns the default [`ureq::tls::TlsProvider`] based on the features flag.
606pub fn get_default_tls_provider() -> UreqTlsProvider {
607    if cfg!(feature = "native-tls") {
608        UreqTlsProvider::NativeTls
609    } else {
610        UreqTlsProvider::Rustls
611    }
612}
613
614/// Implementation to make [`ConnectorBuilder`] accessible from [`Connector`].
615///
616/// # Example
617///
618/// ```rust,no_run
619/// let rt = tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.");
620/// let tls_provider = arti_ureq::get_default_tls_provider();
621///
622/// let client = arti_client::TorClient::with_runtime(rt.clone())
623///     .create_unbootstrapped()
624///     .expect("Error creating Tor Client.");
625///
626/// let builder = arti_ureq::ConnectorBuilder::<tor_rtcompat::PreferredRuntime>::new()
627///     .expect("Failed to create ConnectorBuilder.")
628///     .tor_client(client)
629///     .tls_provider(tls_provider);
630///
631/// let arti_connector = builder.build();
632/// ```
633impl Connector<tor_rtcompat::PreferredRuntime> {
634    /// Returns new [`ConnectorBuilder`] with default values.
635    pub fn builder() -> Result<ConnectorBuilder<tor_rtcompat::PreferredRuntime>, Error> {
636        ConnectorBuilder::new()
637    }
638}
639
640/// Parse the URI.
641///
642/// Obtain the host and port.
643fn uri_to_host_port(uri: &Uri) -> Result<(String, u16), Error> {
644    let host = uri
645        .host()
646        .ok_or_else(|| Error::MissingHostname { uri: uri.clone() })?;
647
648    let port = match uri.scheme() {
649        Some(scheme) if scheme == &Scheme::HTTPS => Ok(443),
650        Some(scheme) if scheme == &Scheme::HTTP => Ok(80),
651        Some(_) => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
652        None => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
653    }?;
654
655    Ok((host.to_owned(), port))
656}
657
658#[cfg(test)]
659mod arti_ureq_test {
660    // @@ begin test lint list maintained by maint/add_warning @@
661    #![allow(clippy::bool_assert_comparison)]
662    #![allow(clippy::clone_on_copy)]
663    #![allow(clippy::dbg_macro)]
664    #![allow(clippy::mixed_attributes_style)]
665    #![allow(clippy::print_stderr)]
666    #![allow(clippy::print_stdout)]
667    #![allow(clippy::single_char_pattern)]
668    #![allow(clippy::unwrap_used)]
669    #![allow(clippy::unchecked_duration_subtraction)]
670    #![allow(clippy::useless_vec)]
671    #![allow(clippy::needless_pass_by_value)]
672    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
673
674    use super::*;
675    use arti_client::config::TorClientConfigBuilder;
676    use std::str::FromStr;
677    use test_temp_dir::test_temp_dir;
678
679    const ARTI_TEST_LIVE_NETWORK: &str = "ARTI_TEST_LIVE_NETWORK";
680    const ARTI_TESTING_ON_LOCAL: &str = "ARTI_TESTING_ON_LOCAL";
681
682    // Helper function to check if two types are equal. The types in this library are to
683    // complex to use the `==` operator or (Partial)Eq. So we compare the types of the individual properties instead.
684    fn assert_equal_types<T>(_: &T, _: &T) {}
685
686    // Helper function to check if the environment variable ARTI_TEST_LIVE_NETWORK is set to 1.
687    // We only want to run tests using the live network when the user explicitly wants to.
688    fn test_live_network() -> bool {
689        let run_test = std::env::var(ARTI_TEST_LIVE_NETWORK).is_ok_and(|v| v == "1");
690        if !run_test {
691            println!("Skipping test, set {}=1 to run.", ARTI_TEST_LIVE_NETWORK);
692        }
693
694        run_test
695    }
696
697    // Helper function to check if the environment variable ARTI_TESTING_ON_LOCAL is set to 1.
698    // Some tests, especially those that create default `Connector` instances, or test the `ConnectorBuilder`,
699    // are not reliable when run on CI.  We don't know why that is.  It's probably a bug.  TODO fix the tests!
700    fn testing_on_local() -> bool {
701        let run_test = std::env::var(ARTI_TESTING_ON_LOCAL).is_ok_and(|v| v == "1");
702        if !run_test {
703            println!("Skipping test, set {}=1 to run.", ARTI_TESTING_ON_LOCAL);
704        }
705
706        run_test
707    }
708
709    // Helper method to allow tests to be ran in a closure.
710    fn test_with_tor_client<R: Runtime>(rt: R, f: impl FnOnce(TorClient<R>)) {
711        let temp_dir = test_temp_dir!();
712        temp_dir.used_by(move |temp_dir| {
713            let arti_config = TorClientConfigBuilder::from_directories(
714                temp_dir.join("state"),
715                temp_dir.join("cache"),
716            )
717            .build()
718            .expect("Failed to build TorClientConfig");
719
720            let tor_client = arti_client::TorClient::with_runtime(rt)
721                .config(arti_config)
722                .create_unbootstrapped()
723                .expect("Error creating Tor Client.");
724
725            f(tor_client);
726        });
727    }
728
729    // Helper function to make a request to check.torproject.org/api/ip and check if
730    // it was done over Tor.
731    fn request_is_tor(agent: ureq::Agent, https: bool) -> bool {
732        let mut request = agent
733            .get(format!(
734                "http{}://check.torproject.org/api/ip",
735                if https { "s" } else { "" }
736            ))
737            .call()
738            .expect("Failed to make request.");
739        let response = request
740            .body_mut()
741            .read_to_string()
742            .expect("Failed to read body.");
743        let json_response: serde_json::Value =
744            serde_json::from_str(&response).expect("Failed to parse JSON.");
745        json_response
746            .get("IsTor")
747            .expect("Failed to retrieve IsTor property from response")
748            .as_bool()
749            .expect("Failed to convert IsTor to bool")
750    }
751
752    // Quick internal test to check if our helper function `equal_types` works as expected.
753    // Otherwise our other tests might not be reliable.
754    #[test]
755    fn test_equal_types() {
756        assert_equal_types(&1, &i32::MIN);
757        assert_equal_types(&1, &i64::MIN);
758        assert_equal_types(&String::from("foo"), &String::with_capacity(1));
759    }
760
761    // `Connector::new` should return the default `Connector`.
762    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
763    #[test]
764    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
765    fn articonnector_new_returns_default() {
766        if !testing_on_local() {
767            return;
768        }
769
770        let actual_connector = Connector::new().expect("Failed to create Connector.");
771        let expected_connector = Connector {
772            client: TorClient::with_runtime(
773                tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
774            )
775            .create_unbootstrapped()
776            .expect("Error creating Tor Client."),
777            tls_provider: UreqTlsProvider::Rustls,
778        };
779
780        assert_equal_types(&expected_connector, &actual_connector);
781        assert_equal_types(
782            &actual_connector.client.runtime().clone(),
783            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
784        );
785        assert_eq!(
786            &actual_connector.tls_provider,
787            &ureq::tls::TlsProvider::Rustls,
788        );
789    }
790
791    // `Connector::with_tor_client` should return a `Connector` with specified Tor client set.
792    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
793    #[test]
794    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
795    fn articonnector_with_tor_client() {
796        if !testing_on_local() {
797            return;
798        }
799
800        let tor_client = TorClient::with_runtime(
801            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
802        )
803        .create_unbootstrapped()
804        .expect("Error creating Tor Client.");
805
806        let actual_connector = Connector::with_tor_client(tor_client);
807        let expected_connector = Connector {
808            client: TorClient::with_runtime(
809                tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
810            )
811            .create_unbootstrapped()
812            .expect("Error creating Tor Client."),
813            tls_provider: UreqTlsProvider::Rustls,
814        };
815
816        assert_equal_types(&expected_connector, &actual_connector);
817        assert_equal_types(
818            &actual_connector.client.runtime().clone(),
819            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
820        );
821        assert_eq!(
822            &actual_connector.tls_provider,
823            &ureq::tls::TlsProvider::Rustls,
824        );
825    }
826
827    // The default instance returned by `Connector::builder` should equal to the default `Connector`.
828    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
829    #[test]
830    fn articonnectorbuilder_new_returns_default() {
831        if !testing_on_local() {
832            return;
833        }
834
835        let expected = Connector::new().expect("Failed to create Connector.");
836        let actual = Connector::<tor_rtcompat::PreferredRuntime>::builder()
837            .expect("Failed to create ConnectorBuilder.")
838            .build()
839            .expect("Failed to create Connector.");
840
841        assert_equal_types(&expected, &actual);
842        assert_equal_types(&expected.client.runtime(), &actual.client.runtime());
843        assert_eq!(&expected.tls_provider, &actual.tls_provider);
844    }
845
846    // `ConnectorBuilder::with_runtime` should return a `ConnectorBuilder` with the specified runtime set.
847    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
848    #[cfg(all(feature = "tokio", feature = "rustls"))]
849    #[test]
850    fn articonnectorbuilder_with_runtime() {
851        if !testing_on_local() {
852            return;
853        }
854
855        let arti_connector = ConnectorBuilder::with_runtime(
856            tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime."),
857        )
858        .expect("Failed to create ConnectorBuilder.")
859        .build()
860        .expect("Failed to create Connector.");
861
862        assert_equal_types(
863            &arti_connector.client.runtime().clone(),
864            &tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime."),
865        );
866
867        let arti_connector = ConnectorBuilder::with_runtime(
868            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
869        )
870        .expect("Failed to create ConnectorBuilder.")
871        .build()
872        .expect("Failed to create Connector.");
873
874        assert_equal_types(
875            &arti_connector.client.runtime().clone(),
876            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
877        );
878    }
879
880    // `ConnectorBuilder::tor_client` should return a `Connector` with the specified `TorClient` set.
881    #[cfg(all(feature = "tokio", feature = "rustls"))]
882    #[test]
883    fn articonnectorbuilder_set_tor_client() {
884        let rt =
885            tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime.");
886
887        test_with_tor_client(rt.clone(), move |tor_client| {
888            let arti_connector = ConnectorBuilder::with_runtime(rt)
889                .expect("Failed to create ConnectorBuilder.")
890                .tor_client(tor_client.clone().isolated_client())
891                .build()
892                .expect("Failed to create Connector.");
893
894            assert_equal_types(
895                &arti_connector.client.runtime().clone(),
896                &tor_rtcompat::tokio::TokioRustlsRuntime::create()
897                    .expect("Failed to create runtime."),
898            );
899        });
900    }
901
902    // Test if the method `uri_to_host_port` returns the correct parameters.
903    #[test]
904    fn test_uri_to_host_port() {
905        let uri = Uri::from_str("http://torproject.org").expect("Error parsing uri.");
906        let (host, port) = uri_to_host_port(&uri).expect("Error parsing uri.");
907
908        assert_eq!(host, "torproject.org");
909        assert_eq!(port, 80);
910
911        let uri = Uri::from_str("https://torproject.org").expect("Error parsing uri.");
912        let (host, port) = uri_to_host_port(&uri).expect("Error parsing uri.");
913
914        assert_eq!(host, "torproject.org");
915        assert_eq!(port, 443);
916
917        let uri = Uri::from_str("https://www.torproject.org/test").expect("Error parsing uri.");
918        let (host, port) = uri_to_host_port(&uri).expect("Error parsing uri.");
919
920        assert_eq!(host, "www.torproject.org");
921        assert_eq!(port, 443);
922    }
923
924    // Test if `arti-ureq` default agent uses Tor to make the request.
925    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
926    #[test]
927    fn request_goes_over_tor() {
928        if !test_live_network() {
929            return;
930        }
931
932        let is_tor = request_is_tor(
933            default_agent().expect("Failed to retrieve default agent."),
934            true,
935        );
936
937        assert_eq!(is_tor, true);
938    }
939
940    // Test if `arti-ureq` default agent uses Tor to make the request.
941    // This test also checks if the Tor API returns false when the request is made with an
942    // `ureq` agent that is not configured to use Tor to ensure the test is reliable.
943    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
944    #[test]
945    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
946    fn request_goes_over_tor_with_unsafe_check() {
947        if !test_live_network() {
948            return;
949        }
950
951        let is_tor = request_is_tor(ureq::Agent::new_with_defaults(), true);
952        assert_eq!(is_tor, false);
953
954        let is_tor = request_is_tor(
955            default_agent().expect("Failed to retrieve default agent."),
956            true,
957        );
958        assert_eq!(is_tor, true);
959    }
960
961    // Test if the `ureq` client configured with `Connector` uses Tor tor make the request using bare HTTP.
962    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
963    #[test]
964    fn request_with_bare_http() {
965        if !test_live_network() {
966            return;
967        }
968
969        let rt = tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.");
970
971        test_with_tor_client(rt, |tor_client| {
972            let arti_connector = Connector::with_tor_client(tor_client);
973            let is_tor = request_is_tor(arti_connector.agent(), false);
974
975            assert_eq!(is_tor, true);
976        });
977    }
978
979    // Test if `get_default_tls_provider` correctly derives the TLS provider from the feature flags.
980    #[test]
981    fn test_get_default_tls_provider() {
982        #[cfg(feature = "native-tls")]
983        assert_eq!(get_default_tls_provider(), UreqTlsProvider::NativeTls);
984
985        #[cfg(not(feature = "native-tls"))]
986        assert_eq!(get_default_tls_provider(), UreqTlsProvider::Rustls);
987    }
988
989    // Test if configuring the `Connector` using `get_default_tls_provider` correctly sets the TLS provider
990    // based on the feature flags.
991    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
992    #[test]
993    fn test_tor_client_with_get_default_tls_provider() {
994        if !testing_on_local() {
995            return;
996        }
997
998        let tor_client = TorClient::with_runtime(
999            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
1000        )
1001        .create_unbootstrapped()
1002        .expect("Error creating Tor Client.");
1003
1004        let arti_connector = Connector::<tor_rtcompat::PreferredRuntime>::builder()
1005            .expect("Failed to create ConnectorBuilder.")
1006            .tor_client(tor_client.clone().isolated_client())
1007            .tls_provider(get_default_tls_provider())
1008            .build()
1009            .expect("Failed to create Connector.");
1010
1011        #[cfg(feature = "native-tls")]
1012        assert_eq!(
1013            &arti_connector.tls_provider,
1014            &ureq::tls::TlsProvider::NativeTls,
1015        );
1016
1017        #[cfg(not(feature = "native-tls"))]
1018        assert_eq!(
1019            &arti_connector.tls_provider,
1020            &ureq::tls::TlsProvider::Rustls,
1021        );
1022    }
1023}