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::time::{Duration, SystemTime};
24

            
25
use itertools::Itertools;
26

            
27
use crate::AnonymizedRequest;
28
use crate::err::RequestError;
29

            
30
/// Declare an inaccessible public type.
31
pub(crate) mod sealed {
32
    use tor_circmgr::ClientDirTunnel;
33

            
34
    use super::{AnonymizedRequest, Result};
35

            
36
    use std::future::Future;
37
    use std::pin::Pin;
38

            
39
    /// Sealed trait to help implement [`Requestable`](super::Requestable): not
40
    /// visible outside this crate, so we can change its methods however we like.
41
    pub trait RequestableInner: Send + Sync {
42
        /// Build an [`http::Request`] from this Requestable, if
43
        /// it is well-formed.
44
        //
45
        // TODO: This API is a bit troublesome in how it takes &self and
46
        // returns a Request<String>.  First, most Requestables don't actually have
47
        // a body to send, and for them having an empty String in their body is a
48
        // bit silly.  Second, taking a reference to self but returning an owned
49
        // String means that we will often have to clone an internal string owned by
50
        // this Requestable instance.
51
        fn make_request(&self) -> Result<http::Request<String>>;
52

            
53
        /// Return true if partial response bodies are potentially useful.
54
        ///
55
        /// This is true for request types where we're going to be downloading
56
        /// multiple documents, and we know how to parse out the ones we wanted
57
        /// if the answer is truncated.
58
        fn partial_response_body_ok(&self) -> bool;
59

            
60
        /// Return the maximum allowable response length we'll accept for this
61
        /// request.
62
4
        fn max_response_len(&self) -> usize {
63
4
            (16 * 1024 * 1024) - 1
64
4
        }
65

            
66
        /// Return an error if there is some problem with the provided circuit that
67
        /// would keep it from being used for this request.
68
        fn check_circuit<'a>(
69
            &self,
70
            tunnel: &'a ClientDirTunnel,
71
        ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
72
            let _ = tunnel;
73
            Box::pin(async { Ok(()) })
74
        }
75

            
76
        /// Return a value to say whether this request must be anonymized.
77
        fn anonymized(&self) -> AnonymizedRequest;
78
    }
79
}
80

            
81
/// A request for an object that can be served over the Tor directory system.
82
pub trait Requestable: sealed::RequestableInner {
83
    /// Return a wrapper around this [`Requestable`] that implements `Debug`,
84
    /// and whose output shows the actual HTTP request that will be generated.
85
    ///
86
    /// The format is not guaranteed to  be stable.
87
2
    fn debug_request(&self) -> DisplayRequestable<'_, Self>
88
2
    where
89
2
        Self: Sized,
90
    {
91
2
        DisplayRequestable(self)
92
2
    }
93
}
94
impl<T: sealed::RequestableInner> Requestable for T {}
95

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

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

            
105
/// How much clock skew do we allow in the distance between the directory
106
/// cache's clock and our own?
107
///
108
///  If we find more skew than this, we end the
109
/// request early, on the theory that the directory will not tell us any
110
/// information we'd accept.
111
#[derive(Clone, Debug)]
112
struct SkewLimit {
113
    /// We refuse to proceed if the directory says we are more fast than this.
114
    ///
115
    /// (This is equivalent to deciding that, from our perspective, the
116
    /// directory is at least this slow.)
117
    max_fast: Duration,
118

            
119
    /// We refuse to proceed if the directory says that we are more slow than
120
    /// this.
121
    ///
122
    /// (This is equivalent to deciding that, from our perspective, the
123
    /// directory is at least this fast.)
124
    max_slow: Duration,
125
}
126

            
127
/// A Request for a consensus directory.
128
#[derive(Debug, Clone)]
129
pub struct ConsensusRequest {
130
    /// What flavor of consensus are we asking for?  Right now, only
131
    /// "microdesc" and "ns" are supported.
132
    flavor: ConsensusFlavor,
133
    /// A list of the authority identities that we believe in.  We tell the
134
    /// directory cache only to give us a consensus if it is signed by enough
135
    /// of these authorities.
136
    authority_ids: Vec<RsaIdentity>,
137
    /// The publication time of the most recent consensus we have.  Used to
138
    /// generate an If-Modified-Since header so that we don't get a document
139
    /// we already have.
140
    last_consensus_published: Option<SystemTime>,
141
    /// A set of SHA3-256 digests of the _signed portion_ of consensuses we have.
142
    /// Used to declare what diffs we would accept.
143
    last_consensus_sha3_256: Vec<[u8; 32]>,
144
    /// If present, the largest amount of clock skew to allow between ourself and a directory cache.
145
    skew_limit: Option<SkewLimit>,
146
}
147

            
148
impl ConsensusRequest {
149
    /// Create a new request for a consensus directory document.
150
332
    pub fn new(flavor: ConsensusFlavor) -> Self {
151
332
        ConsensusRequest {
152
332
            flavor,
153
332
            authority_ids: Vec::new(),
154
332
            last_consensus_published: None,
155
332
            last_consensus_sha3_256: Vec::new(),
156
332
            skew_limit: None,
157
332
        }
158
332
    }
159

            
160
    /// Add `id` to the list of authorities that this request should
161
    /// say we believe in.
162
2
    pub fn push_authority_id(&mut self, id: RsaIdentity) {
163
2
        self.authority_ids.push(id);
164
2
    }
165

            
166
    /// Add `d` to the list of consensus digests this request should
167
    /// say we already have.
168
84
    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
169
84
        self.last_consensus_sha3_256.push(d);
170
84
    }
