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
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
366
        Ok(size > 0)
367
    }
368

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

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

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

            
401
1
        let tls_provider = self.tls_provider.unwrap_or_else(get_default_tls_provider);
402

            
403
1
        Ok(Connector {
404
1
            client,
405
1
            tls_provider,
406
1
        })
407
1
    }
408

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

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

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

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

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

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

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

            
487
            array_vec.push(socket_addr);
488
        }
489

            
490
        Ok(array_vec)
491
    }
492
}
493

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
624
        chain
625
    }
626
}
627

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

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

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

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

            
678
3
    Ok((host.to_owned(), port))
679
3
}
680

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

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

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

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

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

            
718
2
        run_test
719
2
    }
720

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

            
730
3
        run_test
731
3
    }
732

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

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

            
749
1
            f(tor_client);
750
1
        });
751
1
    }
752

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
932
1
        assert_eq!(host, "torproject.org");
933
1
        assert_eq!(port, 80);
934

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

            
938
1
        assert_eq!(host, "torproject.org");
939
1
        assert_eq!(port, 443);
940

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

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

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

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

            
961
        assert_eq!(is_tor, true);
962
1
    }
963

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

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

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

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

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

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

            
999
            assert_eq!(is_tor, true);
        });
1
    }
    // Test if `get_default_tls_provider` correctly derives the TLS provider from the feature flags.
    #[test]
1
    fn test_get_default_tls_provider() {
        #[cfg(feature = "native-tls")]
1
        assert_eq!(get_default_tls_provider(), UreqTlsProvider::NativeTls);
        #[cfg(not(feature = "native-tls"))]
        assert_eq!(get_default_tls_provider(), UreqTlsProvider::Rustls);
1
    }
    // 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]
1
    fn test_tor_client_with_get_default_tls_provider() {
1
        if !testing_on_local() {
1
            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,
        );
1
    }
}