1
//! A generic HTTP uploader.
2

            
3
use std::{net::SocketAddr, sync::Arc, time::Duration};
4

            
5
use async_trait::async_trait;
6
use tor_error::HasKind;
7
use tor_rtcompat::{NetStreamProvider, Runtime, SleepProviderExt};
8

            
9
use crate::{UploadError, Uploader};
10

            
11
/// An [`Uploader`] implementation that uses `tor_dirclient` to make a direct HTTP request,
12
/// not over the Tor network.
13
///
14
/// The targets for this uploader are *lists* of IP addresses:
15
/// Each target represents a *single* directory,
16
/// so we attempt to upload to at most one address in each target.
17
pub struct DirectHttpUploader<R> {
18
    /// A runtime to use for opening TCP connections and creating timers.
19
    runtime: R,
20

            
21
    /// How long should we delay for 503 responses?
22
    polite_delay: Duration,
23
}
24

            
25
/// A failure to upload not counted under some other error type.
26
#[derive(Clone, Debug, thiserror::Error)]
27
enum HttpUploadError {
28
    /// The request required anonymity, and this uploader only supports direct requests.
29
    #[error("Request type requires anonymity")]
30
    RequiresAnonymity,
31

            
32
    /// We tried to upload to a Target with no actual addresses.
33
    #[error("No address for target")]
34
    NoAddress,
35
}
36

            
37
impl HasKind for HttpUploadError {
38
    fn kind(&self) -> tor_error::ErrorKind {
39
        tor_error::ErrorKind::BadApiUsage
40
    }
41
}
42

            
43
#[async_trait]
44
impl<R: Runtime> Uploader for DirectHttpUploader<R> {
45
    type Doc = dyn tor_dirclient::request::Requestable;
46
    type Target = Vec<SocketAddr>;
47

            
48
    async fn upload(
49
        self: Arc<Self>,
50
        target: Arc<Vec<SocketAddr>>,
51
        doc: Arc<dyn tor_dirclient::request::Requestable>,
52
    ) -> Result<(), UploadError> {
53
        if doc.anonymized() != tor_dirclient::AnonymizedRequest::Direct {
54
            return Err(UploadError::DocumentFailedPermanently(Arc::new(
55
                HttpUploadError::RequiresAnonymity,
56
            ) as _));
57
        }
58

            
59
        let mut conn = self.connect(target.as_ref()).await?;
60

            
61
        let source_info = None;
62

            
63
        // (This method already checks for timeouts, so we don't have to.)
64
        let response =
65
            tor_dirclient::send_request(&self.runtime, &doc, &mut conn, source_info).await;
66

            
67
        UploadError::from_directory_response(response)
68
            .map_err(|e| e.set_503_delay(self.polite_delay))
69
    }
70
}
71
impl<R: Runtime> DirectHttpUploader<R> {
72
    /// Create a new [`DirectHttpUploader`].
73
    ///
74
    /// Does not launch any requests; use a [`Publisher`](super::Publisher) for that.
75
    pub fn new(runtime: R) -> Self {
76
        Self {
77
            runtime,
78
            polite_delay: Duration::new(15 * 60, 0), // Arbitrary! Should be lower for authorities!
79
        }
80
    }
81

            
82
    /// Open a new connection to one of the addresses in `target`.
83
    async fn connect(
84
        &self,
85
        target: &[SocketAddr],
86
    ) -> Result<<R as NetStreamProvider>::Stream, UploadError> {
87
        /// How long should we wait for a given connect attempt to succeed or fail?
88
        const CONNECT_TIMEOUT: Duration = Duration::new(30, 0);
89

            
90
        let mut last_error = None;
91
        // TODO: Happy eyeballs?
92
        // TODO: Return all errors?
93
        for addr in target {
94
            match self
95
                .runtime
96
                .timeout(CONNECT_TIMEOUT, self.runtime.connect(addr))
97
                .await
98
            {
99
                Ok(Ok(conn)) => return Ok(conn),
100
                Ok(Err(e)) => last_error = Some(UploadError::Connect(Arc::new(e))),
101
                Err(_timeout) => last_error = Some(UploadError::Timeout),
102
            }
103
        }
104
        Err(last_error.unwrap_or_else(|| {
105
            UploadError::DocumentFailedPermanently(Arc::new(HttpUploadError::NoAddress) as _)
106
        }))
107
    }
108
}