1
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![warn(clippy::cognitive_complexity)]
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_duration_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![deny(clippy::mod_module_files)]
38
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
45

            
46
use std::sync::{Arc, Mutex};
47

            
48
use arti_client::{IntoTorAddr, TorClient};
49
use ureq::{
50
    http::{uri::Scheme, Uri},
51
    tls::TlsProvider as UreqTlsProvider,
52
    unversioned::{
53
        resolver::{ArrayVec, ResolvedSocketAddrs, Resolver as UreqResolver},
54
        transport::{Buffers, Connector as UreqConnector, LazyBuffers, NextTimeout, Transport},
55
    },
56
};
57

            
58
use educe::Educe;
59
use thiserror::Error;
60
use tor_proto::stream::{DataReader, DataWriter};
61
use tor_rtcompat::{Runtime, ToplevelBlockOn};
62

            
63
#[cfg(feature = "rustls")]
64
use ureq::unversioned::transport::RustlsConnector;
65

            
66
#[cfg(feature = "native-tls")]
67
use ureq::unversioned::transport::NativeTlsConnector;
68

            
69
use futures::io::{AsyncReadExt, AsyncWriteExt};
70

            
71
/// High-level functionality for accessing the Tor network as a client.
72
pub use arti_client;
73

            
74
/// Compatibility between different async runtimes for Arti.
75
pub use tor_rtcompat;
76

            
77
/// Underlying HTTP/S client library.
78
pub use ureq;
79

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

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

            
167
    /// Selected [`ureq::tls::TlsProvider`]. Possibile options are `Rustls` or `NativeTls`. The default is `Rustls`.
168
    tls_provider: UreqTlsProvider,
169
}
170

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

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

            
201
    /// Custom selected TlsProvider. Default is `Rustls`. Possible options are `Rustls` or `NativeTls`.
202
    tls_provider: Option<UreqTlsProvider>,
203
}
204

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

            
214
    /// Writer handle to Arti's write stream.
215
    w: Arc<Mutex<DataWriter>>, // TODO #1859
216

            
217
    /// Buffer to store data.
218
    #[educe(Debug(ignore))]
219
    buffer: LazyBuffers,
220

            
221
    /// Runtime used to bridge between sync (`ureq`) and async I/O (`arti`).
222
    rt: R,
223
}
224

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

            
258
/// Error making or using http connection.
259
#[derive(Error, Debug)]
260
#[non_exhaustive]
261
pub enum Error {
262
    /// Unsupported URI scheme.
263
    #[error("unsupported URI scheme in {uri:?}")]
264
    UnsupportedUriScheme {
265
        /// URI.
266
        uri: Uri,
267
    },
268

            
269
    /// Missing hostname.
270
    #[error("Missing hostname in {uri:?}")]
271
    MissingHostname {
272
        /// URI.
273
        uri: Uri,
274
    },
275

            
276
    /// Tor connection failed.
277
    #[error("Tor connection failed")]
278
    Arti(#[from] arti_client::Error),
279

            
280
    /// General I/O error.
281
    #[error("General I/O error")]
282
    Io(#[from] std::io::Error),
283

            
284
    /// TLS configuration mismatch.
285
    #[error("TLS provider in config does not match the one in Connector.")]
286
    TlsConfigMismatch,
287
}
288

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

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

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

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

            
342
        let buffer = self.buffer.output();
343
        let data_to_write = &buffer[..amount];
344

            
345
        self.rt.block_on(async {
346
            writer.write_all(data_to_write).await?;
347
            writer.flush().await?;
348
            Ok(())
349
        })
350
    }
351

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

            
356
        let buffers = self.buffer.input_append_buf();
357
        let size = self.rt.block_on(reader.read(buffers))?;
358
        self.buffer.input_appended(size);
359

            
360
        Ok(size > 0)
361
    }