171

            
172
    /// Set the publication time we should say we have for our last
173
    /// consensus to `when`.
174
166
    pub fn set_last_consensus_date(&mut self, when: SystemTime) {
175
166
        self.last_consensus_published = Some(when);
176
166
    }
177

            
178
    /// Return a slice of the consensus digests that we're saying we
179
    /// already have.
180
166
    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
181
166
        self.last_consensus_sha3_256.iter()
182
166
    }
183

            
184
    /// Return an iterator of the authority identities that this request
185
    /// is saying we believe in.
186
2
    pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
187
2
        self.authority_ids.iter()
188
2
    }
189

            
190
    /// Return the date we're reporting for our most recent consensus.
191
334
    pub fn last_consensus_date(&self) -> Option<SystemTime> {
192
334
        self.last_consensus_published
193
334
    }
194

            
195
    /// Tell the directory client that we should abort the request early if the
196
    /// directory's clock skew exceeds certain limits.
197
    ///
198
    /// The `max_fast` parameter is the most fast that we're willing to be with
199
    /// respect to the directory (or in other words, the most slow that we're
200
    /// willing to let the directory be with respect to us).
201
    ///
202
    /// The `max_slow` parameter is the most _slow_ that we're willing to be with
203
    /// respect to the directory ((or in other words, the most slow that we're
204
    /// willing to let the directory be with respect to us).
205
164
    pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
206
164
        self.skew_limit = Some(SkewLimit { max_fast, max_slow });
207
164
    }
208
}
209

            
210
/// Convert a list of digests in some format to a string, for use in a request
211
///
212
/// The digests `DL` will be sorted, converted to strings with `EF`,
213
/// separated with `sep`, and returned as an fresh `String`.
214
///
215
/// If the digests list is empty, returns None instead.
216
//
217
// In principle this ought to be doable with much less allocating,
218
// starting with hex::encode etc.
219
114
fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
220
114
where
221
114
    DL: IntoIterator<Item = &'d D> + 'd,
222
114
    D: PartialOrd + Ord + 'd,
223
114
    EF: Fn(&'d D) -> String,
224
{
225
114
    let mut digests = digests.into_iter().collect_vec();
226
114
    if digests.is_empty() {
227
86
        return None;
228
28
    }
229
28
    digests.sort_unstable();
230
28
    let ids = digests.into_iter().map(encode).map(Cow::Owned);
231
    // name collision with unstable Iterator::intersperse
232
    // https://github.com/rust-lang/rust/issues/48919
233
28
    let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
234
28
    Some(ids)
235
114
}
236

            
237
impl Default for ConsensusRequest {
238
4
    fn default() -> Self {
239
4
        Self::new(ConsensusFlavor::Microdesc)
240
4
    }
241
}
242

            
243
impl sealed::RequestableInner for ConsensusRequest {
244
45
    fn make_request(&self) -> Result<http::Request<String>> {
245
        // Build the URL.
246
45
        let mut uri = "/tor/status-vote/current/consensus".to_string();
247
45
        match self.flavor {
248
41
            ConsensusFlavor::Plain => {}
249
4
            flav => {
250
4
                uri.push('-');
251
4
                uri.push_str(flav.name());
252
4
            }
253
        }
254
46
        let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
255
45
        if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
256
2
            // With authorities, "../consensus/<F1>+<F2>+<F3>"
257
2
            uri.push('/');
258
2
            uri.push_str(&ids);
259
43
        }
260
        // Without authorities, "../consensus-microdesc"
261

            
262
45
        let mut req = http::Request::builder().method("GET").uri(uri);
263
45
        req = add_common_headers(req, self.anonymized());
264

            
265
        // Possibly, add an if-modified-since header.
266
45
        if let Some(when) = self.last_consensus_date() {
267
2
            req = req.header(
268
2
                http::header::IF_MODIFIED_SINCE,
269
2
                httpdate::fmt_http_date(when),
270
2
            );
271
43
        }
272

            
273
        // Possibly, add an X-Or-Diff-From-Consensus header.
274
45
        if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
275
2
            req = req.header("X-Or-Diff-From-Consensus", &ids);
276
43
        }
277

            
278
45
        Ok(req.body(String::new())?)
279
45
    }
