1//! Define a response type for directory requests.
23use std::str;
45use tor_linkspec::{LoggedChanTarget, OwnedChanTarget};
6use tor_proto::circuit::{ClientCirc, UniqId};
78use crate::{RequestError, RequestFailedError};
910/// 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."]
14pub struct DirResponse {
15/// An HTTP status code.
16status: u16,
17/// The message associated with the status code.
18status_message: Option<String>,
19/// The decompressed output that we got from the directory cache.
20output: Vec<u8>,
21/// The error, if any, that caused us to stop getting this response early.
22error: Option<RequestError>,
23/// Information about the directory cache we used.
24source: Option<SourceInfo>,
25}
2627/// 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, circuit)]
33pub struct SourceInfo {
34/// Unique identifier for the circuit we're using
35circuit: UniqId,
36/// Identity of the directory cache that provided us this information.
37cache_id: LoggedChanTarget,
38}
3940impl DirResponse {
41/// Construct a new DirResponse from its parts
42pub(crate) fn new(
43 status: u16,
44 status_message: Option<String>,
45 error: Option<RequestError>,
46 output: Vec<u8>,
47 source: Option<SourceInfo>,
48 ) -> Self {
49 DirResponse {
50 status,
51 status_message,
52 output,
53 error,
54 source,
55 }
56 }
5758/// Construct a new successful DirResponse from its body.
59pub fn from_body(body: impl AsRef<[u8]>) -> Self {
60Self::new(200, None, None, body.as_ref().to_vec(), None)
61 }
6263/// Return the HTTP status code for this response.
64pub fn status_code(&self) -> u16 {
65self.status
66 }
6768/// Return true if this is in incomplete response.
69pub fn is_partial(&self) -> bool {
70self.error.is_some()
71 }
7273/// Return the error from this response, if any.
74pub fn error(&self) -> Option<&RequestError> {
75self.error.as_ref()
76 }
7778/// Return the output from this response.
79 ///
80 /// Returns some output, even if the response indicates truncation or an error.
81pub fn output_unchecked(&self) -> &[u8] {
82&self.output
83 }
8485/// Return the output from this response, if it was successful and complete.
86pub fn output(&self) -> Result<&[u8], RequestFailedError> {
87self.check_ok()?;
88Ok(self.output_unchecked())
89 }
9091/// Return this the output from this response, as a string,
92 /// if it was successful and complete and valid UTF-8.
93pub fn output_string(&self) -> Result<&str, RequestFailedError> {
94let output = self.output()?;
95let 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).
98error: String::from_utf8(output.to_owned())
99 .expect_err("was bad, now good")
100 .into(),
101 source: self.source.clone(),
102 })?;
103Ok(s)
104 }
105106/// Consume this DirResponse and return the output in it.
107 ///
108 /// Returns some output, even if the response indicates truncation or an error.
109pub fn into_output_unchecked(self) -> Vec<u8> {
110self.output
111 }
112113/// Consume this DirResponse and return the output, if it was successful and complete.
114pub fn into_output(self) -> Result<Vec<u8>, RequestFailedError> {
115self.check_ok()?;
116Ok(self.into_output_unchecked())
117 }
118119/// Consume this DirResponse and return the output, as a string,
120 /// if it was successful and complete and valid UTF-8.
121pub fn into_output_string(self) -> Result<String, RequestFailedError> {
122self.check_ok()?;
123let s = String::from_utf8(self.output).map_err(|error| RequestFailedError {
124 error: error.into(),
125 source: self.source.clone(),
126 })?;
127Ok(s)
128 }
129130/// Return the source information about this response.
131pub fn source(&self) -> Option<&SourceInfo> {
132self.source.as_ref()
133 }
134135/// Check if this request was successful and complete.
136fn check_ok(&self) -> Result<(), RequestFailedError> {
137let wrap_err = |error| {
138Err(RequestFailedError {
139 error,
140 source: self.source.clone(),
141 })
142 };
143if let Some(error) = &self.error {
144return wrap_err(error.clone());
145 }
146assert!(!self.is_partial(), "partial but no error?");
147if self.status_code() != 200 {
148let msg = match &self.status_message {
149Some(m) => m.clone(),
150None => "".to_owned(),
151 };
152return wrap_err(RequestError::HttpStatus(self.status_code(), msg));
153 }
154Ok(())
155 }
156}
157158impl SourceInfo {
159/// Construct a new SourceInfo
160pub(crate) fn from_circuit(circuit: &ClientCirc) -> tor_proto::Result<Self> {
161Ok(SourceInfo {
162 circuit: circuit.unique_id(),
163 cache_id: circuit.first_hop()?.into(),
164 })
165 }
166167/// Return the unique circuit identifier for the circuit on which
168 /// we received this info.
169pub fn unique_circ_id(&self) -> &UniqId {
170&self.circuit
171 }
172173/// Return information about the peer from which we received this info.
174pub fn cache_id(&self) -> &OwnedChanTarget {
175self.cache_id.as_inner()
176 }
177}
178179#[cfg(test)]
180mod test {
181// @@ begin test lint list maintained by maint/add_warning @@
182#![allow(clippy::bool_assert_comparison)]
183 #![allow(clippy::clone_on_copy)]
184 #![allow(clippy::dbg_macro)]
185 #![allow(clippy::mixed_attributes_style)]
186 #![allow(clippy::print_stderr)]
187 #![allow(clippy::print_stdout)]
188 #![allow(clippy::single_char_pattern)]
189 #![allow(clippy::unwrap_used)]
190 #![allow(clippy::unchecked_duration_subtraction)]
191 #![allow(clippy::useless_vec)]
192 #![allow(clippy::needless_pass_by_value)]
193//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
194use super::*;
195196#[test]
197fn errors() {
198let mut response = DirResponse::new(200, None, None, vec![b'Y'], None);
199200assert_eq!(response.output().unwrap(), b"Y");
201assert_eq!(response.clone().into_output().unwrap(), b"Y");
202203let expect_error = |response: &DirResponse, error: RequestError| {
204let error = RequestFailedError {
205 error,
206 source: None,
207 };
208let error = format!("{:?}", error);
209210assert_eq!(error, format!("{:?}", response.output().unwrap_err()));
211assert_eq!(
212 error,
213format!("{:?}", response.clone().into_output().unwrap_err())
214 );
215 };
216217let with_error = |response: &DirResponse| {
218let mut response = response.clone();
219 response.error = Some(RequestError::DirTimeout);
220 expect_error(&response, RequestError::DirTimeout);
221 };
222223 with_error(&response);
224225 response.status = 404;
226 response.status_message = Some("Not found".into());
227 expect_error(&response, RequestError::HttpStatus(404, "Not found".into()));
228229 with_error(&response);
230 }
231}