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 the HTTP status message for this response.
74    pub fn status_message(&self) -> Option<&str> {
75        self.status_message.as_deref()
76    }
77
78    /// Return true if this is in incomplete response.
79    pub fn is_partial(&self) -> bool {
80        self.error.is_some()
81    }
82
83    /// Return the error from this response, if any.
84    pub fn error(&self) -> Option<&RequestError> {
85        self.error.as_ref()
86    }
87
88    /// Return the output from this response.
89    ///
90    /// Returns some output, even if the response indicates truncation or an error.
91    pub fn output_unchecked(&self) -> &[u8] {
92        &self.output
93    }
94
95    /// Return the output from this response, if it was successful and complete.
96    pub fn output(&self) -> Result<&[u8], RequestFailedError> {
97        self.check_ok()?;
98        Ok(self.output_unchecked())
99    }
100
101    /// Return this the output from this response, as a string,
102    /// if it was successful and complete and valid UTF-8.
103    pub fn output_string(&self) -> Result<&str, RequestFailedError> {
104        let output = self.output()?;
105        let s = str::from_utf8(output).map_err(|_| RequestFailedError {
106            // For RequestError::Utf8Encoding We need a `String::FromUtf8Error`
107            // (which contains an owned copy of the bytes).
108            error: String::from_utf8(output.to_owned())
109                .expect_err("was bad, now good")
110                .into(),
111            source: self.source.clone(),
112        })?;
113        Ok(s)
114    }
115
116    /// Consume this DirResponse and return the output in it.
117    ///
118    /// Returns some output, even if the response indicates truncation or an error.
119    pub fn into_output_unchecked(self) -> Vec<u8> {
120        self.output
121    }
122
123    /// Consume this DirResponse and return the output, if it was successful and complete.
124    pub fn into_output(self) -> Result<Vec<u8>, RequestFailedError> {
125        self.check_ok()?;
126        Ok(self.into_output_unchecked())
127    }
128
129    /// Consume this DirResponse and return the output, as a string,
130    /// if it was successful and complete and valid UTF-8.
131    pub fn into_output_string(self) -> Result<String, RequestFailedError> {
132        self.check_ok()?;
133        let s = String::from_utf8(self.output).map_err(|error| RequestFailedError {
134            error: error.into(),
135            source: self.source.clone(),
136        })?;
137        Ok(s)
138    }
139
140    /// Return the source information about this response.
141    pub fn source(&self) -> Option<&SourceInfo> {
142        self.source.as_ref()
143    }
144
145    /// Check if this request was successful and complete.
146    fn check_ok(&self) -> Result<(), RequestFailedError> {
147        let wrap_err = |error| {
148            Err(RequestFailedError {
149                error,
150                source: self.source.clone(),
151            })
152        };
153        if let Some(error) = &self.error {
154            return wrap_err(error.clone());
155        }
156        assert!(!self.is_partial(), "partial but no error?");
157        if self.status_code() != 200 {
158            let msg = match &self.status_message {
159                Some(m) => m.clone(),
160                None => "".to_owned(),
161            };
162            return wrap_err(RequestError::HttpStatus(self.status_code(), msg));
163        }
164
165        // We do not allow a successful response with an empty body on GET.
166        if self.output.is_empty() && self.method == Method::GET {
167            return wrap_err(RequestError::EmptyResponse);
168        }
169
170        Ok(())
171    }
172}
173
174impl SourceInfo {
175    /// Try to construct a new SourceInfo representing the last hop of a given tunnel.
176    ///
177    /// Return an error if the tunnel is closed;
178    /// return `Ok(None)` if the circuit's last hop is virtual.
179    pub fn from_tunnel(
180        tunnel: impl AsRef<tor_proto::ClientTunnel>,
181    ) -> tor_proto::Result<Option<Self>> {
182        let tunnel = tunnel.as_ref();
183        match tunnel.last_hop_info()? {
184            None => Ok(None),
185            Some(last_hop) => Ok(Some(SourceInfo {
186                tunnel: tunnel.unique_id(),
187                cache_id: last_hop.into(),
188            })),
189        }
190    }
191
192    /// Return the unique circuit identifier for the circuit on which
193    /// we received this info.
194    pub fn unique_circ_id(&self) -> &UniqId {
195        &self.tunnel
196    }
197
198    /// Return information about the peer from which we received this info.
199    pub fn cache_id(&self) -> &OwnedChanTarget {
200        self.cache_id.as_inner()
201    }
202}
203
204#[cfg(test)]
205mod test {
206    // @@ begin test lint list maintained by maint/add_warning @@
207    #![allow(clippy::bool_assert_comparison)]
208    #![allow(clippy::clone_on_copy)]
209    #![allow(clippy::dbg_macro)]
210    #![allow(clippy::mixed_attributes_style)]
211    #![allow(clippy::print_stderr)]
212    #![allow(clippy::print_stdout)]
213    #![allow(clippy::single_char_pattern)]
214    #![allow(clippy::unwrap_used)]
215    #![allow(clippy::unchecked_time_subtraction)]
216    #![allow(clippy::useless_vec)]
217    #![allow(clippy::needless_pass_by_value)]
218    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
219    use super::*;
220
221    #[test]
222    fn errors() {
223        let mut response = DirResponse::new(Method::GET, 200, None, None, vec![b'Y'], None);
224
225        assert_eq!(response.output().unwrap(), b"Y");
226        assert_eq!(response.clone().into_output().unwrap(), b"Y");
227
228        let expect_error = |response: &DirResponse, error: RequestError| {
229            let error = RequestFailedError {
230                error,
231                source: None,
232            };
233            let error = format!("{:?}", error);
234
235            assert_eq!(error, format!("{:?}", response.output().unwrap_err()));
236            assert_eq!(
237                error,
238                format!("{:?}", response.clone().into_output().unwrap_err())
239            );
240        };
241
242        let with_error = |response: &DirResponse| {
243            let mut response = response.clone();
244            response.error = Some(RequestError::DirTimeout);
245            expect_error(&response, RequestError::DirTimeout);
246        };
247
248        with_error(&response);
249
250        response.output = vec![];
251        expect_error(&response, RequestError::EmptyResponse);
252
253        response.status = 404;
254        response.status_message = Some("Not found".into());
255        expect_error(&response, RequestError::HttpStatus(404, "Not found".into()));
256
257        with_error(&response);
258    }
259}