1
//! Descriptions objects for different kinds of directory requests
2
//! that we can make.
3

            
4
use tor_circmgr::ClientDirTunnel;
5
use tor_llcrypto::pk::rsa::RsaIdentity;
6
use tor_netdoc::doc::authcert::AuthCertKeyIds;
7
use tor_netdoc::doc::microdesc::MdDigest;
8
use tor_netdoc::doc::netstatus::ConsensusFlavor;
9
#[cfg(feature = "routerdesc")]
10
use tor_netdoc::doc::routerdesc::{ExtraInfoDigest, RdDigest};
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::future::Future;
21
use std::iter::FromIterator;
22
use std::pin::Pin;
23
use std::sync::Arc;
24
use std::time::{Duration, SystemTime};
25

            
26
use itertools::Itertools;
27

            
28
use crate::AnonymizedRequest;
29
use crate::body::RequestBody;
30
use crate::err::RequestError;
31

            
32
/// Declare an inaccessible public type.
33
pub(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
4
        fn max_response_len(&self) -> usize {
60
4
            (16 * 1024 * 1024) - 1
61
4
        }
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.
79
pub 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
14
    fn debug_request(&self) -> DisplayRequestable<'_, Self>
85
14
    where
86
14
        Self: Sized,
87
    {
88
14
        DisplayRequestable(self)
89
14
    }
90
}
91
impl<T: sealed::RequestableInner> Requestable for T {}
92

            
93
/// A wrapper to implement [`Requestable::debug_request`].
94
pub struct DisplayRequestable<'a, R: Requestable>(&'a R);
95

            
96
impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> {
97
14
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98
14
        write!(f, "{:?}", self.0.make_request())
99
14
    }
100
}
101

            
102
impl 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)]
126
struct 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)]
143
pub 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

            
162
impl ConsensusRequest {
163
    /// Create a new request for a consensus directory document.
164
340
    pub fn new(flavor: ConsensusFlavor) -> Self {
165
340
        ConsensusRequest {
166
340
            flavor,
167
340
            authority_ids: Vec::new(),
168
340
            last_consensus_published: None,
169
340
            last_consensus_sha3_256: Vec::new(),
170
340
            skew_limit: None,
171
340
        }
172
340
    }
173

            
174
    /// Add `id` to the list of authorities that this request should
175
    /// say we believe in.
176
2
    pub fn push_authority_id(&mut self, id: RsaIdentity) {
177
2
        self.authority_ids.push(id);
178
2
    }
179

            
180
    /// Add `d` to the list of consensus digests this request should
181
    /// say we already have.
182
86
    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
183
86
        self.last_consensus_sha3_256.push(d);
184
86
    }
185

            
186
    /// Set the publication time we should say we have for our last
187
    /// consensus to `when`.
188
170
    pub fn set_last_consensus_date(&mut self, when: SystemTime) {
189
170
        self.last_consensus_published = Some(when);
190
170
    }
191

            
192
    /// Return a slice of the consensus digests that we're saying we
193
    /// already have.
194
170
    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
195
170
        self.last_consensus_sha3_256.iter()
196
170
    }
197

            
198
    /// Return an iterator of the authority identities that this request
199
    /// is saying we believe in.
200
2
    pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
201
2
        self.authority_ids.iter()
202
2
    }
203

            
204
    /// Return the date we're reporting for our most recent consensus.
205
342
    pub fn last_consensus_date(&self) -> Option<SystemTime> {
206
342
        self.last_consensus_published
207
342
    }
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
168
    pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
220
168
        self.skew_limit = Some(SkewLimit { max_fast, max_slow });
221
168
    }
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.
233
116
fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
234
116
where
235
116
    DL: IntoIterator<Item = &'d D> + 'd,
236
116
    D: PartialOrd + Ord + 'd,
