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
348
    pub fn new(flavor: ConsensusFlavor) -> Self {
165
348
        ConsensusRequest {
166
348
            flavor,
167
348
            authority_ids: Vec::new(),
168
348
            last_consensus_published: None,
169
348
            last_consensus_sha3_256: Vec::new(),
170
348
            skew_limit: None,
171
348
        }
172
348
    }
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
88
    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
183
88
        self.last_consensus_sha3_256.push(d);
184
88
    }
185

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

            
192
    /// Return a slice of the consensus digests that we're saying we
193
    /// already have.
194
174
    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
195
174
        self.last_consensus_sha3_256.iter()
196
174
    }
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
350
    pub fn last_consensus_date(&self) -> Option<SystemTime> {
206
350
        self.last_consensus_published
207
350
    }
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
172
    pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
220
172
        self.skew_limit = Some(SkewLimit { max_fast, max_slow });
221
172
    }
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
120
fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
234
120
where
235
120
    DL: IntoIterator<Item = &'d D> + 'd,
236
120
    D: PartialOrd + Ord + 'd,
237
120
    EF: Fn(&'d D) -> String,
238
{
239
120
    let mut digests = digests.into_iter().collect_vec();
240
120
    if digests.is_empty() {
241
90
        return None;
242
30
    }
243
30
    digests.sort_unstable();
244
30
    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
30
    let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
248
30
    Some(ids)
249
120
}
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
47
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
259
        // Build the URL.
260
47
        let mut uri = "/tor/status-vote/current/consensus".to_string();
261
47
        match self.flavor {
262
43
            ConsensusFlavor::Plain => {}
263
4
            flav => {
264
4
                uri.push('-');
265
4
                uri.push_str(flav.name());
266
4
            }
267
        }
268
48
        let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
269
47
        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
45
        }
274
        // Without authorities, "../consensus-microdesc"
275

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

            
279
        // Possibly, add an if-modified-since header.
280
47
        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
45
        }
286

            
287
        // Possibly, add an X-Or-Diff-From-Consensus header.
288
47
        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
45
        }
291

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

            
295
45
    fn partial_response_body_ok(&self) -> bool {
296
45
        false
297
45
    }
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
90
    fn anonymized(&self) -> AnonymizedRequest {
321
90
        AnonymizedRequest::Direct
322
90
    }
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
174
    pub fn new() -> Self {
335
174
        AuthCertRequest::default()
336
174
    }
337

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

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

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

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

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

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

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

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

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

            
385
90
    fn anonymized(&self) -> AnonymizedRequest {
386
90
        AnonymizedRequest::Direct
387
90
    }
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
233
    pub fn new() -> Self {
410
233
        MicrodescRequest::default()
411
233
    }
412
    /// Add `d` to the list of microdescriptors we want to request.
413
43284
    pub fn push(&mut self, d: MdDigest) {
414
43284
        self.digests.push(d);
415
43284
    }
416

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

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

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

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

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

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

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

            
450
impl FromIterator<MdDigest> for MicrodescRequest {
451
26
    fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
452
26
        let mut req = Self::new();
453
2028
        for i in iter {
454
2028
            req.push(i);
455
2028
        }
456
26
        req
457
26
    }
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
    All,
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::All,
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::All => {
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::All => 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::All => 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
    All,
611
    /// Download extra-infos with these SHA-1 digests.
612
    ///
613
    /// `http://<hostname>/tor/extra/d/...`
614
    Digests(Vec<ExtraInfoDigest>),
615
}
616

            
617
#[cfg(feature = "routerdesc")]
618
impl Default for ExtraInfoRequest {
619
    // TODO: This is probably not a reasonable default.
620
    fn default() -> Self {
621
        Self {
622
            requested_extra_infos: RequestedExtraInfos::Digests(Vec::new()),
623
        }
624
    }
625
}
626

            
627
#[cfg(feature = "routerdesc")]
628
impl ExtraInfoRequest {
629
    /// Construct a request for all extra-infos.
630
2
    pub fn all() -> Self {
631
2
        Self {
632
2
            requested_extra_infos: RequestedExtraInfos::All,
633
2
        }
634
2
    }
635
    /// Construct a new empty request.
636
    pub fn new() -> Self {
637
        Self::default()
638
    }
639
}
640

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

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

            
656
4
        let req = http::Request::builder().method("GET").uri(uri);
657
4
        let req = add_common_headers(req, self.anonymized());
658
4
        Ok(req.body(RequestBody::default())?)
659
4
    }
660

            
661
    fn partial_response_body_ok(&self) -> bool {
662
        match &self.requested_extra_infos {
663
            RequestedExtraInfos::Digests(digests) => digests.len() > 1,
664
            RequestedExtraInfos::All => true,
665
        }
666
    }
667

            
668
    fn max_response_len(&self) -> usize {
669
        // TODO torspec#392: Pick more principled size limits.
670
        // These were copied from the RouterDescRequest impl and doubled.
671
        match &self.requested_extra_infos {
672
            RequestedExtraInfos::Digests(digests) => digests.len().saturating_mul(16 * 1024),
673
            RequestedExtraInfos::All => 128 * 1024 * 1024,
674
        }
675
    }
676

            
677
4
    fn anonymized(&self) -> AnonymizedRequest {
678
4
        AnonymizedRequest::Direct
679
4
    }
