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
#![allow(clippy::cognitive_complexity)] // See arti#2556
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
#![deny(clippy::string_slice)] // See arti#2571
48
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
49
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
50

            
51
use std::sync::{Arc, Mutex};
52

            
53
use arti_client::{IntoTorAddr, TorClient};
54
use ureq::{
55
    http::{Uri, uri::Scheme},
56
    tls::TlsProvider as UreqTlsProvider,
57
    unversioned::{
58
        resolver::{ArrayVec, ResolvedSocketAddrs, Resolver as UreqResolver},
59
        transport::{Buffers, Connector as UreqConnector, LazyBuffers, NextTimeout, Transport},
60
    },
61
};
62

            
63
use educe::Educe;
64
use thiserror::Error;
65
use tor_proto::client::stream::{DataReader, DataWriter};
66
use tor_rtcompat::{Runtime, ToplevelBlockOn};
67

            
68
#[cfg(feature = "rustls")]
69
use ureq::unversioned::transport::RustlsConnector;
70

            
71
#[cfg(feature = "native-tls")]
72
use ureq::unversioned::transport::NativeTlsConnector;
73

            
74
use futures::io::{AsyncReadExt, AsyncWriteExt};
75

            
76
/// High-level functionality for accessing the Tor network as a client.
77
pub use arti_client;
78

            
79
/// Compatibility between different async runtimes for Arti.
80
pub use tor_rtcompat;
81

            
82
/// Underlying HTTP/S client library.
83
pub use ureq;
84

            
85
/// **Default usage**: Returns an instance of [`ureq::Agent`] using the default [`Connector`].
86
///
87
/// Equivalent to `Connector::new()?.agent()`.
88
///
89
/// # Example
90
///
91
/// ```rust,no_run
92
/// arti_ureq::default_agent()
93
///     .expect("Failed to create default agent.")
94
///     .get("http://check.torproject.org/api/ip")
95
///     .call()
96
///     .expect("Failed to make request.");
97
/// ```
98
///
99
/// Warning: This method creates a default [`arti_client::TorClient`]. Using multiple concurrent
100
/// instances of `TorClient` is not recommended. Most programs should create a single `TorClient` centrally.
101
pub fn default_agent() -> Result<ureq::Agent, Error> {
102
    Ok(Connector::new()?.agent())
103
}
104

            
105
/// **Main entrypoint**: Object for making HTTP/S requests through Tor.
106
///
107
/// This type embodies an [`arti_client::TorClient`] and implements [`ureq::unversioned::transport::Connector`],
108
/// allowing HTTP/HTTPS requests to be made with `ureq` over Tor.
109
///
110
/// Also bridges between async I/O (in Arti and Tokio) and sync I/O (in `ureq`).
111
///
112
/// ## A `Connector` object can be constructed in different ways.
113
///
114
/// ### 1. Use [`Connector::new`] to create a `Connector` with a default `TorClient`.
115
/// ```rust,no_run
116
/// let connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
117
/// ```
118
///
119
/// ### 2. Use [`Connector::with_tor_client`] to create a `Connector` with a specific `TorClient`.
120
/// ```rust,no_run
121
/// let tor_client = arti_client::TorClient::with_runtime(
122
///     tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.")
123
/// )
124
/// .create_unbootstrapped()
125
/// .expect("Error creating Tor Client.");
126
///
127
/// let connector = arti_ureq::Connector::with_tor_client(tor_client);
128
/// ```
129
///
130
/// ### 3. Use [`Connector::builder`] to create a `ConnectorBuilder` and configure a `Connector` with it.
131
/// ```rust,no_run
132
/// let connector = arti_ureq::Connector::<tor_rtcompat::PreferredRuntime>::builder()
133
///    .expect("Failed to create ConnectorBuilder.")
134
///    .build()
135
///    .expect("Failed to create Connector.");
136
/// ```
137
///
138
///
139
/// ## Usage of `Connector`.
140
///
141
/// A `Connector` can be used to retrieve an [`ureq::Agent`] with [`Connector::agent`] or pass the `Connector`
142
/// to [`ureq::Agent::with_parts`] along with a custom [`ureq::config::Config`] and a resolver
143
/// obtained from [`Connector::resolver`] to retrieve a more configurable [`ureq::Agent`].
144
///
145
/// ### Retrieve an `ureq::Agent`.
146
/// ```rust,no_run
147
/// let connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
148
/// let ureq_agent = connector.agent();
149
/// ```
150
///
151
/// ### Pass as argument to `ureq::Agent::with_parts`.
152
///
153
/// We highly advice only using `Resolver` instead of e.g `ureq`'s [`ureq::unversioned::resolver::DefaultResolver`] to avoid DNS leaks.
154
///
155
/// ```rust,no_run
156
/// let connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
157
/// let resolver = connector.resolver();
158
///
159
/// let ureq_agent = ureq::Agent::with_parts(
160
///    ureq::config::Config::default(),
161
///    connector,
162
///    resolver,
163
/// );
164
/// ```
165
#[derive(Educe)]
166
#[educe(Debug)]
167
pub struct Connector<R: Runtime> {
168
    /// [`arti_client::TorClient`] used to make requests.
169
    #[educe(Debug(ignore))]
170
    client: Arc<TorClient<R>>,
171

            
172
    /// Selected [`ureq::tls::TlsProvider`]. Possible options are `Rustls` or `NativeTls`. The default is `Rustls`.
173
    tls_provider: UreqTlsProvider,
174
}
175

            
176
/// Object for constructing a [`Connector`].
177
///
178
/// Returned by [`Connector::builder`].
179
///
180
/// # Example
181
///
182
/// ```rust,no_run
183
/// // `Connector` using `NativeTls` as Tls provider.
184
/// let arti_connector = arti_ureq::Connector::<tor_rtcompat::PreferredRuntime>::builder()
185
///    .expect("Failed to create ConnectorBuilder.")
186
///     .tls_provider(ureq::tls::TlsProvider::NativeTls)
187
///     .build()
188
///     .expect("Failed to create Connector.");
189
///
190
/// // Retrieve `ureq::Agent` from the `Connector`.
191
/// let ureq_agent = arti_connector.agent();
192
/// ```
193
pub struct ConnectorBuilder<R: Runtime> {
194
    /// Configured [`arti_client::TorClient`] to be used with [`Connector`].
195
    client: Option<Arc<TorClient<R>>>,
196

            
197
    /// Runtime
198
    ///
199
    /// If `client` is `None`, is used to create one.
200
    /// If `client` is `Some`. we discard this in `.build()` in favour of `.client.runtime()`.
201
    //
202
    // (We could replace `client` and `runtime` with `Either<TorClient<R>, R>` or some such,
203
    // but that would probably be more confusing.)
204
    runtime: R,
205

            
206
    /// Custom selected TlsProvider. Default is `Rustls`. Possible options are `Rustls` or `NativeTls`.
207
    tls_provider: Option<UreqTlsProvider>,
208
}
209

            
210
/// Custom [`ureq::unversioned::transport::Transport`] enabling `ureq` to use
211
/// [`arti_client::TorClient`] for making requests over Tor.
212
#[derive(Educe)]
213
#[educe(Debug)]
214
struct HttpTransport<R: Runtime> {
215
    /// Reader handle to Arti's read stream.
216
    // TODO #1859
217
    r: Arc<Mutex<DataReader>>,
218

            
219
    /// Writer handle to Arti's write stream.
220
    w: Arc<Mutex<DataWriter>>, // TODO #1859
221

            
222
    /// Buffer to store data.
223
    #[educe(Debug(ignore))]
224
    buffer: LazyBuffers,
225

            
226
    /// Runtime used to bridge between sync (`ureq`) and async I/O (`arti`).
227
    rt: R,
228
}
229

            
230
/// Resolver implementing trait [`ureq::unversioned::resolver::Resolver`].
231
///
232
/// Resolves the host to an IP address using [`arti_client::TorClient::resolve`] avoiding DNS leaks.
233
///
234
/// An instance of [`Resolver`] can easily be retrieved using [`Connector::resolver()`].
235
///
236
/// This is needed when using `ureq::Agent::with_parts`,
237
/// to avoid leaking DNS queries to the public local network.
238
/// Usually, use [`Connector::agent`] instead,
239
/// in which case you don't need to deal explicitly with a `Resolver`.
240
///
241
/// # Example
242
///
243
/// ```rust,no_run
244
/// // Retrieve the resolver directly from your `Connector`.
245
/// let arti_connector = arti_ureq::Connector::new().expect("Failed to create Connector.");
246
/// let arti_resolver = arti_connector.resolver();
247
/// let ureq_agent = ureq::Agent::with_parts(
248
///     ureq::config::Config::default(),
249
///     arti_connector,
250
///     arti_resolver,
251
/// );
252
/// ```
253
#[derive(Educe)]
254
#[educe(Debug)]
255
pub struct Resolver<R: Runtime> {
256
    /// [`arti_client::TorClient`] which contains the method [`arti_client::TorClient::resolve`].
257
    ///
258
    /// Use [`Connector::resolver`] or pass the client from your `Connector` to create an instance of `Resolver`.
259
    #[educe(Debug(ignore))]
260
    client: Arc<TorClient<R>>,
261
}
262

            
263
/// Error making or using http connection.
264
#[derive(Error, Debug)]
265
#[non_exhaustive]
266
pub enum Error {
267
    /// Unsupported URI scheme.
268
    #[error("unsupported URI scheme in {uri:?}")]
269
    UnsupportedUriScheme {
270
        /// URI.
271
        uri: Uri,
272
    },
273

            
274
    /// Missing hostname.
275
    #[error("Missing hostname in {uri:?}")]
276
    MissingHostname {
277
        /// URI.
278
        uri: Uri,
279
    },
280

            
281
    /// Tor connection failed.
282
    #[error("Tor connection failed")]
283
    Arti(#[from] arti_client::Error),
284

            
285
    /// General I/O error.
286
    #[error("General I/O error")]
287
    Io(#[from] std::io::Error),
288

            
289
    /// TLS configuration mismatch.
290
    #[error("TLS provider in config does not match the one in Connector.")]
291
    TlsConfigMismatch,
292
}
293

            
294
// Map our own error kinds to Arti's error classification.
295
impl tor_error::HasKind for Error {
296
    #[rustfmt::skip]
297
    fn kind(&self) -> tor_error::ErrorKind {
298
        use tor_error::ErrorKind as EK;
299
        match self {
300
            Error::UnsupportedUriScheme{..} => EK::NotImplemented,
301
            Error::MissingHostname{..}      => EK::BadApiUsage,
302
            Error::Arti(e)                  => e.kind(),
303
            Error::Io(..)                   => EK::Other,
304
            Error::TlsConfigMismatch        => EK::BadApiUsage,
305
        }
306
    }
307
}
308

            
309
// Convert our own error type to ureq's error type.
310
impl std::convert::From<Error> for ureq::Error {
311
    fn from(err: Error) -> Self {
312
        match err {
313
            Error::MissingHostname { uri } => {
314
                ureq::Error::BadUri(format!("Missing hostname in {uri:?}"))
315
            }
316
            Error::UnsupportedUriScheme { uri } => {
317
                ureq::Error::BadUri(format!("Unsupported URI scheme in {uri:?}"))
318
            }
319
            Error::Arti(e) => ureq::Error::Io(std::io::Error::other(e)), // TODO #1858
320
            Error::Io(e) => ureq::Error::Io(e),
321
            Error::TlsConfigMismatch => {
322
                ureq::Error::Tls("TLS provider in config does not match the one in Connector.")
323
            }
324
        }
325
    }
326
}
327

            
328
// Implementation of trait [`ureq::unversioned::transport::Transport`] for [`HttpTransport`].
329
//
330
// Due to this implementation [`Connector`] can have a valid transport to be used with `ureq`.
331
//
332
// In this implementation we map the `ureq` buffer to the `arti` stream. And map the
333
// methods to receive and transmit data between `ureq` and `arti`.
334
//
335
// Here we also bridge between the sync context `ureq` is usually called from and Arti's async I/O
336
// by blocking the provided runtime. Preferably a runtime only used for `arti` should be provided.
337
impl<R: Runtime + ToplevelBlockOn> Transport for HttpTransport<R> {
338
    // Obtain buffers used by ureq.
339
    fn buffers(&mut self) -> &mut dyn Buffers {
340
        &mut self.buffer
341
    }
342

            
343
    // Write received data from ureq request to arti stream.
344
    fn transmit_output(&mut self, amount: usize, _timeout: NextTimeout) -> Result<(), ureq::Error> {
345
        let mut writer = self.w.lock().expect("lock poisoned");
346

            
347
        let buffer = self.buffer.output();
348
        let data_to_write = &buffer[..amount];
349

            
350
        self.rt.block_on(async {
351
            writer.write_all(data_to_write).await?;
352
            writer.flush().await?;
353
            Ok(())
354
        })
355
    }
356

            
357
    // Read data from arti stream to ureq buffer.
358
    fn await_input(&mut self, _timeout: NextTimeout) -> Result<bool, ureq::Error> {
359
        let mut reader = self.r.lock().expect("lock poisoned");
360

            
361
        let buffers = self.buffer.input_append_buf();
362
        let size = self.rt.block_on(reader.read(buffers))?;
363
        self.buffer.input_appended(size);
364

            
365
        Ok(size > 0)
366
    }
367

            
368
    // Check if the connection is open.
369
    fn is_open(&mut self) -> bool {
370
        // We use `TorClient::connect` without `StreamPrefs::optimistic`,
371
        // so `.is_connected()` tells us whether the stream has *ceased to be* open;
372
        // i.e., we don't risk returning `false` because the stream isn't open *yet*.
373
        self.r.lock().is_ok_and(|guard| {
374
            guard
375
                .client_stream_ctrl()
376
                .is_some_and(|ctrl| ctrl.is_connected())
377
        })
378
    }
379
}
380

            
381
impl ConnectorBuilder<tor_rtcompat::PreferredRuntime> {
382
    /// Returns instance of [`ConnectorBuilder`] with default values.
383
    pub fn new() -> Result<Self, Error> {
384
        Ok(ConnectorBuilder {
385
            client: None,
386
            runtime: tor_rtcompat::PreferredRuntime::create()?,
387
            tls_provider: None,
388
        })
389
    }
390
}
391

            
392
impl<R: Runtime> ConnectorBuilder<R> {
393
    /// Creates instance of [`Connector`] from the builder.
394
2
    pub fn build(self) -> Result<Connector<R>, Error> {
395
2
        let client = match self.client {
396
2
            Some(client) => client,
397
            None => TorClient::with_runtime(self.runtime).create_unbootstrapped()?,
398
        };
399

            
400
2
        let tls_provider = self.tls_provider.unwrap_or(get_default_tls_provider());
401

            
402
2
        Ok(Connector {
403
2
            client,
404
2
            tls_provider,
405
2
        })
406
2
    }
407

            
408
    /// Creates new [`Connector`] with an explicitly specified [`tor_rtcompat::Runtime`].
409
    ///
410
    /// The value `runtime` is only used if no [`arti_client::TorClient`] is configured using [`ConnectorBuilder::tor_client`].
411
2
    pub fn with_runtime(runtime: R) -> Result<ConnectorBuilder<R>, Error> {
412
2
        Ok(ConnectorBuilder {
413
2
            client: None,
414
2
            runtime,
415
2
            tls_provider: None,
416
2
        })
417
2
    }
418

            
419
    /// Configure a custom Tor client to be used with [`Connector`].
420
    ///
421
    /// Will also cause `client`'s `Runtime` to be used (obtained via [`TorClient::runtime()`]).
422
    ///
423
    /// If the client isn't `TorClient<PreferredRuntime>`, use [`ConnectorBuilder::with_runtime()`]
424
    /// to create a suitable `ConnectorBuilder`.
425
2
    pub fn tor_client(mut self, client: Arc<TorClient<R>>) -> ConnectorBuilder<R> {
426
2
        self.runtime = client.runtime().clone();
427
2
        self.client = Some(client);
428
2
        self
429
2
    }
430

            
431
    /// Configure the TLS provider to be used with [`Connector`].
432
    pub fn tls_provider(mut self, tls_provider: UreqTlsProvider) -> Self {
433
        self.tls_provider = Some(tls_provider);
434
        self
435
    }
436
}
437

            
438
// Implementation of trait [`ureq::unversioned::resolver::Resolver`] for [`Resolver`].
439
//
440
// `Resolver` can be used in [`ureq::Agent::with_parts`] to resolve the host to an IP address.
441
//
442
// Uses [`arti_client::TorClient::resolve`].
443
//
444
// We highly advice only using `Resolver` instead of e.g `ureq`'s [`ureq::unversioned::resolver::DefaultResolver`] to avoid DNS leaks.
445
impl<R: Runtime + ToplevelBlockOn> UreqResolver for Resolver<R> {
446
    /// Method to resolve the host to an IP address using `arti_client::TorClient::resolve`.
447
    fn resolve(
448
        &self,
449
        uri: &Uri,
450
        _config: &ureq::config::Config,
451
        _timeout: NextTimeout,
452
    ) -> Result<ResolvedSocketAddrs, ureq::Error> {
453
        // We just retrieve the IP addresses using `arti_client::TorClient::resolve` and output
454
        // it in a format that ureq can use.
455
        let (host, port) = uri_to_host_port(uri)?;
456
        let ips = self
457
            .client
458
            .runtime()
459
            .block_on(async { self.client.resolve(&host).await })
460
            .map_err(Error::from)?;
461

            
462
        /// Max number of socket addresses to keep from the resolver.
463
        ///
464
        /// Note: the ureq Resolver::resolve() API returns `ResolvedSocketAddrs`,
465
        /// which is a type-alias for an ArrayVec of socket addresses of size MAX_ADDRS.
466
        /// However, because ureq does not actually expose MAX_ADDRS publicly,
467
        /// we have to define our own constant here, **and** cap our responses to MAX_ADDRS.
468
        /// (IMO, this is a deficiency of the ureq API that needs to be patched upstream, either by
469
        /// exposing MAX_ADDRS, or by adding a more convenient API for building ResolvedSocketAddrs
470
        /// out of an arbitrarily large collection of, or iterator over, SocketAddrs)
471
        const MAX_ADDRS: usize = 16;
472

            
473
        let mut array_vec: ArrayVec<core::net::SocketAddr, MAX_ADDRS> = ArrayVec::from_fn(|_| {
474
            core::net::SocketAddr::new(core::net::IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED), 0)
475
        });
476

            
477
        // The ureq resolver API doesn't allow returning more than MAX_ADDRS,
478
        // so we just return the first MAX_ADDRS here
479
        // (if we decide to continue maintaining arti-ureq,
480
        // we may want to do something more clever here,
481
        // like shuffling the addresses, or making sure we always return
482
        // a good mix of address families, etc.)
483
        for ip in ips.into_iter().take(MAX_ADDRS) {
484
            let socket_addr = core::net::SocketAddr::new(ip, port);
485

            
486
            array_vec.push(socket_addr);
487
        }
488

            
489
        Ok(array_vec)
490
    }
491
}
492

            
493
impl<R: Runtime + ToplevelBlockOn> Connector<R> {
494
    /// Creates new instance with the provided [`arti_client::TorClient`].
495
    pub fn with_tor_client(client: Arc<TorClient<R>>) -> Connector<R> {
496
        Connector {
497
            client,
498
            tls_provider: get_default_tls_provider(),
499
        }
500
    }
501
}
502

            
503
impl<R: Runtime + ToplevelBlockOn> UreqConnector<()> for Connector<R> {
504
    type Out = Box<dyn Transport>;
505

            
506
    /// Makes a connection using the Tor client.
507
    ///
508
    /// Returns a `HttpTransport` which implements trait [`ureq::unversioned::transport::Transport`].
509
    fn connect(
510
        &self,
511
        details: &ureq::unversioned::transport::ConnectionDetails,
512
        _chained: Option<()>,
513
    ) -> Result<Option<Self::Out>, ureq::Error> {
514
        // Retrieve host and port from the ConnectionDetails.
515
        let (host, port) = uri_to_host_port(details.uri)?;
516

            
517
        // Convert to an address we can use to connect over the Tor network.
518
        let addr = (host.as_str(), port)
519
            .into_tor_addr()
520
            .map_err(|e| Error::Arti(e.into()))?;
521

            
522
        // Retrieve stream from Tor connection.
523
        let stream = self
524
            .client
525
            .runtime()
526
            .block_on(async { self.client.connect(addr).await })
527
            .map_err(Error::from)?;
528

            
529
        // Return a HttpTransport with a reader and writer to the stream.
530
        let (r, w) = stream.split();
531
        Ok(Some(Box::new(HttpTransport {
532
            r: Arc::new(Mutex::new(r)),
533
            w: Arc::new(Mutex::new(w)),
534
            buffer: LazyBuffers::new(2048, 2048),
535
            rt: self.client.runtime().clone(),
536
        })))
537
    }
538
}
539

            
540
impl Connector<tor_rtcompat::PreferredRuntime> {
541
    /// Returns new `Connector` with default values.
542
    ///
543
    /// To configure a non-default `Connector`,
544
    /// use [`ConnectorBuilder`].
545
    ///
546
    /// Warning: This method creates a default [`arti_client::TorClient`]. Using multiple concurrent
547
    /// instances of `TorClient` is not recommended. Most programs should create a single `TorClient` centrally.
548
    pub fn new() -> Result<Self, Error> {
549
        Self::builder()?.build()
550
    }
551
}
552

            
553
impl<R: Runtime + ToplevelBlockOn> Connector<R> {
554
    /// Returns instance of [`Resolver`] implementing trait [`ureq::unversioned::resolver::Resolver`].
555
    pub fn resolver(&self) -> Resolver<R> {
556
        Resolver {
557
            client: self.client.clone(),
558
        }
559
    }
560

            
561
    /// Returns instance of [`ureq::Agent`].
562
    ///
563
    /// Equivalent to using [`ureq::Agent::with_parts`] with the default [`ureq::config::Config`]
564
    /// and this `Connector` and the resolver obtained from [`Connector::resolver()`].
565
    ///
566
    /// # Example
567
    ///
568
    /// ```rust,no_run
569
    /// let ureq_agent = arti_ureq::Connector::new()
570
    ///     .expect("Failed to create Connector")
571
    ///     .agent();
572
    ///
573
    /// // Use the agent to make a request.
574
    /// ureq_agent
575
    ///     .get("https://check.torproject.org/api/ip")
576
    ///     .call()
577
    ///     .expect("Failed to make request.");
578
    /// ```
579
    pub fn agent(self) -> ureq::Agent {
580
        let resolver = self.resolver();
581

            
582
        let ureq_config = ureq::config::Config::builder()
583
            .tls_config(
584
                ureq::tls::TlsConfig::builder()
585
                    .provider(self.tls_provider)
586
                    .build(),
587
            )
588
            .build();
589

            
590
        ureq::Agent::with_parts(ureq_config, self.connector_chain(), resolver)
591
    }
592

            
593
    /// Returns instance of [`ureq::Agent`] using the provided [`ureq::config::Config`].
594
    ///
595
    /// Equivalent to [`Connector::agent`] but allows the user to provide a custom [`ureq::config::Config`].
596
    pub fn agent_with_ureq_config(
597
        self,
598
        config: ureq::config::Config,
599
    ) -> Result<ureq::Agent, Error> {
600
        let resolver = self.resolver();
601

            
602
        if self.tls_provider != config.tls_config().provider() {
603
            return Err(Error::TlsConfigMismatch);
604
        }
605

            
606
        Ok(ureq::Agent::with_parts(
607
            config,
608
            self.connector_chain(),
609
            resolver,
610
        ))
611
    }
612

            
613
    /// Returns connector chain depending on features flag.
614
    fn connector_chain(self) -> impl UreqConnector {
615
        let chain = self;
616

            
617
        #[cfg(feature = "rustls")]
618
        let chain = chain.chain(RustlsConnector::default());
619

            
620
        #[cfg(feature = "native-tls")]
621
        let chain = chain.chain(NativeTlsConnector::default());
622

            
623
        chain
624
    }
625
}
626

            
627
/// Returns the default [`ureq::tls::TlsProvider`] based on the features flag.
628
4
pub fn get_default_tls_provider() -> UreqTlsProvider {
629
4
    if cfg!(feature = "native-tls") {
630
4
        UreqTlsProvider::NativeTls
631
    } else {
632
        UreqTlsProvider::Rustls
633
    }
634
4
}
635

            
636
/// Implementation to make [`ConnectorBuilder`] accessible from [`Connector`].
637
///
638
/// # Example
639
///
640
/// ```rust,no_run
641
/// let rt = tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.");
642
/// let tls_provider = arti_ureq::get_default_tls_provider();
643
///
644
/// let client = arti_client::TorClient::with_runtime(rt.clone())
645
///     .create_unbootstrapped()
646
///     .expect("Error creating Tor Client.");
647
///
648
/// let builder = arti_ureq::ConnectorBuilder::<tor_rtcompat::PreferredRuntime>::new()
649
///     .expect("Failed to create ConnectorBuilder.")
650
///     .tor_client(client)
651
///     .tls_provider(tls_provider);
652
///
653
/// let arti_connector = builder.build();
654
/// ```
655
impl Connector<tor_rtcompat::PreferredRuntime> {
656
    /// Returns new [`ConnectorBuilder`] with default values.
657
    pub fn builder() -> Result<ConnectorBuilder<tor_rtcompat::PreferredRuntime>, Error> {
658
        ConnectorBuilder::new()
659
    }
660
}
661

            
662
/// Parse the URI.
663
///
664
/// Obtain the host and port.
665
6
fn uri_to_host_port(uri: &Uri) -> Result<(String, u16), Error> {
666
6
    let host = uri
667
6
        .host()
668
6
        .ok_or_else(|| Error::MissingHostname { uri: uri.clone() })?;
669

            
670
6
    let port = match uri.scheme() {
671
6
        Some(scheme) if scheme == &Scheme::HTTPS => Ok(443),
672
2
        Some(scheme) if scheme == &Scheme::HTTP => Ok(80),
673
        Some(_) => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
674
        None => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
675
    }?;
676

            
677
6
    Ok((host.to_owned(), port))
678
6
}
679

            
680
#[cfg(test)]
681
mod arti_ureq_test {
682
    // @@ begin test lint list maintained by maint/add_warning @@
683
    #![allow(clippy::bool_assert_comparison)]
684
    #![allow(clippy::clone_on_copy)]
685
    #![allow(clippy::dbg_macro)]
686
    #![allow(clippy::mixed_attributes_style)]
687
    #![allow(clippy::print_stderr)]
688
    #![allow(clippy::print_stdout)]
689
    #![allow(clippy::single_char_pattern)]
690
    #![allow(clippy::unwrap_used)]
691
    #![allow(clippy::unchecked_time_subtraction)]
692
    #![allow(clippy::useless_vec)]
693
    #![allow(clippy::needless_pass_by_value)]
694
    #![allow(clippy::string_slice)] // See arti#2571
695
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
696

            
697
    use super::*;
698
    use arti_client::config::TorClientConfigBuilder;
699
    use std::str::FromStr;
700
    use test_temp_dir::test_temp_dir;
701

            
702
    const ARTI_TEST_LIVE_NETWORK: &str = "ARTI_TEST_LIVE_NETWORK";
703
    const ARTI_TESTING_ON_LOCAL: &str = "ARTI_TESTING_ON_LOCAL";
704

            
705
    // Helper function to check if two types are equal. The types in this library are to
706
    // complex to use the `==` operator or (Partial)Eq. So we compare the types of the individual properties instead.
707
8
    fn assert_equal_types<T>(_: &T, _: &T) {}
708

            
709
    // Helper function to check if the environment variable ARTI_TEST_LIVE_NETWORK is set to 1.
710
    // We only want to run tests using the live network when the user explicitly wants to.
711
4
    fn test_live_network() -> bool {
712
4
        let run_test = std::env::var(ARTI_TEST_LIVE_NETWORK).is_ok_and(|v| v == "1");
713
4
        if !run_test {
714
4
            println!("Skipping test, set {}=1 to run.", ARTI_TEST_LIVE_NETWORK);
715
4
        }
716

            
717
4
        run_test
718
4
    }
719

            
720
    // Helper function to check if the environment variable ARTI_TESTING_ON_LOCAL is set to 1.
721
    // Some tests, especially those that create default `Connector` instances, or test the `ConnectorBuilder`,
722
    // are not reliable when run on CI.  We don't know why that is.  It's probably a bug.  TODO fix the tests!
723
6
    fn testing_on_local() -> bool {
724
6
        let run_test = std::env::var(ARTI_TESTING_ON_LOCAL).is_ok_and(|v| v == "1");
725
6
        if !run_test {
726
6
            println!("Skipping test, set {}=1 to run.", ARTI_TESTING_ON_LOCAL);
727
6
        }
728

            
729
6
        run_test
730
6
    }
731

            
732
    // Helper method to allow tests to be ran in a closure.
733
2
    fn test_with_tor_client<R: Runtime>(rt: R, f: impl FnOnce(Arc<TorClient<R>>)) {
734
2
        let temp_dir = test_temp_dir!();
735
2
        temp_dir.used_by(move |temp_dir| {
736
2
            let arti_config = TorClientConfigBuilder::from_directories(
737
2
                temp_dir.join("state"),
738
2
                temp_dir.join("cache"),
739
2
            )
740
2
            .build()
741
2
            .expect("Failed to build TorClientConfig");
742

            
743
2
            let tor_client = arti_client::TorClient::with_runtime(rt)
744
2
                .config(arti_config)
745
2
                .create_unbootstrapped()
746
2
                .expect("Error creating Tor Client.");
747

            
748
2
            f(tor_client);
749
2
        });
750
2
    }
751

            
752
    // Helper function to make a request to check.torproject.org/api/ip and check if
753
    // it was done over Tor.
754
    fn request_is_tor(agent: ureq::Agent, https: bool) -> bool {
755
        let mut request = agent
756
            .get(format!(
757
                "http{}://check.torproject.org/api/ip",
758
                if https { "s" } else { "" }
759
            ))
760
            .call()
761
            .expect("Failed to make request.");
762
        let response = request
763
            .body_mut()
764
            .read_to_string()
765
            .expect("Failed to read body.");
766
        let json_response: serde_json::Value =
767
            serde_json::from_str(&response).expect("Failed to parse JSON.");
768
        json_response
769
            .get("IsTor")
770
            .expect("Failed to retrieve IsTor property from response")
771
            .as_bool()
772
            .expect("Failed to convert IsTor to bool")
773
    }
774

            
775
    // Quick internal test to check if our helper function `equal_types` works as expected.
776
    // Otherwise our other tests might not be reliable.
777
    #[test]
778
2
    fn test_equal_types() {
779
2
        assert_equal_types(&1, &i32::MIN);
780
2
        assert_equal_types(&1, &i64::MIN);
781
2
        assert_equal_types(&String::from("foo"), &String::with_capacity(1));
782
2
    }
783

            
784
    // `Connector::new` should return the default `Connector`.
785
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
786
    #[test]
787
    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
788
    fn articonnector_new_returns_default() {
789
        if !testing_on_local() {
790
            return;
791
        }
792

            
793
        let actual_connector = Connector::new().expect("Failed to create Connector.");
794
        let expected_connector = Connector {
795
            client: TorClient::with_runtime(
796
                tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
797
            )
798
            .create_unbootstrapped()
799
            .expect("Error creating Tor Client."),
800
            tls_provider: UreqTlsProvider::Rustls,
801
        };
802

            
803
        assert_equal_types(&expected_connector, &actual_connector);
804
        assert_equal_types(
805
            &actual_connector.client.runtime().clone(),
806
            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
807
        );
808
        assert_eq!(
809
            &actual_connector.tls_provider,
810
            &ureq::tls::TlsProvider::Rustls,
811
        );
812
    }
813

            
814
    // `Connector::with_tor_client` should return a `Connector` with specified Tor client set.
815
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
816
    #[test]
817
    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
818
    fn articonnector_with_tor_client() {
819
        if !testing_on_local() {
820
            return;
821
        }
822

            
823
        let tor_client = TorClient::with_runtime(
824
            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
825
        )
826
        .create_unbootstrapped()
827
        .expect("Error creating Tor Client.");
828

            
829
        let actual_connector = Connector::with_tor_client(tor_client);
830
        let expected_connector = Connector {
831
            client: TorClient::with_runtime(
832
                tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
833
            )
834
            .create_unbootstrapped()
835
            .expect("Error creating Tor Client."),
836
            tls_provider: UreqTlsProvider::Rustls,
837
        };
838

            
839
        assert_equal_types(&expected_connector, &actual_connector);
840
        assert_equal_types(
841
            &actual_connector.client.runtime().clone(),
842
            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
843
        );
844
        assert_eq!(
845
            &actual_connector.tls_provider,
846
            &ureq::tls::TlsProvider::Rustls,
847
        );
848
    }
849

            
850
    // The default instance returned by `Connector::builder` should equal to the default `Connector`.
851
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
852
    #[test]
853
2
    fn articonnectorbuilder_new_returns_default() {
854
2
        if !testing_on_local() {
855
2
            return;
856
        }
857

            
858
        let expected = Connector::new().expect("Failed to create Connector.");
859
        let actual = Connector::<tor_rtcompat::PreferredRuntime>::builder()
860
            .expect("Failed to create ConnectorBuilder.")
861
            .build()
862
            .expect("Failed to create Connector.");
863

            
864
        assert_equal_types(&expected, &actual);
865
        assert_equal_types(&expected.client.runtime(), &actual.client.runtime());
866
        assert_eq!(&expected.tls_provider, &actual.tls_provider);
867
2
    }
868

            
869
    // `ConnectorBuilder::with_runtime` should return a `ConnectorBuilder` with the specified runtime set.
870
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
871
    #[cfg(all(feature = "tokio", feature = "rustls"))]
872
    #[test]
873
2
    fn articonnectorbuilder_with_runtime() {
874
2
        if !testing_on_local() {
875
2
            return;
876
        }
877

            
878
        let arti_connector = ConnectorBuilder::with_runtime(
879
            tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime."),
880
        )
881
        .expect("Failed to create ConnectorBuilder.")
882
        .build()
883
        .expect("Failed to create Connector.");
884

            
885
        assert_equal_types(
886
            &arti_connector.client.runtime().clone(),
887
            &tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime."),
888
        );
889

            
890
        let arti_connector = ConnectorBuilder::with_runtime(
891
            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
892
        )
893
        .expect("Failed to create ConnectorBuilder.")
894
        .build()
895
        .expect("Failed to create Connector.");
896

            
897
        assert_equal_types(
898
            &arti_connector.client.runtime().clone(),
899
            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
900
        );
901
2
    }
902

            
903
    // `ConnectorBuilder::tor_client` should return a `Connector` with the specified `TorClient` set.
904
    #[cfg(all(feature = "tokio", feature = "rustls"))]
905
    #[test]
906
2
    fn articonnectorbuilder_set_tor_client() {
907
2
        let rt =
908
2
            tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime.");
909

            
910
3
        test_with_tor_client(rt.clone(), move |tor_client| {
911
2
            let arti_connector = ConnectorBuilder::with_runtime(rt)
912
2
                .expect("Failed to create ConnectorBuilder.")
913
2
                .tor_client(tor_client.clone().isolated_client())
914
2
                .build()
915
2
                .expect("Failed to create Connector.");
916

            
917
2
            assert_equal_types(
918
2
                &arti_connector.client.runtime().clone(),
919
2
                &tor_rtcompat::tokio::TokioRustlsRuntime::create()
920
2
                    .expect("Failed to create runtime."),
921
            );
922
2
        });
923
2
    }
924

            
925
    // Test if the method `uri_to_host_port` returns the correct parameters.
926
    #[test]
927
2
    fn test_uri_to_host_port() {
928
2
        let uri = Uri::from_str("http://torproject.org").expect("Error parsing uri.");
929
2
        let (host, port) = uri_to_host_port(&uri).expect("Error parsing uri.");
930

            
931
2
        assert_eq!(host, "torproject.org");
932
2
        assert_eq!(port, 80);
933

            
934
2
        let uri = Uri::from_str("https://torproject.org").expect("Error parsing uri.");
935
2
        let (host, port) = uri_to_host_port(&uri).expect("Error parsing uri.");
936

            
937
2
        assert_eq!(host, "torproject.org");
938
2
        assert_eq!(port, 443);
939

            
940
2
        let uri = Uri::from_str("https://www.torproject.org/test").expect("Error parsing uri.");
941
2
        let (host, port) = uri_to_host_port(&uri).expect("Error parsing uri.");
942

            
943
2
        assert_eq!(host, "www.torproject.org");
944
2
        assert_eq!(port, 443);
945
2
    }
946

            
947
    // Test if `arti-ureq` default agent uses Tor to make the request.
948
    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
949
    #[test]
950
2
    fn request_goes_over_tor() {
951
2
        if !test_live_network() {
952
2
            return;
953
        }
954

            
955
        let is_tor = request_is_tor(
956
            default_agent().expect("Failed to retrieve default agent."),
957
            true,
958
        );
959

            
960
        assert_eq!(is_tor, true);
961
2
    }
962

            
963
    // Test if `arti-ureq` default agent uses Tor to make the request.
964
    // This test also checks if the Tor API returns false when the request is made with an
965
    // `ureq` agent that is not configured to use Tor to ensure the test is reliable.
966
    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
967
    #[test]
968
    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
969
    fn request_goes_over_tor_with_unsafe_check() {
970
        if !test_live_network() {
971
            return;
972
        }
973

            
974
        let is_tor = request_is_tor(ureq::Agent::new_with_defaults(), true);
975
        assert_eq!(is_tor, false);
976

            
977
        let is_tor = request_is_tor(
978
            default_agent().expect("Failed to retrieve default agent."),
979
            true,
980
        );
981
        assert_eq!(is_tor, true);
982
    }
983

            
984
    // Test if the `ureq` client configured with `Connector` uses Tor tor make the request using bare HTTP.
985
    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
986
    #[test]
987
2
    fn request_with_bare_http() {
988
2
        if !test_live_network() {
989
2
            return;
990
        }
991

            
992
        let rt = tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.");
993

            
994
        test_with_tor_client(rt, |tor_client| {
995
            let arti_connector = Connector::with_tor_client(tor_client);
996
            let is_tor = request_is_tor(arti_connector.agent(), false);
997

            
998
            assert_eq!(is_tor, true);
999
        });
2
    }
    // Test if `get_default_tls_provider` correctly derives the TLS provider from the feature flags.
    #[test]
2
    fn test_get_default_tls_provider() {
        #[cfg(feature = "native-tls")]
2
        assert_eq!(get_default_tls_provider(), UreqTlsProvider::NativeTls);
        #[cfg(not(feature = "native-tls"))]
        assert_eq!(get_default_tls_provider(), UreqTlsProvider::Rustls);
2
    }
    // Test if configuring the `Connector` using `get_default_tls_provider` correctly sets the TLS provider
    // based on the feature flags.
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
    #[test]
2
    fn test_tor_client_with_get_default_tls_provider() {
2
        if !testing_on_local() {
2
            return;
        }
        let tor_client = TorClient::with_runtime(
            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
        )
        .create_unbootstrapped()
        .expect("Error creating Tor Client.");
        let arti_connector = Connector::<tor_rtcompat::PreferredRuntime>::builder()
            .expect("Failed to create ConnectorBuilder.")
            .tor_client(tor_client.clone().isolated_client())
            .tls_provider(get_default_tls_provider())
            .build()
            .expect("Failed to create Connector.");
        #[cfg(feature = "native-tls")]
        assert_eq!(
            &arti_connector.tls_provider,
            &ureq::tls::TlsProvider::NativeTls,
        );
        #[cfg(not(feature = "native-tls"))]
        assert_eq!(
            &arti_connector.tls_provider,
            &ureq::tls::TlsProvider::Rustls,
        );
2
    }
}