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

            
4
use tor_llcrypto::pk::rsa::RsaIdentity;
5
use tor_netdoc::doc::authcert::AuthCertKeyIds;
6
use tor_netdoc::doc::microdesc::MdDigest;
7
use tor_netdoc::doc::netstatus::ConsensusFlavor;
8
#[cfg(feature = "routerdesc")]
9
use tor_netdoc::doc::routerdesc::RdDigest;
10
use tor_proto::circuit::ClientCirc;
11

            
12
#[cfg(feature = "hs-client")]
13
use tor_hscrypto::pk::HsBlindId;
14

            
15
/// Alias for a result with a `RequestError`.
16
type Result<T> = std::result::Result<T, crate::err::RequestError>;
17

            
18
use base64ct::{Base64Unpadded, Encoding as _};
19
use std::borrow::Cow;
20
use std::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::err::RequestError;
28
use crate::AnonymizedRequest;
29

            
30
/// Declare an inaccessible public type.
31
pub(crate) mod sealed {
32
    use super::{AnonymizedRequest, ClientCirc, Result};
33

            
34
    use std::future::Future;
35
    use std::pin::Pin;
36

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
260
4
        let mut req = http::Request::builder().method("GET").uri(uri);
261
4
        req = add_common_headers(req, self.anonymized());
262

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

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

            
276
4
        Ok(req.body(String::new())?)
277
4
    }
278

            
279
2
    fn partial_response_body_ok(&self) -> bool {
280
2
        false
281
2
    }
282

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

            
304
4
    fn anonymized(&self) -> AnonymizedRequest {
305
4
        AnonymizedRequest::Direct
306
4
    }
307
}
308

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

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

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

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

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

            
341
4
        let ids: Vec<String> = ids
342
4
            .iter()
343
10
            .map(|id| {
344
8
                format!(
345
8
                    "{}-{}",
346
8
                    hex::encode(id.id_fingerprint.as_bytes()),
347
8
                    hex::encode(id.sk_fingerprint.as_bytes())
348
8
                )
349
10
            })
350
4
            .collect();
351
4

            
352
4
        let uri = format!("/tor/keys/fp-sk/{}", &ids.join("+"));
353
4

            
354
4
        let req = http::Request::builder().method("GET").uri(uri);
355
4
        let req = add_common_headers(req, self.anonymized());
356
4

            
357
4
        Ok(req.body(String::new())?)
358
4
    }
359

            
360
4
    fn partial_response_body_ok(&self) -> bool {
361
4
        self.ids.len() > 1
362
4
    }
363

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

            
369
4
    fn anonymized(&self) -> AnonymizedRequest {
370
4
        AnonymizedRequest::Direct
371
4
    }
372
}
373

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

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

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

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

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

            
415
18
        let req = add_common_headers(req, self.anonymized());
416
18

            
417
18
        Ok(req.body(String::new())?)
418
18
    }
419

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

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

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

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

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

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

            
462
#[cfg(feature = "routerdesc")]
463
impl Default for RouterDescRequest {
464
2
    fn default() -> Self {
465
2
        RouterDescRequest {
466
2
            requested_descriptors: RequestedDescs::Digests(Vec::new()),
467
2
        }
468
2
    }
469
}
470

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

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

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

            
502
6
        let req = http::Request::builder().method("GET").uri(uri);
503
6
        let req = add_common_headers(req, self.anonymized());
504
6

            
505
6
        Ok(req.body(String::new())?)
506
6
    }
507

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

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

            
523
6
    fn anonymized(&self) -> AnonymizedRequest {
524
6
        AnonymizedRequest::Direct
525
6
    }
526
}
527

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

            
533
6
        RouterDescRequest {
534
6
            requested_descriptors: RequestedDescs::Digests(digests),
535
6
        }
536
6
    }
537
}
538

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

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

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

            
560
        Ok(req.body(String::new())?)
561
    }
562

            
563
    fn partial_response_body_ok(&self) -> bool {
564
        false
565
    }
566

            
567
    fn anonymized(&self) -> AnonymizedRequest {
568
        AnonymizedRequest::Direct
569
    }
