/ crates / tor-dirclient / src / request.rs
request.rs
  1  //! Descriptions objects for different kinds of directory requests
  2  //! that we can make.
  3  
  4  use tor_llcrypto::pk::rsa::RsaIdentity;
  5  use tor_netdoc::doc::authcert::AuthCertKeyIds;
  6  use tor_netdoc::doc::microdesc::MdDigest;
  7  use tor_netdoc::doc::netstatus::ConsensusFlavor;
  8  #[cfg(feature = "routerdesc")]
  9  use tor_netdoc::doc::routerdesc::RdDigest;
 10  use tor_proto::circuit::ClientCirc;
 11  
 12  #[cfg(feature = "hs-client")]
 13  use tor_hscrypto::pk::HsBlindId;
 14  
 15  /// Alias for a result with a `RequestError`.
 16  type Result<T> = std::result::Result<T, crate::err::RequestError>;
 17  
 18  use base64ct::{Base64Unpadded, Encoding as _};
 19  use std::borrow::Cow;
 20  use std::iter::FromIterator;
 21  use std::time::{Duration, SystemTime};
 22  
 23  use itertools::Itertools;
 24  
 25  use crate::err::RequestError;
 26  use crate::AnonymizedRequest;
 27  
 28  /// Declare an inaccessible public type.
 29  pub(crate) mod sealed {
 30      use super::{AnonymizedRequest, ClientCirc, Result};
 31      /// Sealed trait to help implement [`Requestable`](super::Requestable): not
 32      /// visible outside this crate, so we can change its methods however we like.
 33      pub trait RequestableInner: Send + Sync {
 34          /// Build an [`http::Request`] from this Requestable, if
 35          /// it is well-formed.
 36          //
 37          // TODO: This API is a bit troublesome in how it takes &self and
 38          // returns a Request<String>.  First, most Requestables don't actually have
 39          // a body to send, and for them having an empty String in their body is a
 40          // bit silly.  Second, taking a reference to self but returning an owned
 41          // String means that we will often have to clone an internal string owned by
 42          // this Requestable instance.
 43          fn make_request(&self) -> Result<http::Request<String>>;
 44  
 45          /// Return true if partial response bodies are potentially useful.
 46          ///
 47          /// This is true for request types where we're going to be downloading
 48          /// multiple documents, and we know how to parse out the ones we wanted
 49          /// if the answer is truncated.
 50          fn partial_response_body_ok(&self) -> bool;
 51  
 52          /// Return the maximum allowable response length we'll accept for this
 53          /// request.
 54          fn max_response_len(&self) -> usize {
 55              (16 * 1024 * 1024) - 1
 56          }
 57  
 58          /// Return an error if there is some problem with the provided circuit that
 59          /// would keep it from being used for this request.
 60          fn check_circuit(&self, circ: &ClientCirc) -> Result<()> {
 61              let _ = circ;
 62              Ok(())
 63          }
 64  
 65          /// Return a value to say whether this request must be anonymized.
 66          fn anonymized(&self) -> AnonymizedRequest;
 67      }
 68  }
 69  
 70  /// A request for an object that can be served over the Tor directory system.
 71  pub trait Requestable: sealed::RequestableInner {
 72      /// Return a wrapper around this [`Requestable`] that implements `Debug`,
 73      /// and whose output shows the actual HTTP request that will be generated.
 74      ///
 75      /// The format is not guaranteed to  be stable.
 76      fn debug_request(&self) -> DisplayRequestable<'_, Self>
 77      where
 78          Self: Sized,
 79      {
 80          DisplayRequestable(self)
 81      }
 82  }
 83  impl<T: sealed::RequestableInner> Requestable for T {}
 84  
 85  /// A wrapper to implement [`Requestable::debug_request`].
 86  pub struct DisplayRequestable<'a, R: Requestable>(&'a R);
 87  
 88  impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> {
 89      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 90          write!(f, "{:?}", self.0.make_request())
 91      }
 92  }
 93  
 94  /// How much clock skew do we allow in the distance between the directory
 95  /// cache's clock and our own?
 96  ///
 97  ///  If we find more skew than this, we end the
 98  /// request early, on the theory that the directory will not tell us any
 99  /// information we'd accept.
