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
#![deny(clippy::string_slice)] // See arti#2571
48
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
364
        Ok(size > 0)
365
    }
366

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

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

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

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

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

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

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

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

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

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

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

            
470
        Ok(array_vec)
471
    }
472
}
473

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
604
        chain
605
    }
606
}
607

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

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

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

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

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

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

            
678
    use super::*;
679
    use arti_client::config::TorClientConfigBuilder;
680
    use std::str::FromStr;
681
    use test_temp_dir::test_temp_dir;
682

            
683
    const ARTI_TEST_LIVE_NETWORK: &str = "ARTI_TEST_LIVE_NETWORK";
684
    const ARTI_TESTING_ON_LOCAL: &str = "ARTI_TESTING_ON_LOCAL";
685

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

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

            
698
4
        run_test
699
4
    }
700

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

            
710
6
        run_test
711
6
    }
712

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

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

            
729
2
            f(tor_client);
730
2
        });
731
2
    }
732

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
912
2
        assert_eq!(host, "torproject.org");
913
2
        assert_eq!(port, 80);
914

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

            
918
2
        assert_eq!(host, "torproject.org");
919
2
        assert_eq!(port, 443);
920

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

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

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

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

            
941
        assert_eq!(is_tor, true);
942
2
    }
943

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

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

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

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

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

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

            
979
            assert_eq!(is_tor, true);
980
        });
981
2
    }
982

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

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

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