237
116
    EF: Fn(&'d D) -> String,
238
{
239
116
    let mut digests = digests.into_iter().collect_vec();
240
116
    if digests.is_empty() {
241
88
        return None;
242
28
    }
243
28
    digests.sort_unstable();
244
28
    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
28
    let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
248
28
    Some(ids)
249
116
}
250

            
251
impl Default for ConsensusRequest {
252
4
    fn default() -> Self {
253
4
        Self::new(ConsensusFlavor::Microdesc)
254
4
    }
255
}
256

            
257
impl sealed::RequestableInner for ConsensusRequest {
258
46
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
259
        // Build the URL.
260
46
        let mut uri = "/tor/status-vote/current/consensus".to_string();
261
46
        match self.flavor {
262
42
            ConsensusFlavor::Plain => {}
263
4
            flav => {
264
4
                uri.push('-');
265
4
                uri.push_str(flav.name());
266
4
            }
267
        }
268
47
        let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
269
46
        if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
270
2
            // With authorities, "../consensus/<F1>+<F2>+<F3>"
271
2
            uri.push('/');
272
2
            uri.push_str(&ids);
273
44
        }
274
        // Without authorities, "../consensus-microdesc"
275

            
276
46
        let mut req = http::Request::builder().method("GET").uri(uri);
277
46
        req = add_common_headers(req, self.anonymized());
278

            
279
        // Possibly, add an if-modified-since header.
280
46
        if let Some(when) = self.last_consensus_date() {
281
2
            req = req.header(
282
2
                http::header::IF_MODIFIED_SINCE,
283
2
                httpdate::fmt_http_date(when),
284
2
            );
285
44
        }
286

            
287
        // Possibly, add an X-Or-Diff-From-Consensus header.
288
46
        if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
289
2
            req = req.header("X-Or-Diff-From-Consensus", &ids);
290
44
        }
291

            
292
46
        Ok(req.body(RequestBody::default())?)
293
46
    }
294

            
295
44
    fn partial_response_body_ok(&self) -> bool {
296
44
        false
297
44
    }
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
88
    fn anonymized(&self) -> AnonymizedRequest {
321
88
        AnonymizedRequest::Direct
322
88
    }
323
}
324

            
325
/// A request for one or more authority certificates.
326
#[derive(Debug, Clone, Default)]
327
pub struct AuthCertRequest {
328
    /// The identity/signing keys of the certificates we want.
329
    ids: Vec<AuthCertKeyIds>,
330
}
331

            
332
impl AuthCertRequest {
333
    /// Create a new request, asking for no authority certificates.
334
170
    pub fn new() -> Self {
335
170
        AuthCertRequest::default()
336
170
    }
337

            
338
    /// Add `ids` to the list of certificates we're asking for.
339
512
    pub fn push(&mut self, ids: AuthCertKeyIds) {
340
512
        self.ids.push(ids);
341
512
    }
342

            
343
    /// Return a list of the keys that we're asking for.
344
128
    pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
345
128
        self.ids.iter()
346
128
    }
347
}
348

            
349
impl sealed::RequestableInner for AuthCertRequest {
350
46
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
351
46
        if self.ids.is_empty() {
352
            return Err(RequestError::EmptyRequest);
353
46
        }
354
46
        let mut ids = self.ids.clone();
355
46
        ids.sort_unstable();
356

            
357
46
        let ids: Vec<String> = ids
358
46
            .iter()
359
389
            .map(|id| {
360
386
                format!(
361
                    "{}-{}",
362
386
                    hex::encode(id.id_fingerprint.as_bytes()),
363
386
                    hex::encode(id.sk_fingerprint.as_bytes())
364
                )
365
386
            })
366
46
            .collect();
367

            
368
46
        let uri = format!("/tor/keys/fp-sk/{}", &ids.join("+"));
369

            
370
46
        let req = http::Request::builder().method("GET").uri(uri);
371
46
        let req = add_common_headers(req, self.anonymized());
372

            
373
46
        Ok(req.body(RequestBody::default())?)
374
46
    }
375

            
376
46
    fn partial_response_body_ok(&self) -> bool {
377
46
        self.ids.len() > 1
378
46
    }
379

            
380
44
    fn max_response_len(&self) -> usize {
381
        // TODO: Pick a more principled number; I just made this one up.
382
44
        self.ids.len().saturating_mul(16 * 1024)
383
44
    }
384

            
385
88
    fn anonymized(&self) -> AnonymizedRequest {
386
88
        AnonymizedRequest::Direct
387
88
    }
388
}
389

            
390
impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
391
4
    fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
392
4
        let mut req = Self::new();
393
6
        for i in iter {
394
6
            req.push(i);
395
6
        }
396
4
        req
397
4
    }