362

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

            
376
impl<R: Runtime> ConnectorBuilder<R> {
377
    /// Returns instance of [`ConnectorBuilder`] with default values.
378
    pub fn new() -> Result<ConnectorBuilder<tor_rtcompat::PreferredRuntime>, Error> {
379
        Ok(ConnectorBuilder {
380
            client: None,
381
            runtime: tor_rtcompat::PreferredRuntime::create()?,
382
            tls_provider: None,
383
        })
384
    }
385

            
386
    /// Creates instance of [`Connector`] from the builder.
387
2
    pub fn build(self) -> Result<Connector<R>, Error> {
388
2
        let client = match self.client {
389
2
            Some(client) => client,
390
            None => TorClient::with_runtime(self.runtime).create_unbootstrapped()?,
391
        };
392

            
393
2
        let tls_provider = self.tls_provider.unwrap_or(get_default_tls_provider());
394
2

            
395
2
        Ok(Connector {
396
2
            client,
397
2
            tls_provider,
398
2
        })
399
2
    }
400

            
401
    /// Creates new [`Connector`] with an explicitly specified [`tor_rtcompat::Runtime`].
402
    ///
403
    /// The value `runtime` is only used if no [`arti_client::TorClient`] is configured using [`ConnectorBuilder::tor_client`].
404
2
    pub fn with_runtime(runtime: R) -> Result<ConnectorBuilder<R>, Error> {
405
2
        Ok(ConnectorBuilder {
406
2
            client: None,
407
2
            runtime,
408
2
            tls_provider: None,
409
2
        })
410
2
    }
411

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

            
424
    /// Configure the TLS provider to be used with [`Connector`].
425
    pub fn tls_provider(mut self, tls_provider: UreqTlsProvider) -> Self {
426
        self.tls_provider = Some(tls_provider);
427
        self
428
    }
429
}
430

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

            
455
        let mut array_vec: ArrayVec<core::net::SocketAddr, 16> = ArrayVec::from_fn(|_| {
456
            core::net::SocketAddr::new(core::net::IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED), 0)
457
        });
458

            
459
        for ip in ips {
460
            let socket_addr = core::net::SocketAddr::new(ip, port);
461
            array_vec.push(socket_addr);
462
        }
463

            
464
        Ok(array_vec)
465
    }
