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

            
49
use std::sync::{Arc, Mutex};
50

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

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

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

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

            
72
use futures::io::{AsyncReadExt, AsyncWriteExt};
73

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

            
77
/// Compatibility between different async runtimes for Arti.
78
pub use tor_rtcompat;
79

            
80
/// Underlying HTTP/S client library.
81
pub use ureq;
82

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
345
        let buffer = self.buffer.output();
346
        let data_to_write = &buffer[..amount];
347

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

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

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

            
363
        Ok(size > 0)
364
    }
365

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

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

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

            
398
2
        let tls_provider = self.tls_provider.unwrap_or(get_default_tls_provider());
399

            
400
2
        Ok(Connector {
401
2
            client,
402
2
            tls_provider,
403
2
        })
404
2
    }
405

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

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

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

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

            
460
        let mut array_vec: ArrayVec<core::net::SocketAddr, 16> = ArrayVec::from_fn(|_| {
461
            core::net::SocketAddr::new(core::net::IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED), 0)
462
        });
463

            
464
        for ip in ips {
465
            let socket_addr = core::net::SocketAddr::new(ip, port);
466
            array_vec.push(socket_addr);
467
        }
468

            
469
        Ok(array_vec)
470
    }
471
}
472

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

            
483
impl<R: Runtime + ToplevelBlockOn> UreqConnector<()> for Connector<R> {
484
    type Out = Box<dyn Transport>;
485

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

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

            
502
        // Retrieve stream from Tor connection.
503
        let stream = self
504
            .client
505
            .runtime()
506
            .block_on(async { self.client.connect(addr).await })
507
            .map_err(Error::from)?;
508

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

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

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

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

            
562
        let ureq_config = ureq::config::Config::builder()
563
            .tls_config(
564
                ureq::tls::TlsConfig::builder()
565
                    .provider(self.tls_provider)
566
                    .build(),
567
            )
568
            .build();
569

            
570
        ureq::Agent::with_parts(ureq_config, self.connector_chain(), resolver)
571
    }
572

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

            
582
        if self.tls_provider != config.tls_config().provider() {
583
            return Err(Error::TlsConfigMismatch);
584
        }
585

            
586
        Ok(ureq::Agent::with_parts(
587
            config,
588
            self.connector_chain(),
589
            resolver,
590
        ))
591
    }
592

            
593
    /// Returns connector chain depending on features flag.
594
    fn connector_chain(self) -> impl UreqConnector {
595
        let chain = self;
596

            
597
        #[cfg(feature = "rustls")]
598
        let chain = chain.chain(RustlsConnector::default());
599

            
600
        #[cfg(feature = "native-tls")]
601
        let chain = chain.chain(NativeTlsConnector::default());
602

            
603
        chain
604
    }
605
}
606

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

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

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

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

            
657
6
    Ok((host.to_owned(), port))
658
6
}
659

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

            
676
    use super::*;
677
    use arti_client::config::TorClientConfigBuilder;
678
    use std::str::FromStr;
679
    use test_temp_dir::test_temp_dir;
680

            
681
    const ARTI_TEST_LIVE_NETWORK: &str = "ARTI_TEST_LIVE_NETWORK";
682
    const ARTI_TESTING_ON_LOCAL: &str = "ARTI_TESTING_ON_LOCAL";
683

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

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

            
696
4
        run_test
697
4
    }
698

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

            
708
6
        run_test
709
6
    }
710

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

            
722
2
            let tor_client = arti_client::TorClient::with_runtime(rt)
723
2
                .config(arti_config)
724
2
                .create_unbootstrapped()
725
2
                .expect("Error creating Tor Client.");
726

            
727
2
            f(tor_client);
728
2
        });
729
2
    }
730

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
910
2
        assert_eq!(host, "torproject.org");
911
2
        assert_eq!(port, 80);
912

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

            
916
2
        assert_eq!(host, "torproject.org");
917
2
        assert_eq!(port, 443);
918

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

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

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

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

            
939
        assert_eq!(is_tor, true);
940
2
    }
941

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

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

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

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

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

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

            
977
            assert_eq!(is_tor, true);
978
        });
979
2
    }
980

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

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

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

            
        let tor_client = TorClient::with_runtime(
            tor_rtcompat::PreferredRuntime::create().expect("Failed to create runtime."),
        )
        .create_unbootstrapped()
        .expect("Error creating Tor Client.");
        let arti_connector = Connector::<tor_rtcompat::PreferredRuntime>::builder()
            .expect("Failed to create ConnectorBuilder.")
            .tor_client(tor_client.clone().isolated_client())
            .tls_provider(get_default_tls_provider())
            .build()
            .expect("Failed to create Connector.");
        #[cfg(feature = "native-tls")]
        assert_eq!(
            &arti_connector.tls_provider,
            &ureq::tls::TlsProvider::NativeTls,
        );
        #[cfg(not(feature = "native-tls"))]
        assert_eq!(
            &arti_connector.tls_provider,
            &ureq::tls::TlsProvider::Rustls,
        );
2
    }
}