398
}
399

            
400
/// A request for one or more microdescriptors
401
#[derive(Debug, Clone, Default)]
402
pub struct MicrodescRequest {
403
    /// The SHA256 digests of the microdescriptors we want.
404
    digests: Vec<MdDigest>,
405
}
406

            
407
impl MicrodescRequest {
408
    /// Construct a request for no microdescriptors.
409
226
    pub fn new() -> Self {
410
226
        MicrodescRequest::default()
411
226
    }
412
    /// Add `d` to the list of microdescriptors we want to request.
413
42276
    pub fn push(&mut self, d: MdDigest) {
414
42276
        self.digests.push(d);
415
42276
    }
416

            
417
    /// Return a list of the microdescriptor digests that we're asking for.
418
44
    pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
419
44
        self.digests.iter()
420
44
    }
421
}
422

            
423
impl sealed::RequestableInner for MicrodescRequest {
424
18
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
425
33
        let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
426
18
        let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
427
18
            .ok_or(RequestError::EmptyRequest)?;
428
18
        let uri = format!("/tor/micro/d/{}", &ids);
429
18
        let req = http::Request::builder().method("GET").uri(uri);
430

            
431
18
        let req = add_common_headers(req, self.anonymized());
432

            
433
18
        Ok(req.body(RequestBody::default())?)
434
18
    }
435

            
436
18
    fn partial_response_body_ok(&self) -> bool {
437
18
        self.digests.len() > 1
438
18
    }
439

            
440
16
    fn max_response_len(&self) -> usize {
441
        // TODO: Pick a more principled number; I just made this one up.
442
16
        self.digests.len().saturating_mul(8 * 1024)
443
16
    }
444

            
445
32
    fn anonymized(&self) -> AnonymizedRequest {
446
32
        AnonymizedRequest::Direct
447
32
    }
448
}
449

            
450
impl FromIterator<MdDigest> for MicrodescRequest {
451
24
    fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
452
24
        let mut req = Self::new();
453
2026
        for i in iter {
454
2026
            req.push(i);
455
2026
        }
456
24
        req
457
24
    }
458
}
459

            
460
/// A request for one, many or all router descriptors.
461
#[derive(Debug, Clone)]
462
#[cfg(feature = "routerdesc")]
463
pub 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")]
471
enum 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.
480
impl Default for RouterDescRequest {
481
2
    fn default() -> Self {
482
2
        RouterDescRequest {
483
2
            requested_descriptors: RequestedDescs::Digests(Vec::new()),
484
2
        }
485
2
    }
486
}
487

            
488
#[cfg(feature = "routerdesc")]
489
impl RouterDescRequest {
490
    /// Construct a request for all router descriptors.
491
2
    pub fn all() -> Self {
492
2
        RouterDescRequest {
493
2
            requested_descriptors: RequestedDescs::AllDescriptors,
494
2
        }
495
2
    }
496
    /// Construct a new empty request.
497
    pub fn new() -> Self {
498
        RouterDescRequest::default()
499
    }
500
}
501

            
502
#[cfg(feature = "routerdesc")]
503
impl sealed::RequestableInner for RouterDescRequest {
504
6
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
505
6
        let mut uri = "/tor/server/".to_string();
506

            
507
6
        match self.requested_descriptors {
508
4
            RequestedDescs::Digests(ref digests) => {
509
4
                uri.push_str("d/");
510
4
                let ids = digest_list_stringify(digests, hex::encode, "+")
511
4
                    .ok_or(RequestError::EmptyRequest)?;
512
4
                uri.push_str(&ids);
513
            }
514
2
            RequestedDescs::AllDescriptors => {
515
2
                uri.push_str("all");
516
2
            }
517
        }
518

            
519
6
        let req = http::Request::builder().method("GET").uri(uri);
520
6
        let req = add_common_headers(req, self.anonymized());
521

            
522
6
        Ok(req.body(RequestBody::default())?)
523
6
    }
524

            
525
6
    fn partial_response_body_ok(&self) -> bool {
526
6
        match self.requested_descriptors {
527
4
            RequestedDescs::Digests(ref digests) => digests.len() > 1,
528
2
            RequestedDescs::AllDescriptors => true,
529
        }
530
6
    }
531

            
532
4
    fn max_response_len(&self) -> usize {
533
        // TODO: Pick a more principled number; I just made these up.
534
4
        match self.requested_descriptors {
535
2
            RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
536
2
            RequestedDescs::AllDescriptors => 64 * 1024 * 1024, // big but not impossible
537
        }
538
4
    }
539

            
540
6
    fn anonymized(&self) -> AnonymizedRequest {
541
6
        AnonymizedRequest::Direct
542
6
    }