280

            
281
43
    fn partial_response_body_ok(&self) -> bool {
282
43
        false
283
43
    }
284

            
285
    fn check_circuit<'a>(
286
        &self,
287
        tunnel: &'a ClientDirTunnel,
288
    ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
289
        let skew_limit = self.skew_limit.clone();
290
        Box::pin(async move {
291
            use tor_proto::ClockSkew::*;
292
            // This is the clock skew _according to the directory_.
293
            let skew = tunnel.first_hop_clock_skew().await?;
294
            match (&skew_limit, &skew) {
295
                (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
296
                    Err(RequestError::TooMuchClockSkew)
297
                }
298
                (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
299
                    Err(RequestError::TooMuchClockSkew)
300
                }
301
                (_, _) => Ok(()),
302
            }
303
        })
304
    }
305

            
306
86
    fn anonymized(&self) -> AnonymizedRequest {
307
86
        AnonymizedRequest::Direct
308
86
    }
309
}
310

            
311
/// A request for one or more authority certificates.
312
#[derive(Debug, Clone, Default)]
313
pub struct AuthCertRequest {
314
    /// The identity/signing keys of the certificates we want.
315
    ids: Vec<AuthCertKeyIds>,
316
}
317

            
318
impl AuthCertRequest {
319
    /// Create a new request, asking for no authority certificates.
320
166
    pub fn new() -> Self {
321
166
        AuthCertRequest::default()
322
166
    }
323

            
324
    /// Add `ids` to the list of certificates we're asking for.
325
500
    pub fn push(&mut self, ids: AuthCertKeyIds) {
326
500
        self.ids.push(ids);
327
500
    }
328

            
329
    /// Return a list of the keys that we're asking for.
330
125
    pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
331
125
        self.ids.iter()
332
125
    }
333
}
334

            
335
impl sealed::RequestableInner for AuthCertRequest {
336
45
    fn make_request(&self) -> Result<http::Request<String>> {
337
45
        if self.ids.is_empty() {
338
            return Err(RequestError::EmptyRequest);
339
45
        }
340
45
        let mut ids = self.ids.clone();
341
45
        ids.sort_unstable();
342

            
343
45
        let ids: Vec<String> = ids
344
45
            .iter()
345
380
            .map(|id| {
346
377
                format!(
347
377
                    "{}-{}",
348
377
                    hex::encode(id.id_fingerprint.as_bytes()),
349
377
                    hex::encode(id.sk_fingerprint.as_bytes())
350
                )
351
377
            })
352
45
            .collect();
353

            
354
45
        let uri = format!("/tor/keys/fp-sk/{}", &ids.join("+"));
355

            
356
45
        let req = http::Request::builder().method("GET").uri(uri);
357
45
        let req = add_common_headers(req, self.anonymized());
358

            
359
45
        Ok(req.body(String::new())?)
360
45
    }
361

            
362
45
    fn partial_response_body_ok(&self) -> bool {
363
45
        self.ids.len() > 1
364
45
    }
365

            
366
43
    fn max_response_len(&self) -> usize {
367
        // TODO: Pick a more principled number; I just made this one up.
368
43
        self.ids.len().saturating_mul(16 * 1024)
369
43
    }
370

            
371
86
    fn anonymized(&self) -> AnonymizedRequest {
372
86
        AnonymizedRequest::Direct
373
86
    }
374
}
375

            
376
impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
377
4
    fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
378
4
        let mut req = Self::new();
379
10
        for i in iter {
380
6
            req.push(i);
381
6
        }
382
4
        req
383
4
    }
