Skip to main content

tor_dirclient/
response.rs

1//! Define a response type for directory requests.
2
3use std::str;
4
5use http::Method;
6use tor_linkspec::{LoggedChanTarget, OwnedChanTarget};
7use tor_proto::circuit::UniqId;
8
9use crate::{RequestError, RequestFailedError};
10
11/// A successful (or at any rate, well-formed) response to a directory
12/// request.
13#[derive(Debug, Clone)]
14#[must_use = "You need to check whether the response was successful."]
15pub struct DirResponse {
16    /// The HTTP method of the request.
17    method: Method,
18    /// An HTTP status code.
19    status: u16,
20    /// The message associated with the status code.
21    status_message: Option<String>,
22    /// The decompressed output that we got from the directory cache.
23    output: Vec<u8>,
24    /// The error, if any, that caused us to stop getting this response early.
25    error: Option<RequestError>,
26    /// Information about the directory cache we used.
27    source: Option<SourceInfo>,
28}
29
30/// Information about the source of a directory response.
31///
32/// We use this to remember when a request has failed, so we can
33/// abandon the circuit.
34#[derive(Debug, Clone, derive_more::Display)]
35#[display("{} via {}", cache_id, tunnel)]
36pub struct SourceInfo {
37    /// Unique identifier for the circuit we're using
38    tunnel: UniqId,
39    /// Identity of the directory cache that provided us this information.
40    cache_id: LoggedChanTarget,
41}
42
43impl DirResponse {
44    /// Construct a new DirResponse from its parts
45    pub(crate) fn new(
46        method: Method,
47        status: u16,
48        status_message: Option<String>,
49        error: Option<RequestError>,
50        output: Vec<u8>,
51        source: Option<SourceInfo>,
52    ) -> Self {
53        DirResponse {
54            method,
55            status,
56            status_message,
57            output,
58            error,
59            source,
60        }
61    }
62
63    /// Construct a new successful DirResponse to a GET request from its body.
64    pub fn from_get_body(body: impl AsRef<[u8]>) -> Self {
65        Self::new(Method::GET, 200, None, None, body.as_ref().to_vec(), None)
66    }
67
68    /// Return the HTTP status code for this response.
69    pub fn status_code(&self) -> u16 {
70        self.status
71    }
72
73    /// Return true if this is in incomplete response.
74    pub fn is_partial(&self) -> bool {
75        self.error.is_some()
76    }
77
78    /// Return the error from this response, if any.
79    pub fn error(&self) -> Option<&RequestError> {
80        self.error.as_ref()
81    }
82
83    /// Return the output from this response.
84    ///
85    /// Returns some output, even if the response indicates truncation or an error.
86    pub fn output_unchecked(&self) -> &[u8] {
87        &self.output
88    }
89
90    /// Return the output from this response, if it was successful and complete.
91    pub fn output(&self) -> Result<&[u8], RequestFailedError> {
92        self.check_ok()?;
93        Ok(self.output_unchecked())
94    }
95
96    /// Return this the output from this response, as a string,
97    /// if it was successful and complete and valid UTF-8.
98    pub fn output_string(&self) -> Result<&str, RequestFailedError> {
99        let output = self.output()?;
100        let s = str::from_utf8(output).map_err(|_| RequestFailedError {
101            // For RequestError::Utf8Encoding We need a `String::FromUtf8Error`
102            // (which contains an owned copy of the bytes).
103            error: String::from_utf8(output.to_owned())
104                .expect_err("was bad, now good")
105                .into(),
106            source: self.source.clone(),
107        })?;
108        Ok(s)
109    }
110
111    /// Consume this DirResponse and return the output in it.
112    ///
113    /// Returns some output, even if the response indicates truncation or an error.
114    pub fn into_output_unchecked(self) -> Vec<u8> {
115        self.output
116    }
117
118    /// Consume this DirResponse and return the output, if it was successful and complete.
119    pub fn into_output(self) -> Result<Vec<u8>, RequestFailedError> {
120        self.check_ok()?;
121        Ok(self.into_output_unchecked())
122    }
123
124    /// Consume this DirResponse and return the output, as a string,
125    /// if it was successful and complete and valid UTF-8.
126    pub fn into_output_string(self) -> Result<String, RequestFailedError> {
127        self.check_ok()?;
128        let s = String::from_utf8(self.output).map_err(|error| RequestFailedError {
129            error: error.into(),
130            source: self.source.clone(),
131        })?;
132        Ok(s)
133    }
134
135    /// Return the source information about this response.
136    pub fn source(&self) -> Option<&SourceInfo> {
137        self.source.as_ref()
138    }
139
140    /// Check if this request was successful and complete.
141    fn check_ok(&self) -> Result<(), RequestFailedError> {
142        let wrap_err = |error| {
143            Err(RequestFailedError {
144                error,
145                source: self.source.clone(),
146            })
147        };
148        if let Some(error) = &self.error {
149            return wrap_err(error.clone());
150        }
151        assert!(!self.is_partial(), "partial but no error?");
152        if self.status_code() != 200 {
153            let msg = match &self.status_message {
154                Some(m) => m.clone(),
155                None => "".to_owned(),
156            };
157            return wrap_err(RequestError::HttpStatus(self.status_code(), msg));
158        }
159
160        // We do not allow a successful response with an empty body on GET.
161        if self.output.is_empty() && self.method == Method::GET {
162            return wrap_err(RequestError::EmptyResponse);
163        }
164
165        Ok(())
166    }
167}
168
169impl SourceInfo {
170    /// Try to construct a new SourceInfo representing the last hop of a given tunnel.
171    ///
172    /// Return an error if the tunnel is closed;
173    /// return `Ok(None)` if the circuit's last hop is virtual.
174    pub fn from_tunnel(
175        tunnel: impl AsRef<tor_proto::ClientTunnel>,
176    ) -> tor_proto::Result<Option<Self>> {
177        let tunnel = tunnel.as_ref();
178        match tunnel.last_hop_info()? {
179            None => Ok(None),
180            Some(last_hop) => Ok(Some(SourceInfo {
181                tunnel: tunnel.unique_id(),
182                cache_id: last_hop.into(),
183            })),
184        }
185    }
186
187    /// Return the unique circuit identifier for the circuit on which
188    /// we received this info.
189    pub fn unique_circ_id(&self) -> &UniqId {
190        &self.tunnel
191    }
192
193    /// Return information about the peer from which we received this info.
194    pub fn cache_id(&self) -> &OwnedChanTarget {
195        self.cache_id.as_inner()
196    }
197}
198
199#[cfg(test)]
200mod test {
201    // @@ begin test lint list maintained by maint/add_warning @@
202    #![allow(clippy::bool_assert_comparison)]
203    #![allow(clippy::clone_on_copy)]
204    #![allow(clippy::dbg_macro)]
205    #![allow(clippy::mixed_attributes_style)]
206    #![allow(clippy::print_stderr)]
207    #![allow(clippy::print_stdout)]
208    #![allow(clippy::single_char_pattern)]
209    #![allow(clippy::unwrap_used)]
210    #![allow(clippy::unchecked_time_subtraction)]
211    #![allow(clippy::useless_vec)]
212    #![allow(clippy::needless_pass_by_value)]
213    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
214    use super::*;
215
216    #[test]
217    fn errors() {
218        let mut response = DirResponse::new(Method::GET, 200, None, None, vec![b'Y'], None);
219
220        assert_eq!(response.output().unwrap(), b"Y");
221        assert_eq!(response.clone().into_output().unwrap(), b"Y");
222
223        let expect_error = |response: &DirResponse, error: RequestError| {
224            let error = RequestFailedError {
225                error,
226                source: None,
227            };
228            let error = format!("{:?}", error);
229
230            assert_eq!(error, format!("{:?}", response.output().unwrap_err()));
231            assert_eq!(
232                error,
233                format!("{:?}", response.clone().into_output().unwrap_err())
234            );
235        };
236
237        let with_error = |response: &DirResponse| {
238            let mut response = response.clone();
239            response.error = Some(RequestError::DirTimeout);
240            expect_error(&response, RequestError::DirTimeout);
241        };
242
243        with_error(&response);
244
245        response.output = vec![];
246        expect_error(&response, RequestError::EmptyResponse);
247
248        response.status = 404;
249        response.status_message = Some("Not found".into());
250        expect_error(&response, RequestError::HttpStatus(404, "Not found".into()));
251
252        with_error(&response);
253    }
254}