680
}
681

            
682
#[cfg(feature = "routerdesc")]
683
impl FromIterator<ExtraInfoDigest> for ExtraInfoRequest {
684
2
    fn from_iter<T: IntoIterator<Item = ExtraInfoDigest>>(iter: T) -> Self {
685
2
        Self {
686
2
            requested_extra_infos: RequestedExtraInfos::Digests(iter.into_iter().collect()),
687
2
        }
688
2
    }
689
}
690

            
691
/// A request to download a hidden service descriptor
692
///
693
/// rend-spec-v3 2.2.6
694
#[derive(Debug, Clone)]
695
#[cfg(feature = "hs-client")]
696
pub struct HsDescDownloadRequest {
697
    /// What hidden service?
698
    hsid: HsBlindId,
699
    /// What's the largest acceptable response length?
700
    max_len: usize,
701
}
702

            
703
#[cfg(feature = "hs-client")]
704
impl HsDescDownloadRequest {
705
    /// Construct a request for a single onion service descriptor by its
706
    /// blinded ID.
707
303
    pub fn new(hsid: HsBlindId) -> Self {
708
        /// Default maximum length to use when we have no other information.
709
        const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
710
303
        HsDescDownloadRequest {
711
303
            hsid,
712
303
            max_len: DEFAULT_HSDESC_MAX_LEN,
713
303
        }
714
303
    }
715

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

            
722
#[cfg(feature = "hs-client")]
723
impl sealed::RequestableInner for HsDescDownloadRequest {
724
604
    fn make_request(&self) -> Result<http::Request<RequestBody>> {
725
604
        let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
726
        // We hardcode version 3 here; if we ever have a v4 onion service
727
        // descriptor, it will need a different kind of Request.
728
604
        let uri = format!("/tor/hs/3/{}", hsid);
729
604
        let req = http::Request::builder().method("GET").uri(uri);
730
604
        let req = add_common_headers(req, self.anonymized());
731
604
        Ok(req.body(RequestBody::default())?)
732
604
    }
733

            
734
303
    fn partial_response_body_ok(&self) -> bool {
735
303
        false
736
303
    }
737

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

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

            
747
/// Define a request type for uploading a document.
748
#[allow(unused)]
749
macro_rules! upload_request {
750
    {
751
        $(
752
            $(#[$m:meta])*
753
            pub struct $t:ident (
754
                // Does this request require anonymity?
755
                // This should be a variant of AnonymizedRequest.
756
                $anonymity:ident,
757
                // To what URI at the server should the document be posted?
758
                // This should begin with "/tor">
759
                $uri:expr,
760
                // Total maximum length of the _response_ that we'll accept.
761
                // If the response is larger than this, we'll abort the request.
762
                //
763
                // Note that expected response body for a POST is typically _empty_,
764
                // but this needs to be nonzero in order to account for
765
                // the status line and headers.
766
                $max_response_len:expr
767
            )
768
        );*
769
        $(;)?
770
    } => {
771
        $(
772
            $(#[$m])*
773
            #[derive(Clone, Debug)]
774
            pub struct $t(Arc<str>);
775

            
776
            impl $t {
777
                /// Create a new upload request
778
3440
                pub fn new(document: Arc<str>) -> Self {
779
3440
                    Self(document)
780
3440
                }
781
            }
782

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

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

            
793
3440
                fn partial_response_body_ok(&self) -> bool {
794
3440
                    false
795
3440
                }
796

            
797
3440
                fn max_response_len(&self) -> usize {
798
                    $max_response_len
799
3440
                }
800

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

            
809
#[cfg(feature = "hs-service")]
810
upload_request! {
811

            
812
    /// A request to upload a hidden service descriptor
813
    ///
814
    /// rend-spec-v3 2.2.6
815
    pub struct HsDescUploadRequest(
816
        Anonymized,
817
        "/tor/hs/3/publish",
818
        // A real Tor POST _response_ will always be less than this length, which
819
        // will fit into 3 DATA messages at most. (The reply will be a single
820
        // HTTP line, followed by a Date header.)
821
        // Do not increase this limit without thinking about side channels!
822
        //
823
        // (Note that the body will be empty, but we need to allow some space
824
        // to account for the status line and headers.)
825
        1024
826
    )
827
}
828

            
829
#[cfg(feature = "relay")]
830
upload_request! {
831
    /// A request to upload a router descriptor and optional extra-info document.
832
    pub struct UploadRouterDesc(Direct, "/tor/", 4096);
833

            
834
}
835

            
836
/// Encodings that all Tor clients support.
837
const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
838

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

            
852
140
    encodings
853
140
}
854

            
855
/// Add commonly used headers to the HTTP request.
856
///
857
/// (Right now, this is only Accept-Encoding.)
858
4168
fn add_common_headers(
859
4168
    req: http::request::Builder,
860
4168
    anon: AnonymizedRequest,
861
4168
) -> http::request::Builder {
862
    // TODO: gzip, brotli
863
4168
    match anon {
864
        AnonymizedRequest::Anonymized => {
865
            // In an anonymized request, we do not admit to supporting any
866
            // encoding besides those that are always available.
867
4044
            req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
868
        }
869
124
        AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
870
    }
871
4168
}
872

            
873
#[cfg(test)]
874
mod test {
875
    // @@ begin test lint list maintained by maint/add_warning @@
876
    #![allow(clippy::bool_assert_comparison)]
877
    #![allow(clippy::clone_on_copy)]
878
    #![allow(clippy::dbg_macro)]
879
    #![allow(clippy::mixed_attributes_style)]
880
    #![allow(clippy::print_stderr)]
881
    #![allow(clippy::print_stdout)]
882
    #![allow(clippy::single_char_pattern)]
883
    #![allow(clippy::unwrap_used)]
884
    #![allow(clippy::unchecked_time_subtraction)]
885
    #![allow(clippy::useless_vec)]
886
    #![allow(clippy::needless_pass_by_value)]
887
    #![allow(clippy::string_slice)] // See arti#2571
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::All => 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(())
    }
}