543
}
544

            
545
#[cfg(feature = "routerdesc")]
546
impl FromIterator<RdDigest> for RouterDescRequest {
547
6
    fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
548
6
        let digests = iter.into_iter().collect();
549

            
550
6
        RouterDescRequest {
551
6
            requested_descriptors: RequestedDescs::Digests(digests),
552
6
        }
553
6
    }
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]
560
pub struct RoutersOwnDescRequest {}
561

            
562
#[cfg(feature = "routerdesc")]
563
impl RoutersOwnDescRequest {
564
    /// Construct a new request.
565
    pub fn new() -> Self {
566
        RoutersOwnDescRequest::default()
567
    }
568
}
569

            
570
#[cfg(feature = "routerdesc")]
571
impl 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")]
595
pub 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]
606
enum 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")]
619
impl 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")]
629
impl ExtraInfoRequest {
630
    /// Construct a request for all extra-infos.
631
2
    pub fn all() -> Self {
632
2
        Self {
633
2
            requested_extra_infos: RequestedExtraInfos::AllExtraInfos,
634
2
        }
635
2
    }
636
    /// Construct a new empty request.
637
    pub fn new() -> Self {
638
        Self::default()
639
    }
640
}
641

            
642
#[cfg(feature = "routerdesc")]
643
impl sealed::RequestableInner for ExtraInfoRequest {
644
4
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
645
4
        let mut uri = "/tor/extra/".to_string();
646

            
647
4
        match &self.requested_extra_infos {
648
2
            RequestedExtraInfos::AllExtraInfos => uri.push_str("all"),
649
2
            RequestedExtraInfos::Digests(digests) => {
650
2
                uri.push_str("d/");
651
2
                let ids = digest_list_stringify(digests, hex::encode_upper, "+")
652
2
                    .ok_or(RequestError::EmptyRequest)?;
653
2
                uri.push_str(&ids);
654
            }
655
        }
656

            
657
4
        let req = http::Request::builder().method("GET").uri(uri);
658
4
        let req = add_common_headers(req, self.anonymized());
659
4
        Ok(req.body(RequestBody::default())?)
660
4
    }
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
4
    fn anonymized(&self) -> AnonymizedRequest {
679
4
        AnonymizedRequest::Direct
680
4
    }
681
}
682

            
683
#[cfg(feature = "routerdesc")]
684
impl FromIterator<ExtraInfoDigest> for ExtraInfoRequest {
685
2
    fn from_iter<T: IntoIterator<Item = ExtraInfoDigest>>(iter: T) -> Self {
686
2
        Self {
687
2
            requested_extra_infos: RequestedExtraInfos::Digests(iter.into_iter().collect()),
688
2
        }
689
2
    }
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")]
697
pub 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")]
705
impl HsDescDownloadRequest {
706
    /// Construct a request for a single onion service descriptor by its
707
    /// blinded ID.
708
296
    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
296
        HsDescDownloadRequest {
712
296
            hsid,
713
296
            max_len: DEFAULT_HSDESC_MAX_LEN,
714
296
        }
715
296
    }
716

            
717
    /// Set the maximum acceptable response length.
718
294
    pub fn set_max_len(&mut self, max_len: usize) {
719
294
        self.max_len = max_len;
720
294
    }
721
}
722

            
723
#[cfg(feature = "hs-client")]
724
impl sealed::RequestableInner for HsDescDownloadRequest {
725
590
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
726
590
        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
590
        let uri = format!("/tor/hs/3/{}", hsid);
730
590
        let req = http::Request::builder().method("GET").uri(uri);
731
590
        let req = add_common_headers(req, self.anonymized());
732
590
        Ok(req.body(RequestBody::default())?)
733
590
    }
734

            
735
296
    fn partial_response_body_ok(&self) -> bool {
736
296
        false
737
296
    }
738

            
739
296
    fn max_response_len(&self) -> usize {
740
296
        self.max_len
741
296
    }
742

            
743
884
    fn anonymized(&self) -> AnonymizedRequest {
744
884
        AnonymizedRequest::Anonymized
745
884
    }
746
}
747

            
748
/// Define a request type for uploading a document.
749
#[allow(unused)]
750
macro_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
3360
                pub fn new(document: Arc<str>) -> Self {
780
3360
                    Self(document)
781
3360
                }
782
            }