570
}
571

            
572
/// A request to download a hidden service descriptor
573
///
574
/// rend-spec-v3 2.2.6
575
#[derive(Debug, Clone)]
576
#[cfg(feature = "hs-client")]
577
pub struct HsDescDownloadRequest {
578
    /// What hidden service?
579
    hsid: HsBlindId,
580
    /// What's the largest acceptable response length?
581
    max_len: usize,
582
}
583

            
584
#[cfg(feature = "hs-client")]
585
impl HsDescDownloadRequest {
586
    /// Construct a request for a single onion service descriptor by its
587
    /// blinded ID.
588
41
    pub fn new(hsid: HsBlindId) -> Self {
589
        /// Default maximum length to use when we have no other information.
590
        const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
591
41
        HsDescDownloadRequest {
592
41
            hsid,
593
41
            max_len: DEFAULT_HSDESC_MAX_LEN,
594
41
        }
595
41
    }
596

            
597
    /// Set the maximum acceptable response length.
598
39
    pub fn set_max_len(&mut self, max_len: usize) {
599
39
        self.max_len = max_len;
600
39
    }
601
}
602

            
603
#[cfg(feature = "hs-client")]
604
impl sealed::RequestableInner for HsDescDownloadRequest {
605
80
    fn make_request(&self) -> Result<http::Request<String>> {
606
80
        let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
607
80
        // We hardcode version 3 here; if we ever have a v4 onion service
608
80
        // descriptor, it will need a different kind of Request.
609
80
        let uri = format!("/tor/hs/3/{}", hsid);
610
80
        let req = http::Request::builder().method("GET").uri(uri);
611
80
        let req = add_common_headers(req, self.anonymized());
612
80
        Ok(req.body(String::new())?)
613
80
    }
614

            
615
41
    fn partial_response_body_ok(&self) -> bool {
616
41
        false
617
41
    }
618

            
619
41
    fn max_response_len(&self) -> usize {
620
41
        self.max_len
621
41
    }
622

            
623
119
    fn anonymized(&self) -> AnonymizedRequest {
624
119
        AnonymizedRequest::Anonymized
625
119
    }
626
}
627

            
628
/// A request to upload a hidden service descriptor
629
///
630
/// rend-spec-v3 2.2.6
631
#[derive(Debug, Clone)]
632
#[cfg(feature = "hs-service")]
633
pub struct HsDescUploadRequest(String);
634

            
635
#[cfg(feature = "hs-service")]
636
impl HsDescUploadRequest {
637
    /// Construct a request for uploading a single onion service descriptor.
638
    pub fn new(hsdesc: String) -> Self {
639
        HsDescUploadRequest(hsdesc)
640
    }
641
}
642

            
643
#[cfg(feature = "hs-service")]
644
impl sealed::RequestableInner for HsDescUploadRequest {
645
    fn make_request(&self) -> Result<http::Request<String>> {
646
        /// The upload URI.
647
        const URI: &str = "/tor/hs/3/publish";
648

            
649
        let req = http::Request::builder().method("POST").uri(URI);
650
        let req = add_common_headers(req, self.anonymized());
651
        Ok(req.body(self.0.clone())?)
652
    }
653

            
654
    fn partial_response_body_ok(&self) -> bool {
655
        false
656
    }
657

            
658
    fn max_response_len(&self) -> usize {
659
        // We expect the response _body_ to be empty, but the max_response_len
660
        // is not zero because it represents the _total_ length of the response
661
        // (which includes the length of the status line and headers).
662
        //
663
        // A real Tor POST response will always be less than that length, which
664
        // will fit into 3 DATA messages at most. (The reply will be a single
665
        // HTTP line, followed by a Date header.)
666
        1024
667
    }
668

            
669
    fn anonymized(&self) -> AnonymizedRequest {
670
        AnonymizedRequest::Anonymized
671
    }
672
}
673

            
674
/// Encodings that all Tor clients support.
675
const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
676

            
677
/// List all the encodings we accept
678
44
fn all_encodings() -> String {
679
44
    #[allow(unused_mut)]
680
44
    let mut encodings = UNIVERSAL_ENCODINGS.to_string();
681
44
    #[cfg(feature = "xz")]
682
44
    {
683
44
        encodings += ", x-tor-lzma";
684
44
    }
685
44
    #[cfg(feature = "zstd")]
686
44
    {
687
44
        encodings += ", x-zstd";
688
44
    }
689
44

            
690
44
    encodings
691
44
}
692

            
693
/// Add commonly used headers to the HTTP request.
694
///
695
/// (Right now, this is only Accept-Encoding.)
696
112
fn add_common_headers(
697
112
    req: http::request::Builder,
698
112
    anon: AnonymizedRequest,
699
112
) -> http::request::Builder {
700
112
    // TODO: gzip, brotli
701
112
    match anon {
702
        AnonymizedRequest::Anonymized => {
703
            // In an anonymized request, we do not admit to supporting any
704
            // encoding besides those that are always available.
705
80
            req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
706
        }
707
32
        AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
708
    }
709
112
}
710

            
711
#[cfg(test)]
712
mod test {
713
    // @@ begin test lint list maintained by maint/add_warning @@
714
    #![allow(clippy::bool_assert_comparison)]
715
    #![allow(clippy::clone_on_copy)]
716
    #![allow(clippy::dbg_macro)]
717
    #![allow(clippy::mixed_attributes_style)]
718
    #![allow(clippy::print_stderr)]
719
    #![allow(clippy::print_stdout)]
720
    #![allow(clippy::single_char_pattern)]
721
    #![allow(clippy::unwrap_used)]
722
    #![allow(clippy::unchecked_duration_subtraction)]
723
    #![allow(clippy::useless_vec)]
724
    #![allow(clippy::needless_pass_by_value)]
725
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
726
    use super::sealed::RequestableInner;
727
    use super::*;
728

            
729
    #[test]
730
    fn test_md_request() -> Result<()> {
731
        let d1 = b"This is a testing digest. it isn";
732
        let d2 = b"'t actually SHA-256.............";
733

            
734
        let mut req = MicrodescRequest::default();
735
        req.push(*d1);
736
        assert!(!req.partial_response_body_ok());
737
        req.push(*d2);
738
        assert!(req.partial_response_body_ok());
739
        assert_eq!(req.max_response_len(), 16 << 10);
740

            
741
        let req = crate::util::encode_request(&req.make_request()?);
742

            
743
        assert_eq!(req,
744
                   format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
745

            
746
        // Try it with FromIterator, and use some accessors.
747
        let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
748
        let ds: Vec<_> = req2.digests().collect();
749
        assert_eq!(ds, vec![d1, d2]);
750
        let req2 = crate::util::encode_request(&req2.make_request()?);
751
        assert_eq!(req, req2);
752

            
753
        Ok(())
754
    }
755

            
756
    #[test]
757
    fn test_cert_request() -> Result<()> {
758
        let d1 = b"This is a testing dn";
759
        let d2 = b"'t actually SHA-256.";
760
        let key1 = AuthCertKeyIds {
761
            id_fingerprint: (*d1).into(),
762
            sk_fingerprint: (*d2).into(),
763
        };
764

            
765
        let d3 = b"blah blah blah 1 2 3";
766
        let d4 = b"I like pizza from Na";
767
        let key2 = AuthCertKeyIds {
768
            id_fingerprint: (*d3).into(),
769
            sk_fingerprint: (*d4).into(),
770
        };
771

            
772
        let mut req = AuthCertRequest::default();
773
        req.push(key1);
774
        assert!(!req.partial_response_body_ok());
775
        req.push(key2);
776
        assert!(req.partial_response_body_ok());
777
        assert_eq!(req.max_response_len(), 32 << 10);
778

            
779
        let keys: Vec<_> = req.keys().collect();
780
        assert_eq!(keys, vec![&key1, &key2]);
781

            
782
        let req = crate::util::encode_request(&req.make_request()?);
783

            
784
        assert_eq!(req,
785
                   format!("GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
786

            
787
        let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
788
        let req2 = crate::util::encode_request(&req2.make_request()?);
789
        assert_eq!(req, req2);
790

            
791
        Ok(())
792
    }
793

            
794
    #[test]
795
    fn test_consensus_request() -> Result<()> {
796
        let d1 = RsaIdentity::from_bytes(
797
            &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
798
        )
799
        .unwrap();
800

            
801
        let d2 = b"blah blah blah 12 blah blah blah";
802
        let d3 = SystemTime::now();
803
        let mut req = ConsensusRequest::default();
804

            
805
        let when = httpdate::fmt_http_date(d3);
806

            
807
        req.push_authority_id(d1);
808
        req.push_old_consensus_digest(*d2);
809
        req.set_last_consensus_date(d3);
810
        assert!(!req.partial_response_body_ok());
811
        assert_eq!(req.max_response_len(), (16 << 20) - 1);
812
        assert_eq!(req.old_consensus_digests().next(), Some(d2));
813
        assert_eq!(req.authority_ids().next(), Some(&d1));
814
        assert_eq!(req.last_consensus_date(), Some(d3));
815

            
816
        let req = crate::util::encode_request(&req.make_request()?);
817

            
818
        assert_eq!(req,
819
                   format!("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", all_encodings(), when));
820

            
821
        // Request without authorities
822
        let req = ConsensusRequest::default();
823
        let req = crate::util::encode_request(&req.make_request()?);
824
        assert_eq!(req,
825
                   format!("GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
826

            
827
        Ok(())
828
    }
829

            
830
    #[test]
831
    #[cfg(feature = "routerdesc")]
832
    fn test_rd_request_all() -> Result<()> {
833
        let req = RouterDescRequest::all();
834
        assert!(req.partial_response_body_ok());
835
        assert_eq!(req.max_response_len(), 1 << 26);
836

            
837
        let req = crate::util::encode_request(&req.make_request()?);
838

            
839
        assert_eq!(
840
            req,
841
            format!(
842
                "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
843
                all_encodings()
844
            )
845
        );
846

            
847
        Ok(())
848
    }
849

            
850
    #[test]
851
    #[cfg(feature = "routerdesc")]
852
    fn test_rd_request() -> Result<()> {
853
        let d1 = b"at some point I got ";
854
        let d2 = b"of writing in hex...";
855

            
856
        let mut req = RouterDescRequest::default();
857

            
858
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
859
            digests.push(*d1);
860
        }
861
        assert!(!req.partial_response_body_ok());
862
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
863
            digests.push(*d2);
864
        }
865
        assert!(req.partial_response_body_ok());
866
        assert_eq!(req.max_response_len(), 16 << 10);
867

            
868
        let req = crate::util::encode_request(&req.make_request()?);
869

            
870
        assert_eq!(req,
871
                   format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
872

            
873
        // Try it with FromIterator, and use some accessors.
874
        let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
875
        let ds: Vec<_> = match req2.requested_descriptors {
876
            RequestedDescs::Digests(ref digests) => digests.iter().collect(),
877
            RequestedDescs::AllDescriptors => Vec::new(),
878
        };
879
        assert_eq!(ds, vec![d1, d2]);
880
        let req2 = crate::util::encode_request(&req2.make_request()?);
881
        assert_eq!(req, req2);
882
        Ok(())
883
    }
884

            
885
    #[test]
886
    #[cfg(feature = "hs-client")]
887
    fn test_hs_desc_download_request() -> Result<()> {
888
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
889
        let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
890
        let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
891
        let hsid = HsBlindId::from(hsid);
892
        let req = HsDescDownloadRequest::new(hsid);
893
        assert!(!req.partial_response_body_ok());
894
        assert_eq!(req.max_response_len(), 50 * 1000);
895

            
896
        let req = crate::util::encode_request(&req.make_request()?);
897

            
898
        assert_eq!(
899
            req,
900
            format!("GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", UNIVERSAL_ENCODINGS)
901
        );
902

            
903
        Ok(())
904
    }
905
}