/ crates / tor-dirclient / src / response.rs
response.rs
  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::{ClientCirc, 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, circuit)]
 33  pub struct SourceInfo {
 34      /// Unique identifier for the circuit we're using
 35      circuit: 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      pub(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      }
 57  
 58      /// Construct a new successful DirResponse from its body.
 59      pub fn from_body(body: impl AsRef<[u8]>) -> Self {
 60          Self::new(200, None, None, body.as_ref().to_vec(), None)
 61      }
 62  
 63      /// Return the HTTP status code for this response.
 64      pub fn status_code(&self) -> u16 {
 65          self.status
 66      }
 67  
 68      /// Return true if this is in incomplete response.
 69      pub fn is_partial(&self) -> bool {
 70          self.error.is_some()
 71      }
 72  
 73      /// Return the error from this response, if any.
 74      pub fn error(&self) -> Option<&RequestError> {
 75          self.error.as_ref()
 76      }
 77  
 78      /// Return the output from this response.
 79      ///
 80      /// Returns some output, even if the response indicates truncation or an error.
 81      pub fn output_unchecked(&self) -> &[u8] {
 82          &self.output
 83      }
 84  
 85      /// Return the output from this response, if it was successful and complete.
 86      pub fn output(&self) -> Result<&[u8], RequestFailedError> {
 87          self.check_ok()?;
 88          Ok(self.output_unchecked())
 89      }
 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      pub fn into_output_unchecked(self) -> Vec<u8> {
110          self.output
111      }
112  
113      /// Consume this DirResponse and return the output, if it was successful and complete.
114      pub fn into_output(self) -> Result<Vec<u8>, RequestFailedError> {
115          self.check_ok()?;
116          Ok(self.into_output_unchecked())
117      }
118  
119      /// Consume this DirResponse and return the output, as a string,
120      /// if it was successful and complete and valid UTF-8.
121      pub fn into_output_string(self) -> Result<String, RequestFailedError> {
122          self.check_ok()?;
123          let s = String::from_utf8(self.output).map_err(|error| RequestFailedError {
124              error: error.into(),
125              source: self.source.clone(),
126          })?;
127          Ok(s)
128      }
129  
130      /// Return the source information about this response.
131      pub fn source(&self) -> Option<&SourceInfo> {
132          self.source.as_ref()
133      }
134  
135      /// Check if this request was successful and complete.
136      fn check_ok(&self) -> Result<(), RequestFailedError> {
137          let wrap_err = |error| {
138              Err(RequestFailedError {
139                  error,
140                  source: self.source.clone(),
141              })
142          };
143          if let Some(error) = &self.error {
144              return wrap_err(error.clone());
145          }
146          assert!(!self.is_partial(), "partial but no error?");
147          if self.status_code() != 200 {
148              let msg = match &self.status_message {
149                  Some(m) => m.clone(),
150                  None => "".to_owned(),
151              };
152              return wrap_err(RequestError::HttpStatus(self.status_code(), msg));
153          }
154          Ok(())
155      }
156  }
157  
158  impl SourceInfo {
159      /// Construct a new SourceInfo
160      pub(crate) fn from_circuit(circuit: &ClientCirc) -> Self {
161          SourceInfo {
162              circuit: circuit.unique_id(),
163              cache_id: circuit.first_hop().into(),
164          }
165      }
166  
167      /// Return the unique circuit identifier for the circuit on which
168      /// we received this info.
169      pub fn unique_circ_id(&self) -> &UniqId {
170          &self.circuit
171      }
172  
173      /// Return information about the peer from which we received this info.
174      pub fn cache_id(&self) -> &OwnedChanTarget {
175          self.cache_id.as_inner()
176      }
177  }
178  
179  #[cfg(test)]
180  mod 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 @@ -->
194      use super::*;
195  
196      #[test]
197      fn errors() {
198          let mut response = DirResponse::new(200, None, None, vec![b'Y'], None);
199  
200          assert_eq!(response.output().unwrap(), b"Y");
201          assert_eq!(response.clone().into_output().unwrap(), b"Y");
202  
203          let expect_error = |response: &DirResponse, error: RequestError| {
204              let error = RequestFailedError {
205                  error,
206                  source: None,
207              };
208              let error = format!("{:?}", error);
209  
210              assert_eq!(error, format!("{:?}", response.output().unwrap_err()));
211              assert_eq!(
212                  error,
213                  format!("{:?}", response.clone().into_output().unwrap_err())
214              );
215          };
216  
217          let with_error = |response: &DirResponse| {
218              let mut response = response.clone();
219              response.error = Some(RequestError::DirTimeout);
220              expect_error(&response, RequestError::DirTimeout);
221          };
222  
223          with_error(&response);
224  
225          response.status = 404;
226          response.status_message = Some("Not found".into());
227          expect_error(&response, RequestError::HttpStatus(404, "Not found".into()));
228  
229          with_error(&response);
230      }
231  }