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::Scheme, Uri},
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::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<R: Runtime> ConnectorBuilder<R> {
378    /// Returns instance of [`ConnectorBuilder`] with default values.
379    pub fn new() -> Result<ConnectorBuilder<tor_rtcompat::PreferredRuntime>, Error> {
380        Ok(ConnectorBuilder {
381            client: None,
382            runtime: tor_rtcompat::PreferredRuntime::create()?,
383            tls_provider: None,
384        })
385    }
386
387    /// Creates instance of [`Connector`] from the builder.
388    pub fn build(self) -> Result<Connector<R>, Error> {
389        let client = match self.client {
390            Some(client) => client,
391            None => TorClient::with_runtime(self.runtime).create_unbootstrapped()?,
392        };
393
394        let tls_provider = self.tls_provider.unwrap_or(get_default_tls_provider());
395
396        Ok(Connector {
397            client,
398            tls_provider,
399        })
400    }
401
402    /// Creates new [`Connector`] with an explicitly specified [`tor_rtcompat::Runtime`].
403    ///
404    /// The value `runtime` is only used if no [`arti_client::TorClient`] is configured using [`ConnectorBuilder::tor_client`].
405    pub fn with_runtime(runtime: R) -> Result<ConnectorBuilder<R>, Error> {
406        Ok(ConnectorBuilder {
407            client: None,
408            runtime,
409            tls_provider: None,
410        })
411    }
412
413    /// Configure a custom Tor client to be used with [`Connector`].
414    ///
415    /// Will also cause `client`'s `Runtime` to be used (obtained via [`TorClient::runtime()`]).
416    ///
417    /// If the client isn't `TorClient<PreferredRuntime>`, use [`ConnectorBuilder::with_runtime()`]
418    /// to create a suitable `ConnectorBuilder`.
419    pub fn tor_client(mut self, client: TorClient<R>) -> ConnectorBuilder<R> {
420        self.runtime = client.runtime().clone();
421        self.client = Some(client);
422        self
423    }
424
425    /// Configure the TLS provider to be used with [`Connector`].
426    pub fn tls_provider(mut self, tls_provider: UreqTlsProvider) -> Self {
427        self.tls_provider = Some(tls_provider);
428        self
429    }
430}
431
432// Implementation of trait [`ureq::unversioned::resolver::Resolver`] for [`Resolver`].
433//
434// `Resolver` can be used in [`ureq::Agent::with_parts`] to resolve the host to an IP address.
435//
436// Uses [`arti_client::TorClient::resolve`].
437//
438// We highly advice only using `Resolver` instead of e.g `ureq`'s [`ureq::unversioned::resolver::DefaultResolver`] to avoid DNS leaks.
439impl<R: Runtime + ToplevelBlockOn> UreqResolver for Resolver<R> {
440    /// Method to resolve the host to an IP address using `arti_client::TorClient::resolve`.
441    fn resolve(
442        &self,
443        uri: &Uri,
444        _config: &ureq::config::Config,
445        _timeout: NextTimeout,
446    ) -> Result<ResolvedSocketAddrs, ureq::Error> {
447        // We just retrieve the IP addresses using `arti_client::TorClient::resolve` and output
448        // it in a format that ureq can use.
449        let (host, port) = uri_to_host_port(uri)?;
450        let ips = self
451            .client
452            .runtime()
453            .block_on(async { self.client.resolve(&host).await })
454            .map_err(Error::from)?;
455
456        let mut array_vec: ArrayVec<core::net::SocketAddr, 16> = ArrayVec::from_fn(|_| {
457            core::net::SocketAddr::new(core::net::IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED), 0)
458        });
459
460        for ip in ips {
461            let socket_addr = core::net::SocketAddr::new(ip, port);
462            array_vec.push(socket_addr);
463        }
464
465        Ok(array_vec)
466    }
467}
468
469impl<R: Runtime + ToplevelBlockOn> Connector<R> {
470    /// Creates new instance with the provided [`arti_client::TorClient`].
471    pub fn with_tor_client(client: TorClient<R>) -> Connector<R> {
472        Connector {
473            client,
474            tls_provider: get_default_tls_provider(),
475        }
476    }
477}
478
479impl<R: Runtime + ToplevelBlockOn> UreqConnector<()> for Connector<R> {
480    type Out = Box<dyn Transport>;
481
482    /// Makes a connection using the Tor client.
483    ///
484    /// Returns a `HttpTransport` which implements trait [`ureq::unversioned::transport::Transport`].
485    fn connect(
486        &self,
487        details: &ureq::unversioned::transport::ConnectionDetails,
488        _chained: Option<()>,
489    ) -> Result<Option<Self::Out>, ureq::Error> {
490        // Retrieve host and port from the ConnectionDetails.
491        let (host, port) = uri_to_host_port(details.uri)?;
492
493        // Convert to an address we can use to connect over the Tor network.
494        let addr = (host.as_str(), port)
495            .into_tor_addr()
496            .map_err(|e| Error::Arti(e.into()))?;
497
498        // Retrieve stream from Tor connection.
499        let stream = self
500            .client
501            .runtime()
502            .block_on(async { self.client.connect(addr).await })
503            .map_err(Error::from)?;
504
505        // Return a HttpTransport with a reader and writer to the stream.
506        let (r, w) = stream.split();
507        Ok(Some(Box::new(HttpTransport {
508            r: Arc::new(Mutex::new(r)),
509            w: Arc::new(Mutex::new(w)),
510            buffer: LazyBuffers::new(2048, 2048),
511            rt: self.client.runtime().clone(),
512        })))
513    }
514}
515
516impl Connector<tor_rtcompat::PreferredRuntime> {
517    /// Returns new `Connector` with default values.
518    ///
519    /// To configure a non-default `Connector`,
520    /// use [`ConnectorBuilder`].
521    ///
522    /// Warning: This method creates a default [`arti_client::TorClient`]. Using multiple concurrent
523    /// instances of `TorClient` is not recommended. Most programs should create a single `TorClient` centrally.
524    pub fn new() -> Result<Self, Error> {
525        Self::builder()?.build()
526    }
527}
528
529impl<R: Runtime + ToplevelBlockOn> Connector<R> {
530    /// Returns instance of [`Resolver`] implementing trait [`ureq::unversioned::resolver::Resolver`].
531    pub fn resolver(&self) -> Resolver<R> {
532        Resolver {
533            client: self.client.clone(),
534        }
535    }
536
537    /// Returns instance of [`ureq::Agent`].
538    ///
539    /// Equivalent to using [`ureq::Agent::with_parts`] with the default [`ureq::config::Config`]
540    /// and this `Connector` and the resolver obtained from [`Connector::resolver()`].
541    ///
542    /// # Example
543    ///
544    /// ```rust,no_run
545    /// let ureq_agent = arti_ureq::Connector::new()
546    ///     .expect("Failed to create Connector")
547    ///     .agent();
548    ///
549    /// // Use the agent to make a request.
550    /// ureq_agent
551    ///     .get("https://check.torproject.org/api/ip")
552    ///     .call()
553    ///     .expect("Failed to make request.");
554    /// ```
555    pub fn agent(self) -> ureq::Agent {
556        let resolver = self.resolver();
557
558        let ureq_config = ureq::config::Config::builder()
559            .tls_config(
560                ureq::tls::TlsConfig::builder()
561                    .provider(self.tls_provider)
562                    .build(),
563            )
564            .build();
565
566        ureq::Agent::with_parts(ureq_config, self.connector_chain(), resolver)
567    }
568
569    /// Returns instance of [`ureq::Agent`] using the provided [`ureq::config::Config`].
570    ///
571    /// Equivalent to [`Connector::agent`] but allows the user to provide a custom [`ureq::config::Config`].
572    pub fn agent_with_ureq_config(
573        self,
574        config: ureq::config::Config,
575    ) -> Result<ureq::Agent, Error> {
576        let resolver = self.resolver();
577
578        if self.tls_provider != config.tls_config().provider() {
579            return Err(Error::TlsConfigMismatch);
580        }
581
582        Ok(ureq::Agent::with_parts(
583            config,
584            self.connector_chain(),
585            resolver,
586        ))
587    }
588
589    /// Returns connector chain depending on features flag.
590    fn connector_chain(self) -> impl UreqConnector {
591        let chain = self;
592
593        #[cfg(feature = "rustls")]
594        let chain = chain.chain(RustlsConnector::default());
595
596        #[cfg(feature = "native-tls")]
597        let chain = chain.chain(NativeTlsConnector::default());
598
599        chain
600    }
601}
602
603/// Returns the default [`ureq::tls::TlsProvider`] based on the features flag.
604pub fn get_default_tls_provider() -> UreqTlsProvider {
605    if cfg!(feature = "native-tls") {
606        UreqTlsProvider::NativeTls
607    } else {
608        UreqTlsProvider::Rustls
609    }
610}
611
612/// Implementation to make [`ConnectorBuilder`] accessible from [`Connector`].
613///
614/// # Example
615///
616/// ```rust,no_run
617/// let rt = tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.");
618/// let tls_provider = arti_ureq::get_default_tls_provider();
619///    
620/// let client = arti_client::TorClient::with_runtime(rt.clone())
621///     .create_unbootstrapped()
622///     .expect("Error creating Tor Client.");
623///
624/// let builder = arti_ureq::ConnectorBuilder::<tor_rtcompat::PreferredRuntime>::new()
625///     .expect("Failed to create ConnectorBuilder.")
626///     .tor_client(client)
627///     .tls_provider(tls_provider);
628///
629/// let arti_connector = builder.build();
630/// ```
631impl<R: Runtime> Connector<R> {
632    /// Returns new [`ConnectorBuilder`] with default values.
633    pub fn builder() -> Result<ConnectorBuilder<tor_rtcompat::PreferredRuntime>, Error> {
634        ConnectorBuilder::<R>::new()
635    }
636}
637
638/// Parse the URI.
639///
640/// Obtain the host and port.
641fn uri_to_host_port(uri: &Uri) -> Result<(String, u16), Error> {
642    let host = uri
643        .host()
644        .ok_or_else(|| Error::MissingHostname { uri: uri.clone() })?;
645
646    let port = match uri.scheme() {
647        Some(scheme) if scheme == &Scheme::HTTPS => Ok(443),
648        Some(scheme) if scheme == &Scheme::HTTP => Ok(80),
649        Some(_) => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
650        None => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
651    }?;
652
653    Ok((host.to_owned(), port))
654}
655
656#[cfg(test)]
657mod arti_ureq_test {
658    // @@ begin test lint list maintained by maint/add_warning @@
659    #![allow(clippy::bool_assert_comparison)]
660    #![allow(clippy::clone_on_copy)]
661    #![allow(clippy::dbg_macro)]
662    #![allow(clippy::mixed_attributes_style)]
663    #![allow(clippy::print_stderr)]
664    #![allow(clippy::print_stdout)]
665    #![allow(clippy::single_char_pattern)]
666    #![allow(clippy::unwrap_used)]
667    #![allow(clippy::unchecked_duration_subtraction)]
668    #![allow(clippy::useless_vec)]
669    #![allow(clippy::needless_pass_by_value)]
670    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
671
672    use super::*;
673    use arti_client::config::TorClientConfigBuilder;
674    use std::str::FromStr;
675    use test_temp_dir::test_temp_dir;
676
677    const ARTI_TEST_LIVE_NETWORK: &str = "ARTI_TEST_LIVE_NETWORK";
678    const ARTI_TESTING_ON_LOCAL: &str = "ARTI_TESTING_ON_LOCAL";
679
680    // Helper function to check if two types are equal. The types in this library are to
681    // complex to use the `==` operator or (Partial)Eq. So we compare the types of the individual properties instead.
682    fn assert_equal_types<T>(_: &T, _: &T) {}
683
684    // Helper function to check if the environment variable ARTI_TEST_LIVE_NETWORK is set to 1.
685    // We only want to run tests using the live network when the user explicitly wants to.
686    fn test_live_network() -> bool {
687        let run_test = std::env::var(ARTI_TEST_LIVE_NETWORK).is_ok_and(|v| v == "1");
688        if !run_test {
689            println!("Skipping test, set {}=1 to run.", ARTI_TEST_LIVE_NETWORK);
690        }
691
692        run_test
693    }
694
695    // Helper function to check if the environment variable ARTI_TESTING_ON_LOCAL is set to 1.
696    // Some tests, especially those that create default `Connector` instances, or test the `ConnectorBuilder`,
697    // are not reliable when run on CI.  We don't know why that is.  It's probably a bug.  TODO fix the tests!
698    fn testing_on_local() -> bool {
699        let run_test = std::env::var(ARTI_TESTING_ON_LOCAL).is_ok_and(|v| v == "1");
700        if !run_test {
701            println!("Skipping test, set {}=1 to run.", ARTI_TESTING_ON_LOCAL);
702        }
703
704        run_test
705    }
706
707    // Helper method to allow tests to be ran in a closure.
708    fn test_with_tor_client<R: Runtime>(rt: R, f: impl FnOnce(TorClient<R>)) {
709        let temp_dir = test_temp_dir!();
710        temp_dir.used_by(move |temp_dir| {
711            let arti_config = TorClientConfigBuilder::from_directories(
712                temp_dir.join("state"),
713                temp_dir.join("cache"),
714            )
715            .build()
716            .expect("Failed to build TorClientConfig");
717
718            let tor_client = arti_client::TorClient::with_runtime(rt)
719                .config(arti_config)
720                .create_unbootstrapped()
721                .expect("Error creating Tor Client.");
722
723            f(tor_client);
724        });
725    }
726
727    // Helper function to make a request to check.torproject.org/api/ip and check if
728    // it was done over Tor.
729    fn request_is_tor(agent: ureq::Agent, https: bool) -> bool {
730        let mut request = agent
731            .get(format!(
732                "http{}://check.torproject.org/api/ip",
733                if https { "s" } else { "" }
734            ))
735            .call()
736            .expect("Failed to make request.");
737        let response = request
738            .body_mut()
739            .read_to_string()
740            .expect("Failed to read body.");
741        let json_response: serde_json::Value =
742            serde_json::from_str(&response).expect("Failed to parse JSON.");
743        let is_tor = json_response
744            .get("IsTor")
745            .expect("Failed to retrieve IsTor property from response")
746            .as_bool()
747            .expect("Failed to convert IsTor to bool");
748
749        is_tor
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}