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 }