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