466
}
467

            
468
impl<R: Runtime + ToplevelBlockOn> Connector<R> {
469
    /// Creates new instance with the provided [`arti_client::TorClient`].
470
    pub fn with_tor_client(client: TorClient<R>) -> Connector<R> {
471
        Connector {
472
            client,
473
            tls_provider: get_default_tls_provider(),
474
        }
475
    }
476
}
477

            
478
impl<R: Runtime + ToplevelBlockOn> UreqConnector<()> for Connector<R> {
479
    type Out = Box<dyn Transport>;
480

            
481
    /// Makes a connection using the Tor client.
482
    ///
483
    /// Returns a `HttpTransport` which implements trait [`ureq::unversioned::transport::Transport`].
484
    fn connect(
485
        &self,
486
        details: &ureq::unversioned::transport::ConnectionDetails,
487
        _chained: Option<()>,
488
    ) -> Result<Option<Self::Out>, ureq::Error> {
489
        // Retrieve host and port from the ConnectionDetails.
490
        let (host, port) = uri_to_host_port(details.uri)?;
491

            
492
        // Convert to an address we can use to connect over the Tor network.
493
        let addr = (host.as_str(), port)
494
            .into_tor_addr()
495
            .map_err(|e| Error::Arti(e.into()))?;
496

            
497
        // Retrieve stream from Tor connection.
498
        let stream = self
499
            .client
500
            .runtime()
501
            .block_on(async { self.client.connect(addr).await })
502
            .map_err(Error::from)?;
503

            
504
        // Return a HttpTransport with a reader and writer to the stream.
505
        let (r, w) = stream.split();
506
        Ok(Some(Box::new(HttpTransport {
507
            r: Arc::new(Mutex::new(r)),
508
            w: Arc::new(Mutex::new(w)),
509
            buffer: LazyBuffers::new(2048, 2048),
510
            rt: self.client.runtime().clone(),
511
        })))
512
    }
513
}
514

            
515
impl Connector<tor_rtcompat::PreferredRuntime> {
516
    /// Returns new `Connector` with default values.
517
    ///
518
    /// To configure a non-default `Connector`,
519
    /// use [`ConnectorBuilder`].
520
    ///
521
    /// Warning: This method creates a default [`arti_client::TorClient`]. Using multiple concurrent
522
    /// instances of `TorClient` is not recommended. Most programs should create a single `TorClient` centrally.
523
    pub fn new() -> Result<Self, Error> {
524
        Self::builder()?.build()
525
    }
526
}
527

            
528
impl<R: Runtime + ToplevelBlockOn> Connector<R> {
529
    /// Returns instance of [`Resolver`] implementing trait [`ureq::unversioned::resolver::Resolver`].
530
    pub fn resolver(&self) -> Resolver<R> {
531
        Resolver {
532
            client: self.client.clone(),
533
        }
534
    }
535

            
536
    /// Returns instance of [`ureq::Agent`].
537
    ///
538
    /// Equivalent to using [`ureq::Agent::with_parts`] with the default [`ureq::config::Config`]
539
    /// and this `Connector` and the resolver obtained from [`Connector::resolver()`].
540
    ///
541
    /// # Example
542
    ///
543
    /// ```rust,no_run
544
    /// let ureq_agent = arti_ureq::Connector::new()
545
    ///     .expect("Failed to create Connector")
546
    ///     .agent();
547
    ///
548
    /// // Use the agent to make a request.
549
    /// ureq_agent
550
    ///     .get("https://check.torproject.org/api/ip")
551
    ///     .call()
552
    ///     .expect("Failed to make request.");
553
    /// ```
554
    pub fn agent(self) -> ureq::Agent {
555
        let resolver = self.resolver();
556

            
557
        let ureq_config = ureq::config::Config::builder()
558
            .tls_config(
559
                ureq::tls::TlsConfig::builder()
560
                    .provider(self.tls_provider)
561
                    .build(),
562
            )
563
            .build();
564

            
565
        ureq::Agent::with_parts(ureq_config, self.connector_chain(), resolver)
566
    }
567

            
568
    /// Returns instance of [`ureq::Agent`] using the provided [`ureq::config::Config`].
569
    ///
570
    /// Equivalent to [`Connector::agent`] but allows the user to provide a custom [`ureq::config::Config`].
571
    pub fn agent_with_ureq_config(
572
        self,
573
        config: ureq::config::Config,
574
    ) -> Result<ureq::Agent, Error> {
575
        let resolver = self.resolver();
576

            
577
        if self.tls_provider != config.tls_config().provider() {
578
            return Err(Error::TlsConfigMismatch);
579
        }
580

            
581
        Ok(ureq::Agent::with_parts(
582
            config,
583
            self.connector_chain(),
584
            resolver,
585
        ))
586
    }
587

            
588
    /// Returns connector chain depending on features flag.
589
    fn connector_chain(self) -> impl UreqConnector {
590
        let chain = self;
591

            
592
        #[cfg(feature = "rustls")]
593
        let chain = chain.chain(RustlsConnector::default());
594

            
595
        #[cfg(feature = "native-tls")]
596
        let chain = chain.chain(NativeTlsConnector::default());
597

            
598
        chain
599
    }
600
}
601

            
602
/// Returns the default [`ureq::tls::TlsProvider`] based on the features flag.
603
4
pub fn get_default_tls_provider() -> UreqTlsProvider {
604
4
    if cfg!(feature = "native-tls") {
605
4
        UreqTlsProvider::NativeTls
606
    } else {
607
        UreqTlsProvider::Rustls
608
    }
609
4
}
610

            
611
/// Implementation to make [`ConnectorBuilder`] accessible from [`Connector`].
612
///
613
/// # Example
614
///
615
/// ```rust,no_run
616
/// let rt = tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime.");
617
/// let tls_provider = arti_ureq::get_default_tls_provider();
618
///    
619
/// let client = arti_client::TorClient::with_runtime(rt.clone())
620
///     .create_unbootstrapped()
621
///     .expect("Error creating Tor Client.");
622
///
623
/// let builder = arti_ureq::ConnectorBuilder::<tor_rtcompat::PreferredRuntime>::new()
624
///     .expect("Failed to create ConnectorBuilder.")
625
///     .tor_client(client)
626
///     .tls_provider(tls_provider);
627
///
628
/// let arti_connector = builder.build();
629
/// ```
630
impl<R: Runtime> Connector<R> {
631
    /// Returns new [`ConnectorBuilder`] with default values.
632
    pub fn builder() -> Result<ConnectorBuilder<tor_rtcompat::PreferredRuntime>, Error> {
633
        ConnectorBuilder::<R>::new()
634
    }
635
}
636

            
637
/// Parse the URI.
638
///
639
/// Obtain the host and port.
640
6
fn uri_to_host_port(uri: &Uri) -> Result<(String, u16), Error> {
641
6
    let host = uri
642
6
        .host()
643
6
        .ok_or_else(|| Error::MissingHostname { uri: uri.clone() })?;
644

            
645
6
    let port = match uri.scheme() {
646
6
        Some(scheme) if scheme == &Scheme::HTTPS => Ok(443),
647
2
        Some(scheme) if scheme == &Scheme::HTTP => Ok(80),
648
        Some(_) => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
649
        None => Err(Error::UnsupportedUriScheme { uri: uri.clone() }),
650
    }?;
651

            
652
6
    Ok((host.to_owned(), port))
653
6
}
654

            
655
#[cfg(test)]
656
mod arti_ureq_test {
657
    // @@ begin test lint list maintained by maint/add_warning @@
658
    #![allow(clippy::bool_assert_comparison)]
659
    #![allow(clippy::clone_on_copy)]
660
    #![allow(clippy::dbg_macro)]
661
    #![allow(clippy::mixed_attributes_style)]
662
    #![allow(clippy::print_stderr)]
663
    #![allow(clippy::print_stdout)]
664
    #![allow(clippy::single_char_pattern)]
665
    #![allow(clippy::unwrap_used)]
666
    #![allow(clippy::unchecked_duration_subtraction)]
667
    #![allow(clippy::useless_vec)]
668
    #![allow(clippy::needless_pass_by_value)]
669
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
670

            
671
    use super::*;
672
    use arti_client::config::TorClientConfigBuilder;
673
    use std::str::FromStr;
674
    use test_temp_dir::test_temp_dir;
675

            
676
    const ARTI_TEST_LIVE_NETWORK: &str = "ARTI_TEST_LIVE_NETWORK";
677
    const ARTI_TESTING_ON_LOCAL: &str = "ARTI_TESTING_ON_LOCAL";
678

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

            
683
    // Helper function to check if the enviroment variable ARTI_TEST_LIVE_NETWORK is set to 1.
684
    // We only want to run tests using the live network when the user explicitly wants to.
685
4
    fn test_live_network() -> bool {
686
4
        let run_test = std::env::var(ARTI_TEST_LIVE_NETWORK).is_ok_and(|v| v == "1");
687
4
        if !run_test {
688
4
            println!("Skipping test, set {}=1 to run.", ARTI_TEST_LIVE_NETWORK);
689
4
        }
690

            
691
4
        run_test
692
4
    }
693

            
694
    // Helper function to check if the enviroment variable ARTI_TESTING_ON_LOCAL is set to 1.
695
    // Some tests, especially those that create default `Connector` instances, or test the `ConnectorBuilder`,
696
    // are not reliable when run on CI.  We don't know why that is.  It's probably a bug.  TODO fix the tests!
697
6
    fn testing_on_local() -> bool {
698
6
        let run_test = std::env::var(ARTI_TESTING_ON_LOCAL).is_ok_and(|v| v == "1");
699
6
        if !run_test {
700
6
            println!("Skipping test, set {}=1 to run.", ARTI_TESTING_ON_LOCAL);
701
6
        }
702

            
703
6
        run_test
704
6
    }
705

            
706
    // Helper method to allow tests to be ran in a closure.
707
2
    fn test_with_tor_client<R: Runtime>(rt: R, f: impl FnOnce(TorClient<R>)) {
708
2
        let temp_dir = test_temp_dir!();
709
2
        temp_dir.used_by(move |temp_dir| {
710
2
            let arti_config = TorClientConfigBuilder::from_directories(
711
2
                temp_dir.join("state"),
712
2
                temp_dir.join("cache"),
713
2
            )
714
2
            .build()
715
2
            .expect("Failed to build TorClientConfig");
716
2

            
717
2
            let tor_client = arti_client::TorClient::with_runtime(rt)
718
2
                .config(arti_config)
719
2
                .create_unbootstrapped()
720
2
                .expect("Error creating Tor Client.");
721
2

            
722
2
            f(tor_client);
723
2
        });
724
2
    }
725

            
726
    // Helper function to make a request to check.torproject.org/api/ip and check if
727
    // it was done over Tor.
728
    fn request_is_tor(agent: ureq::Agent, https: bool) -> bool {
729
        let mut request = agent
730
            .get(format!(
731
                "http{}://check.torproject.org/api/ip",
732
                if https { "s" } else { "" }
733
            ))
734
            .call()
735
            .expect("Failed to make request.");
736
        let response = request
737
            .body_mut()
738
            .read_to_string()
739
            .expect("Failed to read body.");
740
        let json_response: serde_json::Value =
741
            serde_json::from_str(&response).expect("Failed to parse JSON.");
742
        let is_tor = json_response
743
            .get("IsTor")
744
            .expect("Failed to retrieve IsTor property from response")
745
            .as_bool()
746
            .expect("Failed to convert IsTor to bool");
747

            
748
        is_tor
749
    }
750

            
751
    // Quick internal test to check if our helper function `equal_types` works as expected.
752
    // Otherwise our other tests might not be reliable.
753
    #[test]
754
2
    fn test_equal_types() {
755
2
        assert_equal_types(&1, &i32::MIN);
756
2
        assert_equal_types(&1, &i64::MIN);
757
2
        assert_equal_types(&String::from("foo"), &String::with_capacity(1));
758
2
    }
759

            
760
    // `Connector::new` should return the default `Connector`.
761
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
762
    #[test]
763
    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
764
    fn articonnector_new_returns_default() {
765
        if !testing_on_local() {
766
            return;
767
        }
768

            
769
        let actual_connector = Connector::new().expect("Failed to create Connector.");
770
        let expected_connector = Connector {
771
            client: TorClient::with_runtime(
772
                tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
773
            )
774
            .create_unbootstrapped()
775
            .expect("Error creating Tor Client."),
776
            tls_provider: UreqTlsProvider::Rustls,
777
        };
778

            
779
        assert_equal_types(&expected_connector, &actual_connector);
780
        assert_equal_types(
781
            &actual_connector.client.runtime().clone(),
782
            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
783
        );
784
        assert_eq!(
785
            &actual_connector.tls_provider,
786
            &ureq::tls::TlsProvider::Rustls,
787
        );
788
    }
789

            
790
    // `Connector::with_tor_client` should return a `Connector` with specified Tor client set.
791
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
792
    #[test]
793
    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
794
    fn articonnector_with_tor_client() {
795
        if !testing_on_local() {
796
            return;
797
        }
798

            
799
        let tor_client = TorClient::with_runtime(
800
            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
801
        )
802
        .create_unbootstrapped()
803
        .expect("Error creating Tor Client.");
804

            
805
        let actual_connector = Connector::with_tor_client(tor_client);
806
        let expected_connector = Connector {
807
            client: TorClient::with_runtime(
808
                tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
809
            )
810
            .create_unbootstrapped()
811
            .expect("Error creating Tor Client."),
812
            tls_provider: UreqTlsProvider::Rustls,
813
        };
814

            
815
        assert_equal_types(&expected_connector, &actual_connector);
816
        assert_equal_types(
817
            &actual_connector.client.runtime().clone(),
818
            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
819
        );
820
        assert_eq!(
821
            &actual_connector.tls_provider,
822
            &ureq::tls::TlsProvider::Rustls,
823
        );
824
    }
825

            
826
    // The default instance returned by `Connector::builder` should equal to the default `Connector`.
827
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
828
    #[test]
829
2
    fn articonnectorbuilder_new_returns_default() {
830
2
        if !testing_on_local() {
831
2
            return;
832
        }
833

            
834
        let expected = Connector::new().expect("Failed to create Connector.");
835
        let actual = Connector::<tor_rtcompat::PreferredRuntime>::builder()
836
            .expect("Failed to create ConnectorBuilder.")
837
            .build()
838
            .expect("Failed to create Connector.");
839

            
840
        assert_equal_types(&expected, &actual);
841
        assert_equal_types(&expected.client.runtime(), &actual.client.runtime());
842
        assert_eq!(&expected.tls_provider, &actual.tls_provider);
843
2
    }
844

            
845
    // `ConnectorBuilder::with_runtime` should return a `ConnectorBuilder` with the specified runtime set.
846
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
847
    #[cfg(all(feature = "tokio", feature = "rustls"))]
848
    #[test]
849
2
    fn articonnectorbuilder_with_runtime() {
850
2
        if !testing_on_local() {
851
2
            return;
852
        }
853

            
854
        let arti_connector = ConnectorBuilder::with_runtime(
855
            tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime."),
856
        )
857
        .expect("Failed to create ConnectorBuilder.")
858
        .build()
859
        .expect("Failed to create Connector.");
860

            
861
        assert_equal_types(
862
            &arti_connector.client.runtime().clone(),
863
            &tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime."),
864
        );
865

            
866
        let arti_connector = ConnectorBuilder::with_runtime(
867
            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
868
        )
869
        .expect("Failed to create ConnectorBuilder.")
870
        .build()
871
        .expect("Failed to create Connector.");
872

            
873
        assert_equal_types(
874
            &arti_connector.client.runtime().clone(),
875
            &tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
876
        );
877
2
    }
878

            
879
    // `ConnectorBuilder::tor_client` should return a `Connector` with the specified `TorClient` set.
880
    #[cfg(all(feature = "tokio", feature = "rustls"))]
881
    #[test]
882
2
    fn articonnectorbuilder_set_tor_client() {
883
2
        let rt =
884
2
            tor_rtcompat::tokio::TokioRustlsRuntime::create().expect("Failed to create runtime.");
885
2

            
886
3
        test_with_tor_client(rt.clone(), move |tor_client| {
887
2
            let arti_connector = ConnectorBuilder::with_runtime(rt)
888
2
                .expect("Failed to create ConnectorBuilder.")
889
2
                .tor_client(tor_client.clone().isolated_client())
890
2
                .build()
891
2
                .expect("Failed to create Connector.");
892
2

            
893
2
            assert_equal_types(
894
2
                &arti_connector.client.runtime().clone(),
895
2
                &tor_rtcompat::tokio::TokioRustlsRuntime::create()
896
2
                    .expect("Failed to create runtime."),
897
2
            );
898
3
        });
899
2
    }
900

            
901
    // Test if the method `uri_to_host_port` returns the correct parameters.
902
    #[test]
903
2
    fn test_uri_to_host_port() {
904
2
        let uri = Uri::from_str("http://torproject.org").expect("Error parsing uri.");
905
2
        let (host, port) = uri_to_host_port(&uri).expect("Error parsing uri.");
906
2

            
907
2
        assert_eq!(host, "torproject.org");
908
2
        assert_eq!(port, 80);
909

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

            
913
2
        assert_eq!(host, "torproject.org");
914
2
        assert_eq!(port, 443);
915

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

            
919
2
        assert_eq!(host, "www.torproject.org");
920
2
        assert_eq!(port, 443);
921
2
    }
922

            
923
    // Test if `arti-ureq` default agent uses Tor to make the request.
924
    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
925
    #[test]
926
2
    fn request_goes_over_tor() {
927
2
        if !test_live_network() {
928
2
            return;
929
        }
930

            
931
        let is_tor = request_is_tor(
932
            default_agent().expect("Failed to retrieve default agent."),
933
            true,
934
        );
935

            
936
        assert_eq!(is_tor, true);
937
2
    }
938

            
939
    // Test if `arti-ureq` default agent uses Tor to make the request.
940
    // This test also checks if the Tor API returns false when the request is made with an
941
    // `ureq` agent that is not configured to use Tor to ensure the test is reliable.
942
    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
943
    #[test]
944
    #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
945
    fn request_goes_over_tor_with_unsafe_check() {
946
        if !test_live_network() {
947
            return;
948
        }
949

            
950
        let is_tor = request_is_tor(ureq::Agent::new_with_defaults(), true);
951
        assert_eq!(is_tor, false);
952

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

            
960
    // Test if the `ureq` client configured with `Connector` uses Tor tor make the request using bare HTTP.
961
    // This test is only ran when ARTI_TEST_LIVE_NETWORK is set to 1.
962
    #[test]
963
2
    fn request_with_bare_http() {
964
2
        if !test_live_network() {
965
2
            return;
966
        }
967

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

            
970
        test_with_tor_client(rt, |tor_client| {
971
            let arti_connector = Connector::with_tor_client(tor_client);
972
            let is_tor = request_is_tor(arti_connector.agent(), false);
973

            
974
            assert_eq!(is_tor, true);
975
        });
976
2
    }
977

            
978
    // Test if `get_default_tls_provider` correctly derives the TLS provider from the feature flags.
979
    #[test]
980
2
    fn test_get_default_tls_provider() {
981
2
        #[cfg(feature = "native-tls")]
982
2
        assert_eq!(get_default_tls_provider(), UreqTlsProvider::NativeTls);
983

            
984
        #[cfg(not(feature = "native-tls"))]
985
        assert_eq!(get_default_tls_provider(), UreqTlsProvider::Rustls);
986
2
    }
987

            
988
    // Test if configuring the `Connector` using `get_default_tls_provider` correctly sets the TLS provider
989
    // based on the feature flags.
990
    // This test is only ran when ARTI_TESTING_ON_LOCAL is set to 1.
991
    #[test]
992
2
    fn test_tor_client_with_get_default_tls_provider() {
993
2
        if !testing_on_local() {
994
2
            return;
995
        }
996

            
997
        let tor_client = TorClient::with_runtime(
998
            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
999
        )
        .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
    }
}