384
}
385

            
386
/// A request for one or more microdescriptors
387
#[derive(Debug, Clone, Default)]
388
pub struct MicrodescRequest {
389
    /// The SHA256 digests of the microdescriptors we want.
390
    digests: Vec<MdDigest>,
391
}
392

            
393
impl MicrodescRequest {
394
    /// Construct a request for no microdescriptors.
395
221
    pub fn new() -> Self {
396
221
        MicrodescRequest::default()
397
221
    }
398
    /// Add `d` to the list of microdescriptors we want to request.
399
41270
    pub fn push(&mut self, d: MdDigest) {
400
41270
        self.digests.push(d);
401
41270
    }
402

            
403
    /// Return a list of the microdescriptor digests that we're asking for.
404
43
    pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
405
43
        self.digests.iter()
406
43
    }
407
}
408

            
409
impl sealed::RequestableInner for MicrodescRequest {
410
18
    fn make_request(&self) -> Result<http::Request<String>> {
411
33
        let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
412
18
        let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
413
18
            .ok_or(RequestError::EmptyRequest)?;
414
18
        let uri = format!("/tor/micro/d/{}", &ids);
415
18
        let req = http::Request::builder().method("GET").uri(uri);
416

            
417
18
        let req = add_common_headers(req, self.anonymized());
418

            
419
18
        Ok(req.body(String::new())?)
420
18
    }
421

            
422
18
    fn partial_response_body_ok(&self) -> bool {
423
18
        self.digests.len() > 1
424
18
    }
425

            
426
16
    fn max_response_len(&self) -> usize {
427
        // TODO: Pick a more principled number; I just made this one up.
428
16
        self.digests.len().saturating_mul(8 * 1024)
429
16
    }
430

            
431
32
    fn anonymized(&self) -> AnonymizedRequest {
432
32
        AnonymizedRequest::Direct
433
32
    }
434
}
435

            
436
impl FromIterator<MdDigest> for MicrodescRequest {
437
24
    fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
438
24
        let mut req = Self::new();
439
2050
        for i in iter {
440
2026
            req.push(i);
441
2026
        }
442
24
        req
443
24
    }
444
}
445

            
446
/// A request for one, many or all router descriptors.
447
#[derive(Debug, Clone)]
448
#[cfg(feature = "routerdesc")]
449
pub struct RouterDescRequest {
450
    /// The descriptors to request.
451
    requested_descriptors: RequestedDescs,
452
}
453

            
454
/// Tracks the different router descriptor types.
455
#[derive(Debug, Clone)]
456
#[cfg(feature = "routerdesc")]
457
enum RequestedDescs {
458
    /// If this is set, we just ask for all the descriptors.
459
    AllDescriptors,
460
    /// A list of digests to download.
461
    Digests(Vec<RdDigest>),
462
}
463

            
464
#[cfg(feature = "routerdesc")]
465
// TODO: This is probably not a reasonable default.
466
impl Default for RouterDescRequest {
467
2
    fn default() -> Self {
468
2
        RouterDescRequest {
469
2
            requested_descriptors: RequestedDescs::Digests(Vec::new()),
470
2
        }
471
2
    }
472
}
473

            
474
#[cfg(feature = "routerdesc")]
475
impl RouterDescRequest {
476
    /// Construct a request for all router descriptors.
477
2
    pub fn all() -> Self {
478
2
        RouterDescRequest {
479
2
            requested_descriptors: RequestedDescs::AllDescriptors,
480
2
        }
481
2
    }
482
    /// Construct a new empty request.
483
    pub fn new() -> Self {
484
        RouterDescRequest::default()
485
    }
486
}
487

            
488
#[cfg(feature = "routerdesc")]
489
impl sealed::RequestableInner for RouterDescRequest {
490
6
    fn make_request(&self) -> Result<http::Request<String>> {
491
6
        let mut uri = "/tor/server/".to_string();
492

            
493
6
        match self.requested_descriptors {
494
4
            RequestedDescs::Digests(ref digests) => {
495
4
                uri.push_str("d/");
496
4
                let ids = digest_list_stringify(digests, hex::encode, "+")
497
4
                    .ok_or(RequestError::EmptyRequest)?;
498
4
                uri.push_str(&ids);
499
            }
500
2
            RequestedDescs::AllDescriptors => {
501
2
                uri.push_str("all");
502
2
            }
503
        }
504

            
505
6
        let req = http::Request::builder().method("GET").uri(uri);
506
6
        let req = add_common_headers(req, self.anonymized());
507

            
508
6
        Ok(req.body(String::new())?)
509
6
    }
510

            
511
6
    fn partial_response_body_ok(&self) -> bool {
512
6
        match self.requested_descriptors {
513
4
            RequestedDescs::Digests(ref digests) => digests.len() > 1,
514
2
            RequestedDescs::AllDescriptors => true,
515
        }
516
6
    }
517

            
518
4
    fn max_response_len(&self) -> usize {
519
        // TODO: Pick a more principled number; I just made these up.
520
4
        match self.requested_descriptors {
521
2
            RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
522
2
            RequestedDescs::AllDescriptors => 64 * 1024 * 1024, // big but not impossible
523
        }
524
4
    }
525

            
526
6
    fn anonymized(&self) -> AnonymizedRequest {
527
6
        AnonymizedRequest::Direct
528
6
    }
529
}
530

            
531
#[cfg(feature = "routerdesc")]
532
impl FromIterator<RdDigest> for RouterDescRequest {
533
6
    fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
534
6
        let digests = iter.into_iter().collect();
535

            
536
6
        RouterDescRequest {
537
6
            requested_descriptors: RequestedDescs::Digests(digests),
538
6
        }
539
6
    }
