Skip to main content

arti_ureq/
lib.rs

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