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
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
361
        Ok(size > 0)
362
    }
363

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

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

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

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

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

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

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

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

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

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

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

            
465
        Ok(array_vec)
466
    }
467
}
468

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
599
        chain
600
    }
601
}
602

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

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

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

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

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

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

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

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

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

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

            
692
4
        run_test
693
4
    }
694

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

            
704
6
        run_test
705
6
    }
706

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

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

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

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

            
749
        is_tor
750
    }
751

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
998
        let tor_client = TorClient::with_runtime(
999
            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
    }
}