540
}
541

            
542
/// A request for the descriptor of whatever relay we are making the request to
543
#[derive(Debug, Clone, Default)]
544
#[cfg(feature = "routerdesc")]
545
#[non_exhaustive]
546
pub struct RoutersOwnDescRequest {}
547

            
548
#[cfg(feature = "routerdesc")]
549
impl RoutersOwnDescRequest {
550
    /// Construct a new request.
551
    pub fn new() -> Self {
552
        RoutersOwnDescRequest::default()
553
    }
554
}
555

            
556
#[cfg(feature = "routerdesc")]
557
impl sealed::RequestableInner for RoutersOwnDescRequest {
558
    fn make_request(&self) -> Result<http::Request<String>> {
559
        let uri = "/tor/server/authority";
560
        let req = http::Request::builder().method("GET").uri(uri);
561
        let req = add_common_headers(req, self.anonymized());
562

            
563
        Ok(req.body(String::new())?)
564
    }
565

            
566
    fn partial_response_body_ok(&self) -> bool {
567
        false
568
    }
569

            
570
    fn anonymized(&self) -> AnonymizedRequest {
571
        AnonymizedRequest::Direct
572
    }
573
}
574

            
575
/// A request for one or many extra-infos.
576
///
577
/// <https://spec.torproject.org/dir-spec/general-use-http-urls.html>
578
/// (search in page for "extra-info")
579
#[derive(Debug, Clone)]
580
#[cfg(feature = "routerdesc")]
581
pub struct ExtraInfoRequest {
582
    /// The extra-infos to request.
583
    requested_extra_infos: RequestedExtraInfos,
584
}
585

            
586
/// Which extra-info documents to download.
587
///
588
/// Currently only a subset of the available URLs are supported.
589
#[derive(Debug, Clone)]
590
#[cfg(feature = "routerdesc")]
591
#[non_exhaustive]
592
enum RequestedExtraInfos {
593
    /// Just ask for all the extra-infos.
594
    ///
595
    /// `http://<hostname>/tor/extra/all`
596
    // TODO: Rename this to `All`, alongside `RequestedRouterDescs`.
597
    AllExtraInfos,
598
    /// Download extra-infos with these SHA-1 digests.
599
    ///
600
    /// `http://<hostname>/tor/extra/d/...`
601
    Digests(Vec<ExtraInfoDigest>),
602
}
603

            
604
#[cfg(feature = "routerdesc")]
605
impl Default for ExtraInfoRequest {
606
    // TODO: This is probably not a reasonable default.
607
    fn default() -> Self {
608
        Self {
609
            requested_extra_infos: RequestedExtraInfos::Digests(Vec::new()),
610
        }
611
    }
612
}
613

            
614
#[cfg(feature = "routerdesc")]
615
impl ExtraInfoRequest {
616
    /// Construct a request for all extra-infos.
617
2
    pub fn all() -> Self {
618
2
        Self {
619
2
            requested_extra_infos: RequestedExtraInfos::AllExtraInfos,
620
2
        }
621
2
    }
622
    /// Construct a new empty request.
623
    pub fn new() -> Self {
624
        Self::default()
625
    }
626
}
627

            
628
#[cfg(feature = "routerdesc")]
629
impl sealed::RequestableInner for ExtraInfoRequest {
630
4
    fn make_request(&self) -> Result<http::Request<String>> {
631
4
        let mut uri = "/tor/extra/".to_string();
632

            
633
4
        match &self.requested_extra_infos {
634
2
            RequestedExtraInfos::AllExtraInfos => uri.push_str("all"),
635
2
            RequestedExtraInfos::Digests(digests) => {
636
2
                uri.push_str("d/");
637
2
                let ids = digest_list_stringify(digests, hex::encode_upper, "+")
638
2
                    .ok_or(RequestError::EmptyRequest)?;
639
2
                uri.push_str(&ids);
640
            }
641
        }
642

            
643
4
        let req = http::Request::builder().method("GET").uri(uri);
644
4
        let req = add_common_headers(req, self.anonymized());
645
4
        Ok(req.body(String::new())?)
646
4
    }
647

            
648
    fn partial_response_body_ok(&self) -> bool {
649
        match &self.requested_extra_infos {
650
            RequestedExtraInfos::Digests(digests) => digests.len() > 1,
651
            RequestedExtraInfos::AllExtraInfos => true,
652
        }
653
    }
654

            
655
    fn max_response_len(&self) -> usize {
656
        // TODO torspec#392: Pick more principled size limits.
657
        // These were copied from the RouterDescRequest impl and doubled.
658
        match &self.requested_extra_infos {
659
            RequestedExtraInfos::Digests(digests) => digests.len().saturating_mul(16 * 1024),
660
            RequestedExtraInfos::AllExtraInfos => 128 * 1024 * 1024,
661
        }
662
    }
663

            
664
4
    fn anonymized(&self) -> AnonymizedRequest {
665
4
        AnonymizedRequest::Direct
666
4
    }
667
}
668

            
669
#[cfg(feature = "routerdesc")]
670
impl FromIterator<ExtraInfoDigest> for ExtraInfoRequest {
671
2
    fn from_iter<T: IntoIterator<Item = ExtraInfoDigest>>(iter: T) -> Self {
672
2
        Self {
673
2
            requested_extra_infos: RequestedExtraInfos::Digests(iter.into_iter().collect()),
674
2
        }
675
2
    }
676
}
677

            
678
/// A request to download a hidden service descriptor
679
///
680
/// rend-spec-v3 2.2.6
681
#[derive(Debug, Clone)]
682
#[cfg(feature = "hs-client")]
683
pub struct HsDescDownloadRequest {
684
    /// What hidden service?
685
    hsid: HsBlindId,
686
    /// What's the largest acceptable response length?
687
    max_len: usize,
688
}
689

            
690
#[cfg(feature = "hs-client")]
691
impl HsDescDownloadRequest {
692
    /// Construct a request for a single onion service descriptor by its
693
    /// blinded ID.
694
43
    pub fn new(hsid: HsBlindId) -> Self {
695
        /// Default maximum length to use when we have no other information.
696
        const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
697
43
        HsDescDownloadRequest {
698
43
            hsid,
699
43
            max_len: DEFAULT_HSDESC_MAX_LEN,
700
43
        }
701
43
    }
702

            
703
    /// Set the maximum acceptable response length.
704
41
    pub fn set_max_len(&mut self, max_len: usize) {
705
41
        self.max_len = max_len;
706
41
    }
707
}
708

            
709
#[cfg(feature = "hs-client")]
710
impl sealed::RequestableInner for HsDescDownloadRequest {
711
84
    fn make_request(&self) -> Result<http::Request<String>> {
712
84
        let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
713
        // We hardcode version 3 here; if we ever have a v4 onion service
714
        // descriptor, it will need a different kind of Request.
715
84
        let uri = format!("/tor/hs/3/{}", hsid);
716
84
        let req = http::Request::builder().method("GET").uri(uri);
717
84
        let req = add_common_headers(req, self.anonymized());
718
84
        Ok(req.body(String::new())?)
719
84
    }
720

            
721
43
    fn partial_response_body_ok(&self) -> bool {
722
43
        false
723
43
    }
724

            
725
43
    fn max_response_len(&self) -> usize {
726
43
        self.max_len
727
43
    }
728

            
729
125
    fn anonymized(&self) -> AnonymizedRequest {
730
125
        AnonymizedRequest::Anonymized
731
125
    }
732
}
733

            
734
/// A request to upload a hidden service descriptor
735
///
736
/// rend-spec-v3 2.2.6
737
#[derive(Debug, Clone)]
738
#[cfg(feature = "hs-service")]
739
pub struct HsDescUploadRequest(String);
740

            
741
#[cfg(feature = "hs-service")]
742
impl HsDescUploadRequest {
743
    /// Construct a request for uploading a single onion service descriptor.
744
3608
    pub fn new(hsdesc: String) -> Self {
745
3608
        HsDescUploadRequest(hsdesc)
746
3608
    }
747
}
748

            
749
#[cfg(feature = "hs-service")]
750
impl sealed::RequestableInner for HsDescUploadRequest {
751
3608
    fn make_request(&self) -> Result<http::Request<String>> {
752
        /// The upload URI.
753
        const URI: &str = "/tor/hs/3/publish";
754

            
755
3608
        let req = http::Request::builder().method("POST").uri(URI);
756
3608
        let req = add_common_headers(req, self.anonymized());
757
3608
        Ok(req.body(self.0.clone())?)
758
3608
    }
759

            
760
3608
    fn partial_response_body_ok(&self) -> bool {
761
3608
        false
762
3608
    }
763

            
764
3608
    fn max_response_len(&self) -> usize {
765
        // We expect the response _body_ to be empty, but the max_response_len
766
        // is not zero because it represents the _total_ length of the response
767
        // (which includes the length of the status line and headers).
768
        //
769
        // A real Tor POST response will always be less than that length, which
770
        // will fit into 3 DATA messages at most. (The reply will be a single
771
        // HTTP line, followed by a Date header.)
772
3608
        1024
773
3608
    }
774

            
775
7216
    fn anonymized(&self) -> AnonymizedRequest {
776
7216
        AnonymizedRequest::Anonymized
777
7216
    }
778
}
779

            
780
/// Encodings that all Tor clients support.
781
const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
782

            
783
/// List all the encodings we accept
784
134
fn all_encodings() -> String {
785
    #[allow(unused_mut)]
786
134
    let mut encodings = UNIVERSAL_ENCODINGS.to_string();
787
    #[cfg(feature = "xz")]
788
134
    {
789
134
        encodings += ", x-tor-lzma";
790
134
    }
791
    #[cfg(feature = "zstd")]
792
134
    {
793
134
        encodings += ", x-zstd";
794
134
    }
795

            
796
134
    encodings
797
134
}
798

            
799
/// Add commonly used headers to the HTTP request.
800
///
801
/// (Right now, this is only Accept-Encoding.)
802
3810
fn add_common_headers(
803
3810
    req: http::request::Builder,
804
3810
    anon: AnonymizedRequest,
805
3810
) -> http::request::Builder {
806
    // TODO: gzip, brotli
807
3810
    match anon {
808
        AnonymizedRequest::Anonymized => {
809
            // In an anonymized request, we do not admit to supporting any
810
            // encoding besides those that are always available.
811
3692
            req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
812
        }
813
118
        AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
814
    }
815
3810
}
816

            
817
#[cfg(test)]
818
mod test {
819
    // @@ begin test lint list maintained by maint/add_warning @@
820
    #![allow(clippy::bool_assert_comparison)]
821
    #![allow(clippy::clone_on_copy)]
822
    #![allow(clippy::dbg_macro)]
823
    #![allow(clippy::mixed_attributes_style)]
824
    #![allow(clippy::print_stderr)]
825
    #![allow(clippy::print_stdout)]
826
    #![allow(clippy::single_char_pattern)]
827
    #![allow(clippy::unwrap_used)]
828
    #![allow(clippy::unchecked_time_subtraction)]
829
    #![allow(clippy::useless_vec)]
830
    #![allow(clippy::needless_pass_by_value)]
831
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
832
    use super::sealed::RequestableInner;
833
    use super::*;
834
    use web_time_compat::SystemTimeExt;
835

            
836
    #[test]
837
    fn test_md_request() -> Result<()> {
838
        let d1 = b"This is a testing digest. it isn";
839
        let d2 = b"'t actually SHA-256.............";
840

            
841
        let mut req = MicrodescRequest::default();
842
        req.push(*d1);
843
        assert!(!req.partial_response_body_ok());
844
        req.push(*d2);
845
        assert!(req.partial_response_body_ok());
846
        assert_eq!(req.max_response_len(), 16 << 10);
847

            
848
        let req = crate::util::encode_request(&req.make_request()?);
849

            
850
        assert_eq!(
851
            req,
852
            format!(
853
                "GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
854
                all_encodings()
855
            )
856
        );
857

            
858
        // Try it with FromIterator, and use some accessors.
859
        let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
860
        let ds: Vec<_> = req2.digests().collect();
861
        assert_eq!(ds, vec![d1, d2]);
862
        let req2 = crate::util::encode_request(&req2.make_request()?);
863
        assert_eq!(req, req2);
864

            
865
        Ok(())
866
    }
867

            
868
    #[test]
869
    fn test_cert_request() -> Result<()> {
870
        let d1 = b"This is a testing dn";
871
        let d2 = b"'t actually SHA-256.";
872
        let key1 = AuthCertKeyIds {
873
            id_fingerprint: (*d1).into(),
874
            sk_fingerprint: (*d2).into(),
875
        };
876

            
877
        let d3 = b"blah blah blah 1 2 3";
878
        let d4 = b"I like pizza from Na";
879
        let key2 = AuthCertKeyIds {
880
            id_fingerprint: (*d3).into(),
881
            sk_fingerprint: (*d4).into(),
882
        };
883

            
884
        let mut req = AuthCertRequest::default();
885
        req.push(key1);
886
        assert!(!req.partial_response_body_ok());
887
        req.push(key2);
888
        assert!(req.partial_response_body_ok());
889
        assert_eq!(req.max_response_len(), 32 << 10);
890

            
891
        let keys: Vec<_> = req.keys().collect();
892
        assert_eq!(keys, vec![&key1, &key2]);
893

            
894
        let req = crate::util::encode_request(&req.make_request()?);
895

            
896
        assert_eq!(
897
            req,
898
            format!(
899
                "GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
900
                all_encodings()
901
            )
902
        );
903

            
904
        let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
905
        let req2 = crate::util::encode_request(&req2.make_request()?);
906
        assert_eq!(req, req2);
907

            
908
        Ok(())
909
    }
910

            
911
    #[test]
912
    fn test_consensus_request() -> Result<()> {
913
        let d1 = RsaIdentity::from_bytes(
914
            &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
915
        )
916
        .unwrap();
917

            
918
        let d2 = b"blah blah blah 12 blah blah blah";
919
        let d3 = SystemTime::get();
920
        let mut req = ConsensusRequest::default();
921

            
922
        let when = httpdate::fmt_http_date(d3);
923

            
924
        req.push_authority_id(d1);
925
        req.push_old_consensus_digest(*d2);
926
        req.set_last_consensus_date(d3);
927
        assert!(!req.partial_response_body_ok());
928
        assert_eq!(req.max_response_len(), (16 << 20) - 1);
929
        assert_eq!(req.old_consensus_digests().next(), Some(d2));
930
        assert_eq!(req.authority_ids().next(), Some(&d1));
931
        assert_eq!(req.last_consensus_date(), Some(d3));
932

            
933
        let req = crate::util::encode_request(&req.make_request()?);
934

            
935
        assert_eq!(
936
            req,
937
            format!(
938
                "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",
939
                all_encodings(),
940
                when
941
            )
942
        );
943

            
944
        // Request without authorities
945
        let req = ConsensusRequest::default();
946
        let req = crate::util::encode_request(&req.make_request()?);
947
        assert_eq!(
948
            req,
949
            format!(
950
                "GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
951
                all_encodings()
952
            )
953
        );
954

            
955
        Ok(())
956
    }
957

            
958
    #[test]
959
    #[cfg(feature = "routerdesc")]
960
    fn test_rd_request_all() -> Result<()> {
961
        let req = RouterDescRequest::all();
962
        assert!(req.partial_response_body_ok());
963
        assert_eq!(req.max_response_len(), 1 << 26);
964

            
965
        let req = crate::util::encode_request(&req.make_request()?);
966

            
967
        assert_eq!(
968
            req,
969
            format!(
970
                "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
971
                all_encodings()
972
            )
973
        );
974

            
975
        Ok(())
976
    }
977

            
978
    #[test]
979
    #[cfg(feature = "routerdesc")]
980
    fn test_rd_request() -> Result<()> {
981
        let d1 = b"at some point I got ";
982
        let d2 = b"of writing in hex...";
983

            
984
        let mut req = RouterDescRequest::default();
985

            
986
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
987
            digests.push(*d1);
988
        }
989
        assert!(!req.partial_response_body_ok());
990
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
991
            digests.push(*d2);
992
        }
993
        assert!(req.partial_response_body_ok());
994
        assert_eq!(req.max_response_len(), 16 << 10);
995

            
996
        let req = crate::util::encode_request(&req.make_request()?);
997

            
998
        assert_eq!(
999
            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::encode_request(&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::encode_request(&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::encode_request(&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::encode_request(&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(())
    }
}