100  #[derive(Clone, Debug)]
101  struct SkewLimit {
102      /// We refuse to proceed if the directory says we are more fast than this.
103      ///
104      /// (This is equivalent to deciding that, from our perspective, the
105      /// directory is at least this slow.)
106      max_fast: Duration,
107  
108      /// We refuse to proceed if the directory says that we are more slow than
109      /// this.
110      ///
111      /// (This is equivalent to deciding that, from our perspective, the
112      /// directory is at least this fast.)
113      max_slow: Duration,
114  }
115  
116  /// A Request for a consensus directory.
117  #[derive(Debug, Clone)]
118  pub struct ConsensusRequest {
119      /// What flavor of consensus are we asking for?  Right now, only
120      /// "microdesc" and "ns" are supported.
121      flavor: ConsensusFlavor,
122      /// A list of the authority identities that we believe in.  We tell the
123      /// directory cache only to give us a consensus if it is signed by enough
124      /// of these authorities.
125      authority_ids: Vec<RsaIdentity>,
126      /// The publication time of the most recent consensus we have.  Used to
127      /// generate an If-Modified-Since header so that we don't get a document
128      /// we already have.
129      last_consensus_published: Option<SystemTime>,
130      /// A set of SHA3-256 digests of the _signed portion_ of consensuses we have.
131      /// Used to declare what diffs we would accept.
132      ///
133      /// (Currently we don't send this, since we can't handle diffs.)
134      last_consensus_sha3_256: Vec<[u8; 32]>,
135      /// If present, the largest amount of clock skew to allow between ourself and a directory cache.
136      skew_limit: Option<SkewLimit>,
137  }
138  
139  impl ConsensusRequest {
140      /// Create a new request for a consensus directory document.
141      pub fn new(flavor: ConsensusFlavor) -> Self {
142          ConsensusRequest {
143              flavor,
144              authority_ids: Vec::new(),
145              last_consensus_published: None,
146              last_consensus_sha3_256: Vec::new(),
147              skew_limit: None,
148          }
149      }
150  
151      /// Add `id` to the list of authorities that this request should
152      /// say we believe in.
153      pub fn push_authority_id(&mut self, id: RsaIdentity) {
154          self.authority_ids.push(id);
155      }
156  
157      /// Add `d` to the list of consensus digests this request should
158      /// say we already have.
159      pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
160          self.last_consensus_sha3_256.push(d);
161      }
162  
163      /// Set the publication time we should say we have for our last
164      /// consensus to `when`.
165      pub fn set_last_consensus_date(&mut self, when: SystemTime) {
166          self.last_consensus_published = Some(when);
167      }
168  
169      /// Return a slice of the consensus digests that we're saying we
170      /// already have.
171      pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
172          self.last_consensus_sha3_256.iter()
173      }
174  
175      /// Return an iterator of the authority identities that this request
176      /// is saying we believe in.
177      pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
178          self.authority_ids.iter()
179      }
180  
181      /// Return the date we're reporting for our most recent consensus.
182      pub fn last_consensus_date(&self) -> Option<SystemTime> {
183          self.last_consensus_published
184      }
185  
186      /// Tell the directory client that we should abort the request early if the
187      /// directory's clock skew exceeds certain limits.
188      ///
189      /// The `max_fast` parameter is the most fast that we're willing to be with
190      /// respect to the directory (or in other words, the most slow that we're
191      /// willing to let the directory be with respect to us).
192      ///
193      /// The `max_slow` parameter is the most _slow_ that we're willing to be with
194      /// respect to the directory ((or in other words, the most slow that we're
195      /// willing to let the directory be with respect to us).
196      pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
197          self.skew_limit = Some(SkewLimit { max_fast, max_slow });
198      }
199  }
200  
201  /// Convert a list of digests in some format to a string, for use in a request
202  ///
203  /// The digests `DL` will be sorted, converted to strings with `EF`,
204  /// separated with `sep`, and returned as an fresh `String`.
205  ///
206  /// If the digests list is empty, returns None instead.
207  //
208  // In principle this ought to be doable with much less allocating,
209  // starting with hex::encode etc.
210  fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
211  where
212      DL: IntoIterator<Item = &'d D> + 'd,
213      D: PartialOrd + Ord + 'd,
214      EF: Fn(&'d D) -> String,
215  {
216      let mut digests = digests.into_iter().collect_vec();
217      if digests.is_empty() {
218          return None;
219      }
220      digests.sort_unstable();
221      let ids = digests.into_iter().map(encode).map(Cow::Owned);
222      // name collision with unstable Iterator::intersperse
223      // https://github.com/rust-lang/rust/issues/48919
224      let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
225      Some(ids)
226  }
227  
228  impl Default for ConsensusRequest {
229      fn default() -> Self {
230          Self::new(ConsensusFlavor::Microdesc)
231      }
232  }
233  
234  impl sealed::RequestableInner for ConsensusRequest {
235      fn make_request(&self) -> Result<http::Request<String>> {
236          // Build the URL.
237          let mut uri = "/tor/status-vote/current/consensus".to_string();
238          match self.flavor {
239              ConsensusFlavor::Ns => {}
240              flav => {
241                  uri.push('-');
242                  uri.push_str(flav.name());
243              }
244          }
245          let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
246          if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
247              // With authorities, "../consensus/<F1>+<F2>+<F3>.z"
248              uri.push('/');
249              uri.push_str(&ids);
250          }
251          // Without authorities, "../consensus-microdesc.z"
252          uri.push_str(".z");
253  
254          let mut req = http::Request::builder().method("GET").uri(uri);
255          req = add_common_headers(req, self.anonymized());
256  
257          // Possibly, add an if-modified-since header.
258          if let Some(when) = self.last_consensus_date() {
259              req = req.header(
260                  http::header::IF_MODIFIED_SINCE,
261                  httpdate::fmt_http_date(when),
262              );
263          }
264  
265          // Possibly, add an X-Or-Diff-From-Consensus header.
266          if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
267              req = req.header("X-Or-Diff-From-Consensus", &ids);
268          }
269  
270          Ok(req.body(String::new())?)
271      }
272  
273      fn partial_response_body_ok(&self) -> bool {
274          false
275      }
276  
277      fn check_circuit(&self, circ: &ClientCirc) -> Result<()> {
278          use tor_proto::ClockSkew::*;
279          // This is the clock skew _according to the directory_.
280          let skew = circ.channel().clock_skew();
281          match (&self.skew_limit, &skew) {
282              (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
283                  Err(RequestError::TooMuchClockSkew)
284              }
285              (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
286                  Err(RequestError::TooMuchClockSkew)
287              }
288              (_, _) => Ok(()),
289          }
290      }
291  
292      fn anonymized(&self) -> AnonymizedRequest {
293          AnonymizedRequest::Direct
294      }
295  }
296  
297  /// A request for one or more authority certificates.
298  #[derive(Debug, Clone, Default)]
299  pub struct AuthCertRequest {
300      /// The identity/signing keys of the certificates we want.
301      ids: Vec<AuthCertKeyIds>,
302  }
303  
304  impl AuthCertRequest {
305      /// Create a new request, asking for no authority certificates.
306      pub fn new() -> Self {
307          AuthCertRequest::default()
308      }
309  
310      /// Add `ids` to the list of certificates we're asking for.
311      pub fn push(&mut self, ids: AuthCertKeyIds) {
312          self.ids.push(ids);
313      }
314  
315      /// Return a list of the keys that we're asking for.
316      pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
317          self.ids.iter()
318      }
319  }
320  
321  impl sealed::RequestableInner for AuthCertRequest {
322      fn make_request(&self) -> Result<http::Request<String>> {
323          if self.ids.is_empty() {
324              return Err(RequestError::EmptyRequest);
325          }
326          let mut ids = self.ids.clone();
327          ids.sort_unstable();
328  
329          let ids: Vec<String> = ids
330              .iter()
331              .map(|id| {
332                  format!(
333                      "{}-{}",
334                      hex::encode(id.id_fingerprint.as_bytes()),
335                      hex::encode(id.sk_fingerprint.as_bytes())
336                  )
337              })
338              .collect();
339  
340          let uri = format!("/tor/keys/fp-sk/{}.z", &ids.join("+"));
341  
342          let req = http::Request::builder().method("GET").uri(uri);
343          let req = add_common_headers(req, self.anonymized());
344  
345          Ok(req.body(String::new())?)
346      }
347  
348      fn partial_response_body_ok(&self) -> bool {
349          self.ids.len() > 1
350      }
351  
352      fn max_response_len(&self) -> usize {
353          // TODO: Pick a more principled number; I just made this one up.
354          self.ids.len().saturating_mul(16 * 1024)
355      }
356  
357      fn anonymized(&self) -> AnonymizedRequest {
358          AnonymizedRequest::Direct
359      }
360  }
361  
362  impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
363      fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
364          let mut req = Self::new();
365          for i in iter {
366              req.push(i);
367          }
368          req
369      }
370  }
371  
372  /// A request for one or more microdescriptors
373  #[derive(Debug, Clone, Default)]
374  pub struct MicrodescRequest {
375      /// The SHA256 digests of the microdescriptors we want.
376      digests: Vec<MdDigest>,
377  }
378  
379  impl MicrodescRequest {
380      /// Construct a request for no microdescriptors.
381      pub fn new() -> Self {
382          MicrodescRequest::default()
383      }
384      /// Add `d` to the list of microdescriptors we want to request.
385      pub fn push(&mut self, d: MdDigest) {
386          self.digests.push(d);
387      }
388  
389      /// Return a list of the microdescriptor digests that we're asking for.
390      pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
391          self.digests.iter()
392      }
393  }
394  
395  impl sealed::RequestableInner for MicrodescRequest {
396      fn make_request(&self) -> Result<http::Request<String>> {
397          let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
398          let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
399              .ok_or(RequestError::EmptyRequest)?;
400          let uri = format!("/tor/micro/d/{}.z", &ids);
401          let req = http::Request::builder().method("GET").uri(uri);
402  
403          let req = add_common_headers(req, self.anonymized());
404  
405          Ok(req.body(String::new())?)
406      }
407  
408      fn partial_response_body_ok(&self) -> bool {
409          self.digests.len() > 1
410      }
411  
412      fn max_response_len(&self) -> usize {
413          // TODO: Pick a more principled number; I just made this one up.
414          self.digests.len().saturating_mul(8 * 1024)
415      }
416  
417      fn anonymized(&self) -> AnonymizedRequest {
418          AnonymizedRequest::Direct
419      }
420  }
421  
422  impl FromIterator<MdDigest> for MicrodescRequest {
423      fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
424          let mut req = Self::new();
425          for i in iter {
426              req.push(i);
427          }
428          req
429      }
430  }
431  
432  /// A request for one, many or all router descriptors.
433  #[derive(Debug, Clone)]
434  #[cfg(feature = "routerdesc")]
435  pub struct RouterDescRequest {
436      /// The descriptors to request.
437      requested_descriptors: RequestedDescs,
438  }
439  
440  /// Tracks the different router descriptor types.
441  #[derive(Debug, Clone)]
442  #[cfg(feature = "routerdesc")]
443  enum RequestedDescs {
444      /// If this is set, we just ask for all the descriptors.
445      AllDescriptors,
446      /// A list of digests to download.
447      Digests(Vec<RdDigest>),
448  }
449  
450  #[cfg(feature = "routerdesc")]
451  impl Default for RouterDescRequest {
452      fn default() -> Self {
453          RouterDescRequest {
454              requested_descriptors: RequestedDescs::Digests(Vec::new()),
455          }
456      }
457  }
458  
459  #[cfg(feature = "routerdesc")]
460  impl RouterDescRequest {
461      /// Construct a request for all router descriptors.
462      pub fn all() -> Self {
463          RouterDescRequest {
464              requested_descriptors: RequestedDescs::AllDescriptors,
465          }
466      }
467      /// Construct a new empty request.
468      pub fn new() -> Self {
469          RouterDescRequest::default()
470      }
471  }
472  
473  #[cfg(feature = "routerdesc")]
474  impl sealed::RequestableInner for RouterDescRequest {
475      fn make_request(&self) -> Result<http::Request<String>> {
476          let mut uri = "/tor/server/".to_string();
477  
478          match self.requested_descriptors {
479              RequestedDescs::Digests(ref digests) => {
480                  uri.push_str("d/");
481                  let ids = digest_list_stringify(digests, hex::encode, "+")
482                      .ok_or(RequestError::EmptyRequest)?;
483                  uri.push_str(&ids);
484              }
485              RequestedDescs::AllDescriptors => {
486                  uri.push_str("all");
487              }
488          }
489  
490          uri.push_str(".z");
491  
492          let req = http::Request::builder().method("GET").uri(uri);
493          let req = add_common_headers(req, self.anonymized());
494  
495          Ok(req.body(String::new())?)
496      }
497  
498      fn partial_response_body_ok(&self) -> bool {
499          match self.requested_descriptors {
500              RequestedDescs::Digests(ref digests) => digests.len() > 1,
501              RequestedDescs::AllDescriptors => true,
502          }
503      }
504  
505      fn max_response_len(&self) -> usize {
506          // TODO: Pick a more principled number; I just made these up.
507          match self.requested_descriptors {
508              RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
509              RequestedDescs::AllDescriptors => 64 * 1024 * 1024, // big but not impossible
510          }
511      }
512  
513      fn anonymized(&self) -> AnonymizedRequest {
514          AnonymizedRequest::Direct
515      }
516  }
517  
518  #[cfg(feature = "routerdesc")]
519  impl FromIterator<RdDigest> for RouterDescRequest {
520      fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
521          let digests = iter.into_iter().collect();
522  
523          RouterDescRequest {
524              requested_descriptors: RequestedDescs::Digests(digests),
525          }
526      }
527  }
528  
529  /// A request for the descriptor of whatever relay we are making the request to
530  #[derive(Debug, Clone, Default)]
531  #[cfg(feature = "routerdesc")]
532  #[non_exhaustive]
533  pub struct RoutersOwnDescRequest {}
534  
535  #[cfg(feature = "routerdesc")]
536  impl RoutersOwnDescRequest {
537      /// Construct a new request.
538      pub fn new() -> Self {
539          RoutersOwnDescRequest::default()
540      }
541  }
542  
543  #[cfg(feature = "routerdesc")]
544  impl sealed::RequestableInner for RoutersOwnDescRequest {
545      fn make_request(&self) -> Result<http::Request<String>> {
546          let uri = "/tor/server/authority.z";
547          let req = http::Request::builder().method("GET").uri(uri);
548          let req = add_common_headers(req, self.anonymized());
549  
550          Ok(req.body(String::new())?)
551      }
552  
553      fn partial_response_body_ok(&self) -> bool {
554          false
555      }
556  
557      fn anonymized(&self) -> AnonymizedRequest {
558          AnonymizedRequest::Direct
559      }
560  }
561  
562  /// A request to download a hidden service descriptor
563  ///
564  /// rend-spec-v3 2.2.6
565  #[derive(Debug, Clone)]
566  #[cfg(feature = "hs-client")]
567  pub struct HsDescDownloadRequest {
568      /// What hidden service?
569      hsid: HsBlindId,
570      /// What's the largest acceptable response length?
571      max_len: usize,
572  }
573  
574  #[cfg(feature = "hs-client")]
575  impl HsDescDownloadRequest {
576      /// Construct a request for a single onion service descriptor by its
577      /// blinded ID.
578      pub fn new(hsid: HsBlindId) -> Self {
579          /// Default maximum length to use when we have no other information.
580          const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
581          HsDescDownloadRequest {
582              hsid,
583              max_len: DEFAULT_HSDESC_MAX_LEN,
584          }
585      }
586  
587      /// Set the maximum acceptable response length.
588      pub fn set_max_len(&mut self, max_len: usize) {
589          self.max_len = max_len;
590      }
591  }
592  
593  #[cfg(feature = "hs-client")]
594  impl sealed::RequestableInner for HsDescDownloadRequest {
595      fn make_request(&self) -> Result<http::Request<String>> {
596          let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
597          // We hardcode version 3 here; if we ever have a v4 onion service
598          // descriptor, it will need a different kind of Request.
599          let uri = format!("/tor/hs/3/{}", hsid);
600          let req = http::Request::builder().method("GET").uri(uri);
601          let req = add_common_headers(req, self.anonymized());
602          Ok(req.body(String::new())?)
603      }
604  
605      fn partial_response_body_ok(&self) -> bool {
606          false
607      }
608  
609      fn max_response_len(&self) -> usize {
610          self.max_len
611      }
612  
613      fn anonymized(&self) -> AnonymizedRequest {
614          AnonymizedRequest::Anonymized
615      }
616  }
617  
618  /// A request to upload a hidden service descriptor
619  ///
620  /// rend-spec-v3 2.2.6
621  #[derive(Debug, Clone)]
622  #[cfg(feature = "hs-service")]
623  pub struct HsDescUploadRequest(String);
624  
625  #[cfg(feature = "hs-service")]
626  impl HsDescUploadRequest {
627      /// Construct a request for uploading a single onion service descriptor.
628      pub fn new(hsdesc: String) -> Self {
629          HsDescUploadRequest(hsdesc)
630      }
631  }
632  
633  #[cfg(feature = "hs-service")]
634  impl sealed::RequestableInner for HsDescUploadRequest {
635      fn make_request(&self) -> Result<http::Request<String>> {
636          /// The upload URI.
637          const URI: &str = "/tor/hs/3/publish";
638  
639          let req = http::Request::builder().method("POST").uri(URI);
640          let req = add_common_headers(req, self.anonymized());
641          Ok(req.body(self.0.clone())?)
642      }
643  
644      fn partial_response_body_ok(&self) -> bool {
645          false
646      }
647  
648      fn max_response_len(&self) -> usize {
649          // We expect the response _body_ to be empty, but the max_response_len
650          // is not zero because it represents the _total_ length of the response
651          // (which includes the length of the status line and headers).
652          //
653          // A real Tor POST response will always be less than that length, which
654          // will fit into 3 DATA messages at most. (The reply will be a single
655          // HTTP line, followed by a Date header.)
656          1024
657      }
658  
659      fn anonymized(&self) -> AnonymizedRequest {
660          AnonymizedRequest::Anonymized
661      }
662  }
663  
664  /// Encodings that all Tor clients support.
665  const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
666  
667  /// List all the encodings we accept
668  fn all_encodings() -> String {
669      #[allow(unused_mut)]
670      let mut encodings = UNIVERSAL_ENCODINGS.to_string();
671      #[cfg(feature = "xz")]
672      {
673          encodings += ", x-tor-lzma";
674      }
675      #[cfg(feature = "zstd")]
676      {
677          encodings += ", x-zstd";
678      }
679  
680      encodings
681  }
682  
683  /// Add commonly used headers to the HTTP request.
684  ///
685  /// (Right now, this is only Accept-Encoding.)
686  fn add_common_headers(
687      req: http::request::Builder,
688      anon: AnonymizedRequest,
689  ) -> http::request::Builder {
690      // TODO: gzip, brotli
691      match anon {
692          AnonymizedRequest::Anonymized => {
693              // In an anonymized request, we do not admit to supporting any
694              // encoding besides those that are always available.
695              req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
696          }
697          AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
698      }
699  }
700  
701  #[cfg(test)]
702  mod test {
703      // @@ begin test lint list maintained by maint/add_warning @@
704      #![allow(clippy::bool_assert_comparison)]
705      #![allow(clippy::clone_on_copy)]
706      #![allow(clippy::dbg_macro)]
707      #![allow(clippy::mixed_attributes_style)]
708      #![allow(clippy::print_stderr)]
709      #![allow(clippy::print_stdout)]
710      #![allow(clippy::single_char_pattern)]
711      #![allow(clippy::unwrap_used)]
712      #![allow(clippy::unchecked_duration_subtraction)]
713      #![allow(clippy::useless_vec)]
714      #![allow(clippy::needless_pass_by_value)]
715      //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
716      use super::sealed::RequestableInner;
717      use super::*;
718  
719      #[test]
720      fn test_md_request() -> Result<()> {
721          let d1 = b"This is a testing digest. it isn";
722          let d2 = b"'t actually SHA-256.............";
723  
724          let mut req = MicrodescRequest::default();
725          req.push(*d1);
726          assert!(!req.partial_response_body_ok());
727          req.push(*d2);
728          assert!(req.partial_response_body_ok());
729          assert_eq!(req.max_response_len(), 16 << 10);
730  
731          let req = crate::util::encode_request(&req.make_request()?);
732  
733          assert_eq!(req,
734                     format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
735  
736          // Try it with FromIterator, and use some accessors.
737          let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
738          let ds: Vec<_> = req2.digests().collect();
739          assert_eq!(ds, vec![d1, d2]);
740          let req2 = crate::util::encode_request(&req2.make_request()?);
741          assert_eq!(req, req2);
742  
743          Ok(())
744      }
745  
746      #[test]
747      fn test_cert_request() -> Result<()> {
748          let d1 = b"This is a testing dn";
749          let d2 = b"'t actually SHA-256.";
750          let key1 = AuthCertKeyIds {
751              id_fingerprint: (*d1).into(),
752              sk_fingerprint: (*d2).into(),
753          };
754  
755          let d3 = b"blah blah blah 1 2 3";
756          let d4 = b"I like pizza from Na";
757          let key2 = AuthCertKeyIds {
758              id_fingerprint: (*d3).into(),
759              sk_fingerprint: (*d4).into(),
760          };
761  
762          let mut req = AuthCertRequest::default();
763          req.push(key1);
764          assert!(!req.partial_response_body_ok());
765          req.push(key2);
766          assert!(req.partial_response_body_ok());
767          assert_eq!(req.max_response_len(), 32 << 10);
768  
769          let keys: Vec<_> = req.keys().collect();
770          assert_eq!(keys, vec![&key1, &key2]);
771  
772          let req = crate::util::encode_request(&req.make_request()?);
773  
774          assert_eq!(req,
775                     format!("GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
776  
777          let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
778          let req2 = crate::util::encode_request(&req2.make_request()?);
779          assert_eq!(req, req2);
780  
781          Ok(())
782      }
783  
784      #[test]
785      fn test_consensus_request() -> Result<()> {
786          let d1 = RsaIdentity::from_bytes(
787              &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
788          )
789          .unwrap();
790  
791          let d2 = b"blah blah blah 12 blah blah blah";
792          let d3 = SystemTime::now();
793          let mut req = ConsensusRequest::default();
794  
795          let when = httpdate::fmt_http_date(d3);
796  
797          req.push_authority_id(d1);
798          req.push_old_consensus_digest(*d2);
799          req.set_last_consensus_date(d3);
800          assert!(!req.partial_response_body_ok());
801          assert_eq!(req.max_response_len(), (16 << 20) - 1);
802          assert_eq!(req.old_consensus_digests().next(), Some(d2));
803          assert_eq!(req.authority_ids().next(), Some(&d1));
804          assert_eq!(req.last_consensus_date(), Some(d3));
805  
806          let req = crate::util::encode_request(&req.make_request()?);
807  
808          assert_eq!(req,
809                     format!("GET /tor/status-vote/current/consensus-microdesc/03479e93ebf3ff2c58c1c9dbf2de9de9c2801b3e.z HTTP/1.0\r\naccept-encoding: {}\r\nif-modified-since: {}\r\nx-or-diff-from-consensus: 626c616820626c616820626c616820313220626c616820626c616820626c6168\r\n\r\n", all_encodings(), when));
810  
811          // Request without authorities
812          let req = ConsensusRequest::default();
813          let req = crate::util::encode_request(&req.make_request()?);
814          assert_eq!(req,
815                     format!("GET /tor/status-vote/current/consensus-microdesc.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
816  
817          Ok(())
818      }
819  
820      #[test]
821      #[cfg(feature = "routerdesc")]
822      fn test_rd_request_all() -> Result<()> {
823          let req = RouterDescRequest::all();
824          assert!(req.partial_response_body_ok());
825          assert_eq!(req.max_response_len(), 1 << 26);
826  
827          let req = crate::util::encode_request(&req.make_request()?);
828  
829          assert_eq!(
830              req,
831              format!(
832                  "GET /tor/server/all.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
833                  all_encodings()
834              )
835          );
836  
837          Ok(())
838      }
839  
840      #[test]
841      #[cfg(feature = "routerdesc")]
842      fn test_rd_request() -> Result<()> {
843          let d1 = b"at some point I got ";
844          let d2 = b"of writing in hex...";
845  
846          let mut req = RouterDescRequest::default();
847  
848          if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
849              digests.push(*d1);
850          }
851          assert!(!req.partial_response_body_ok());
852          if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
853              digests.push(*d2);
854          }
855          assert!(req.partial_response_body_ok());
856          assert_eq!(req.max_response_len(), 16 << 10);
857  
858          let req = crate::util::encode_request(&req.make_request()?);
859  
860          assert_eq!(req,
861                     format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
862  
863          // Try it with FromIterator, and use some accessors.
864          let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
865          let ds: Vec<_> = match req2.requested_descriptors {
866              RequestedDescs::Digests(ref digests) => digests.iter().collect(),
867              RequestedDescs::AllDescriptors => Vec::new(),
868          };
869          assert_eq!(ds, vec![d1, d2]);
870          let req2 = crate::util::encode_request(&req2.make_request()?);
871          assert_eq!(req, req2);
872          Ok(())
873      }
874  
875      #[test]
876      #[cfg(feature = "hs-client")]
877      fn test_hs_desc_download_request() -> Result<()> {
878          use tor_llcrypto::pk::ed25519::Ed25519Identity;
879          let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
880          let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
881          let hsid = HsBlindId::from(hsid);
882          let req = HsDescDownloadRequest::new(hsid);
883          assert!(!req.partial_response_body_ok());
884          assert_eq!(req.max_response_len(), 50 * 1000);
885  
886          let req = crate::util::encode_request(&req.make_request()?);
887  
888          assert_eq!(
889              req,
890              format!("GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", UNIVERSAL_ENCODINGS)
891          );
892  
893          Ok(())
894      }
895  }