Skip to main content

tor_dirclient/
request.rs

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