1
//! Define a response type for directory requests.
2

            
3
use std::str;
4

            
5
use tor_linkspec::{LoggedChanTarget, OwnedChanTarget};
6
use tor_proto::circuit::UniqId;
7

            
8
use crate::{RequestError, RequestFailedError};
9

            
10
/// A successful (or at any rate, well-formed) response to a directory
11
/// request.
12
#[derive(Debug, Clone)]
13
#[must_use = "You need to check whether the response was successful."]
14
pub struct DirResponse {
15
    /// An HTTP status code.
16
    status: u16,
17
    /// The message associated with the status code.
18
    status_message: Option<String>,
19
    /// The decompressed output that we got from the directory cache.
20
    output: Vec<u8>,
21
    /// The error, if any, that caused us to stop getting this response early.
22
    error: Option<RequestError>,
23
    /// Information about the directory cache we used.
24
    source: Option<SourceInfo>,
25
}
26

            
27
/// Information about the source of a directory response.
28
///
29
/// We use this to remember when a request has failed, so we can
30
/// abandon the circuit.
31
#[derive(Debug, Clone, derive_more::Display)]
32
#[display("{} via {}", cache_id, tunnel)]
33
pub struct SourceInfo {
34
    /// Unique identifier for the circuit we're using
35
    tunnel: UniqId,
36
    /// Identity of the directory cache that provided us this information.
37
    cache_id: LoggedChanTarget,
38
}
39

            
40
impl DirResponse {
41
    /// Construct a new DirResponse from its parts
42
2582
    pub(crate) fn new(
43
2582
        status: u16,
44
2582
        status_message: Option<String>,
45
2582
        error: Option<RequestError>,
46
2582
        output: Vec<u8>,
47
2582
        source: Option<SourceInfo>,
48
2582
    ) -> Self {
49
2582
        DirResponse {
50
2582
            status,
51
2582
            status_message,
52
2582
            output,
53
2582
            error,
54
2582
            source,
55
2582
        }
56
2582
    }
57

            
58
    /// Construct a new successful DirResponse from its body.
59
2
    pub fn from_body(body: impl AsRef<[u8]>) -> Self {
60
2
        Self::new(200, None, None, body.as_ref().to_vec(), None)
61
2
    }
62

            
63
    /// Return the HTTP status code for this response.
64
3177
    pub fn status_code(&self) -> u16 {
65
3177
        self.status
66
3177
    }
67

            
68
    /// Return true if this is in incomplete response.
69
2547
    pub fn is_partial(&self) -> bool {
70
2547
        self.error.is_some()
71
2547
    }
72

            
73
    /// Return the error from this response, if any.
74
4
    pub fn error(&self) -> Option<&RequestError> {
75
4
        self.error.as_ref()
76
4
    }
77

            
78
    /// Return the output from this response.
79
    ///
80
    /// Returns some output, even if the response indicates truncation or an error.
81
8
    pub fn output_unchecked(&self) -> &[u8] {
82
8
        &self.output
83
8
    }
84

            
85
    /// Return the output from this response, if it was successful and complete.
86
8
    pub fn output(&self) -> Result<&[u8], RequestFailedError> {
87
8
        self.check_ok()?;
88
2
        Ok(self.output_unchecked())
89
8
    }
90

            
91
    /// Return this the output from this response, as a string,
92
    /// if it was successful and complete and valid UTF-8.
93
    pub fn output_string(&self) -> Result<&str, RequestFailedError> {
94
        let output = self.output()?;
95
        let s = str::from_utf8(output).map_err(|_| RequestFailedError {
96
            // For RequestError::Utf8Encoding We need a `String::FromUtf8Error`
97
            // (which contains an owned copy of the bytes).
98
            error: String::from_utf8(output.to_owned())
99
                .expect_err("was bad, now good")
100
                .into(),
101
            source: self.source.clone(),
102
        })?;
103
        Ok(s)
104
    }
105

            
106
    /// Consume this DirResponse and return the output in it.
107
    ///
108
    /// Returns some output, even if the response indicates truncation or an error.
109
43
    pub fn into_output_unchecked(self) -> Vec<u8> {
110
43
        self.output
111
43
    }
112

            
113
    /// Consume this DirResponse and return the output, if it was successful and complete.
114
8
    pub fn into_output(self) -> Result<Vec<u8>, RequestFailedError> {
115
8
        self.check_ok()?;
116
2
        Ok(self.into_output_unchecked())
117
8
    }
118

            
119
    /// Consume this DirResponse and return the output, as a string,
120
    /// if it was successful and complete and valid UTF-8.
121
2535
    pub fn into_output_string(self) -> Result<String, RequestFailedError> {
122
2535
        self.check_ok()?;
123
1911
        let s = String::from_utf8(self.output).map_err(|error| RequestFailedError {
124
            error: error.into(),
125
            source: self.source.clone(),
126
1911
        })?;
127
1911
        Ok(s)
128
2535
    }
129

            
130
    /// Return the source information about this response.
131
41
    pub fn source(&self) -> Option<&SourceInfo> {
132
41
        self.source.as_ref()
133
41
    }
134

            
135
    /// Check if this request was successful and complete.
136
2551
    fn check_ok(&self) -> Result<(), RequestFailedError> {
137
2573
        let wrap_err = |error| {
138
636
            Err(RequestFailedError {
139
636
                error,
140
636
                source: self.source.clone(),
141
636
            })
142
636
        };
143
2551
        if let Some(error) = &self.error {
144
8
            return wrap_err(error.clone());
145
2543
        }
146
2543
        assert!(!self.is_partial(), "partial but no error?");
147
2543
        if self.status_code() != 200 {
148
628
            let msg = match &self.status_message {
149
628
                Some(m) => m.clone(),
150
                None => "".to_owned(),
151
            };
152
628
            return wrap_err(RequestError::HttpStatus(self.status_code(), msg));
153
1915
        }
154
1915
        Ok(())
155
2551
    }
156
}
157

            
158
impl SourceInfo {
159
    /// Try to construct a new SourceInfo representing the last hop of a given tunnel.
160
    ///
161
    /// Return an error if the tunnel is closed;
162
    /// return `Ok(None)` if the circuit's last hop is virtual.
163
    pub fn from_tunnel(
164
        tunnel: impl AsRef<tor_proto::ClientTunnel>,
165
    ) -> tor_proto::Result<Option<Self>> {
166
        let tunnel = tunnel.as_ref();
167
        match tunnel.last_hop_info()? {
168
            None => Ok(None),
169
            Some(last_hop) => Ok(Some(SourceInfo {
170
                tunnel: tunnel.unique_id(),
171
                cache_id: last_hop.into(),
172
            })),
173
        }
174
    }
175

            
176
    /// Return the unique circuit identifier for the circuit on which
177
    /// we received this info.
178
    pub fn unique_circ_id(&self) -> &UniqId {
179
        &self.tunnel
180
    }
181

            
182
    /// Return information about the peer from which we received this info.
183
    pub fn cache_id(&self) -> &OwnedChanTarget {
184
        self.cache_id.as_inner()
185
    }
186
}
187

            
188
#[cfg(test)]
189
mod test {
190
    // @@ begin test lint list maintained by maint/add_warning @@
191
    #![allow(clippy::bool_assert_comparison)]
192
    #![allow(clippy::clone_on_copy)]
193
    #![allow(clippy::dbg_macro)]
194
    #![allow(clippy::mixed_attributes_style)]
195
    #![allow(clippy::print_stderr)]
196
    #![allow(clippy::print_stdout)]
197
    #![allow(clippy::single_char_pattern)]
198
    #![allow(clippy::unwrap_used)]
199
    #![allow(clippy::unchecked_duration_subtraction)]
200
    #![allow(clippy::useless_vec)]
201
    #![allow(clippy::needless_pass_by_value)]
202
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
203
    use super::*;
204

            
205
    #[test]
206
    fn errors() {
207
        let mut response = DirResponse::new(200, None, None, vec![b'Y'], None);
208

            
209
        assert_eq!(response.output().unwrap(), b"Y");
210
        assert_eq!(response.clone().into_output().unwrap(), b"Y");
211

            
212
        let expect_error = |response: &DirResponse, error: RequestError| {
213
            let error = RequestFailedError {
214
                error,
215
                source: None,
216
            };
217
            let error = format!("{:?}", error);
218

            
219
            assert_eq!(error, format!("{:?}", response.output().unwrap_err()));
220
            assert_eq!(
221
                error,
222
                format!("{:?}", response.clone().into_output().unwrap_err())
223
            );
224
        };
225

            
226
        let with_error = |response: &DirResponse| {
227
            let mut response = response.clone();
228
            response.error = Some(RequestError::DirTimeout);
229
            expect_error(&response, RequestError::DirTimeout);
230
        };
231

            
232
        with_error(&response);
233

            
234
        response.status = 404;
235
        response.status_message = Some("Not found".into());
236
        expect_error(&response, RequestError::HttpStatus(404, "Not found".into()));
237

            
238
        with_error(&response);
239
    }
240
}