783

            
784
            impl sealed::RequestableInner for $t {
785
3360
                fn make_request(&self) -> Result<http::Request<RequestBody>> {
786
                    /// The upload URI.
787
                    const URI: &str = $uri;
788

            
789
3360
                    let req = http::Request::builder().method("POST").uri(URI);
790
3360
                    let req = add_common_headers(req, self.anonymized());
791
3360
                    Ok(req.body(RequestBody::from(Arc::clone(&self.0)))?)
792
3360
                }
793

            
794
3360
                fn partial_response_body_ok(&self) -> bool {
795
3360
                    false
796
3360
                }
797

            
798
3360
                fn max_response_len(&self) -> usize {
799
                    $max_response_len
800
3360
                }
801

            
802
6720
                fn anonymized(&self) -> AnonymizedRequest {
803
6720
                    AnonymizedRequest::$anonymity
804
6720
                }
805
            }
806
         )*
807
    }
808
}
809

            
810
#[cfg(feature = "hs-service")]
811
upload_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")]
831
upload_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.
838
const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
839

            
840
/// List all the encodings we accept
841
136
fn all_encodings() -> String {
842
    #[allow(unused_mut)]
843
136
    let mut encodings = UNIVERSAL_ENCODINGS.to_string();
844
    #[cfg(feature = "xz")]
845
136
    {
846
136
        encodings += ", x-tor-lzma";
847
136
    }
848
    #[cfg(feature = "zstd")]
849
136
    {
850
136
        encodings += ", x-zstd";
851
136
    }
852

            
853
136
    encodings
854
136
}
855

            
856
/// Add commonly used headers to the HTTP request.
857
///
858
/// (Right now, this is only Accept-Encoding.)
859
4070
fn add_common_headers(
860
4070
    req: http::request::Builder,
861
4070
    anon: AnonymizedRequest,
862
4070
) -> http::request::Builder {
863
    // TODO: gzip, brotli
864
4070
    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
3950
            req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
869
        }
870
120
        AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
871
    }
872
4070
}
873

            
874
#[cfg(test)]
875
mod 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
        );
        // Request without authorities
        let req = ConsensusRequest::default();
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        Ok(())
    }
    #[test]
    #[cfg(feature = "routerdesc")]
    fn test_rd_request_all() -> Result<()> {
        let req = RouterDescRequest::all();
        assert!(req.partial_response_body_ok());
        assert_eq!(req.max_response_len(), 1 << 26);
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        Ok(())
    }
    #[test]
    #[cfg(feature = "routerdesc")]
    fn test_rd_request() -> Result<()> {
        let d1 = b"at some point I got ";
        let d2 = b"of writing in hex...";
        let mut req = RouterDescRequest::default();
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
            digests.push(*d1);
        }
        assert!(!req.partial_response_body_ok());
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
            digests.push(*d2);
        }
        assert!(req.partial_response_body_ok());
        assert_eq!(req.max_response_len(), 16 << 10);
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        // Try it with FromIterator, and use some accessors.
        let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
        let ds: Vec<_> = match req2.requested_descriptors {
            RequestedDescs::Digests(ref digests) => digests.iter().collect(),
            RequestedDescs::AllDescriptors => Vec::new(),
        };
        assert_eq!(ds, vec![d1, d2]);
        let req2 = crate::util::request_to_string(&req2.make_request()?);
        assert_eq!(req, req2);
        Ok(())
    }
    #[test]
    #[cfg(feature = "routerdesc")]
    fn test_extra_info_request() -> Result<()> {
        let req = ExtraInfoRequest::from_iter([[0; 20], [1; 20], [2; 20]]);
        assert_eq!(
            crate::util::request_to_string(&req.make_request()?),
            format!(
                "GET /tor/extra/d/{}+{}+{} HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                hex::encode_upper([0; 20]),
                hex::encode_upper([1; 20]),
                hex::encode_upper([2; 20]),
                all_encodings()
            )
        );
        let req = ExtraInfoRequest::all();
        assert_eq!(
            crate::util::request_to_string(&req.make_request()?),
            format!(
                "GET /tor/extra/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                all_encodings()
            )
        );
        Ok(())
    }
    #[test]
    #[cfg(feature = "hs-client")]
    fn test_hs_desc_download_request() -> Result<()> {
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
        let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
        let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
        let hsid = HsBlindId::from(hsid);
        let req = HsDescDownloadRequest::new(hsid);
        assert!(!req.partial_response_body_ok());
        assert_eq!(req.max_response_len(), 50 * 1000);
        let req = crate::util::request_to_string(&req.make_request()?);
        assert_eq!(
            req,
            format!(
                "GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
                UNIVERSAL_ENCODINGS
            )
        );
        Ok(())
    }
}