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::iter::FromIterator;
21
use std::time::{Duration, SystemTime};
22

            
23
use itertools::Itertools;
24

            
25
use crate::err::RequestError;
26
use crate::AnonymizedRequest;
27

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

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

            
52
        /// Return the maximum allowable response length we'll accept for this
53
        /// request.
54
2
        fn max_response_len(&self) -> usize {
55
2
            (16 * 1024 * 1024) - 1
56
2
        }
57

            
58
        /// Return an error if there is some problem with the provided circuit that
59
        /// would keep it from being used for this request.
60
        fn check_circuit(&self, circ: &ClientCirc) -> Result<()> {
61
            let _ = circ;
62
            Ok(())
63
        }
64

            
65
        /// Return a value to say whether this request must be anonymized.
66
        fn anonymized(&self) -> AnonymizedRequest;
67
    }
68
}
69

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

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

            
88
impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> {
89
2
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90
2
        write!(f, "{:?}", self.0.make_request())
91
2
    }
92
}
93

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

            
108
    /// We refuse to proceed if the directory says that we are more slow than
109
    /// this.
110
    ///
111
    /// (This is equivalent to deciding that, from our perspective, the
112
    /// directory is at least this fast.)
113
    max_slow: Duration,
114
}
115

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

            
139
impl ConsensusRequest {
140
    /// Create a new request for a consensus directory document.
141
228
    pub fn new(flavor: ConsensusFlavor) -> Self {
142
228
        ConsensusRequest {
143
228
            flavor,
144
228
            authority_ids: Vec::new(),
145
228
            last_consensus_published: None,
146
228
            last_consensus_sha3_256: Vec::new(),
147
228
            skew_limit: None,
148
228
        }
149
228
    }
150

            
151
    /// Add `id` to the list of authorities that this request should
152
    /// say we believe in.
153
2
    pub fn push_authority_id(&mut self, id: RsaIdentity) {
154
2
        self.authority_ids.push(id);
155
2
    }
156

            
157
    /// Add `d` to the list of consensus digests this request should
158
    /// say we already have.
159
66
    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
160
66
        self.last_consensus_sha3_256.push(d);
161
66
    }
162

            
163
    /// Set the publication time we should say we have for our last
164
    /// consensus to `when`.
165
130
    pub fn set_last_consensus_date(&mut self, when: SystemTime) {
166
130
        self.last_consensus_published = Some(when);
167
130
    }
168

            
169
    /// Return a slice of the consensus digests that we're saying we
170
    /// already have.
171
130
    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
172
130
        self.last_consensus_sha3_256.iter()
173
130
    }
174

            
175
    /// Return an iterator of the authority identities that this request
176
    /// is saying we believe in.
177
2
    pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
178
2
        self.authority_ids.iter()
179
2
    }
180

            
181
    /// Return the date we're reporting for our most recent consensus.
182
230
    pub fn last_consensus_date(&self) -> Option<SystemTime> {
183
230
        self.last_consensus_published
184
230
    }
185

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

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

            
228
impl Default for ConsensusRequest {
229
4
    fn default() -> Self {
230
4
        Self::new(ConsensusFlavor::Microdesc)
231
4
    }
232
}
233

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

            
254
4
        let mut req = http::Request::builder().method("GET").uri(uri);
255
4
        req = add_common_headers(req, self.anonymized());
256

            
257
        // Possibly, add an if-modified-since header.
258
4
        if let Some(when) = self.last_consensus_date() {
259
2
            req = req.header(
260
2
                http::header::IF_MODIFIED_SINCE,
261
2
                httpdate::fmt_http_date(when),
262
2
            );
263
2
        }
264

            
265
        // Possibly, add an X-Or-Diff-From-Consensus header.
266
4
        if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
267
2
            req = req.header("X-Or-Diff-From-Consensus", &ids);
268
2
        }
269

            
270
4
        Ok(req.body(String::new())?)
271
4
    }
272

            
273
2
    fn partial_response_body_ok(&self) -> bool {
274
2
        false
275
2
    }
276

            
277
    fn check_circuit(&self, circ: &ClientCirc) -> Result<()> {
278
        use tor_proto::ClockSkew::*;
279
        // This is the clock skew _according to the directory_.
280
        let skew = circ.channel().clock_skew();
281
        match (&self.skew_limit, &skew) {
282
            (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
283
                Err(RequestError::TooMuchClockSkew)
284
            }
285
            (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
286
                Err(RequestError::TooMuchClockSkew)
287
            }
288
            (_, _) => Ok(()),
289
        }
290
    }
291

            
292
4
    fn anonymized(&self) -> AnonymizedRequest {
293
4
        AnonymizedRequest::Direct
294
4
    }
295
}
296

            
297
/// A request for one or more authority certificates.
298
#[derive(Debug, Clone, Default)]
299
pub struct AuthCertRequest {
300
    /// The identity/signing keys of the certificates we want.
301
    ids: Vec<AuthCertKeyIds>,
302
}
303

            
304
impl AuthCertRequest {
305
    /// Create a new request, asking for no authority certificates.
306
98
    pub fn new() -> Self {
307
98
        AuthCertRequest::default()
308
98
    }
309

            
310
    /// Add `ids` to the list of certificates we're asking for.
311
104
    pub fn push(&mut self, ids: AuthCertKeyIds) {
312
104
        self.ids.push(ids);
313
104
    }
314

            
315
    /// Return a list of the keys that we're asking for.
316
98
    pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
317
98
        self.ids.iter()
318
98
    }
319
}
320

            
321
impl sealed::RequestableInner for AuthCertRequest {
322
4
    fn make_request(&self) -> Result<http::Request<String>> {
323
4
        if self.ids.is_empty() {
324
            return Err(RequestError::EmptyRequest);
325
4
        }
326
4
        let mut ids = self.ids.clone();
327
4
        ids.sort_unstable();
328
4

            
329
4
        let ids: Vec<String> = ids
330
4
            .iter()
331
10
            .map(|id| {
332
8
                format!(
333
8
                    "{}-{}",
334
8
                    hex::encode(id.id_fingerprint.as_bytes()),
335
8
                    hex::encode(id.sk_fingerprint.as_bytes())
336
8
                )
337
10
            })
338
4
            .collect();
339
4

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

            
342
4
        let req = http::Request::builder().method("GET").uri(uri);
343
4
        let req = add_common_headers(req, self.anonymized());
344
4

            
345
4
        Ok(req.body(String::new())?)
346
4
    }
347

            
348
4
    fn partial_response_body_ok(&self) -> bool {
349
4
        self.ids.len() > 1
350
4
    }
351

            
352
2
    fn max_response_len(&self) -> usize {
353
2
        // TODO: Pick a more principled number; I just made this one up.
354
2
        self.ids.len().saturating_mul(16 * 1024)
355
2
    }
356

            
357
4
    fn anonymized(&self) -> AnonymizedRequest {
358
4
        AnonymizedRequest::Direct
359
4
    }
360
}
361

            
362
impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
363
4
    fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
364
4
        let mut req = Self::new();
365
10
        for i in iter {
366
6
            req.push(i);
367
6
        }
368
4
        req
369
4
    }
370
}
371

            
372
/// A request for one or more microdescriptors
373
#[derive(Debug, Clone, Default)]
374
pub struct MicrodescRequest {
375
    /// The SHA256 digests of the microdescriptors we want.
376
    digests: Vec<MdDigest>,
377
}
378

            
379
impl MicrodescRequest {
380
    /// Construct a request for no microdescriptors.
381
176
    pub fn new() -> Self {
382
176
        MicrodescRequest::default()
383
176
    }
384
    /// Add `d` to the list of microdescriptors we want to request.
385
32216
    pub fn push(&mut self, d: MdDigest) {
386
32216
        self.digests.push(d);
387
32216
    }
388

            
389
    /// Return a list of the microdescriptor digests that we're asking for.
390
34
    pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
391
34
        self.digests.iter()
392
34
    }
393
}
394

            
395
impl sealed::RequestableInner for MicrodescRequest {
396
18
    fn make_request(&self) -> Result<http::Request<String>> {
397
33
        let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
398
18
        let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
399
18
            .ok_or(RequestError::EmptyRequest)?;
400
18
        let uri = format!("/tor/micro/d/{}.z", &ids);
401
18
        let req = http::Request::builder().method("GET").uri(uri);
402
18

            
403
18
        let req = add_common_headers(req, self.anonymized());
404
18

            
405
18
        Ok(req.body(String::new())?)
406
18
    }
407

            
408
18
    fn partial_response_body_ok(&self) -> bool {
409
18
        self.digests.len() > 1
410
18
    }
411

            
412
16
    fn max_response_len(&self) -> usize {
413
16
        // TODO: Pick a more principled number; I just made this one up.
414
16
        self.digests.len().saturating_mul(8 * 1024)
415
16
    }
416

            
417
32
    fn anonymized(&self) -> AnonymizedRequest {
418
32
        AnonymizedRequest::Direct
419
32
    }
420
}
421

            
422
impl FromIterator<MdDigest> for MicrodescRequest {
423
24
    fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
424
24
        let mut req = Self::new();
425
2050
        for i in iter {
426
2026
            req.push(i);
427
2026
        }
428
24
        req
429
24
    }
430
}
431

            
432
/// A request for one, many or all router descriptors.
433
#[derive(Debug, Clone)]
434
#[cfg(feature = "routerdesc")]
435
pub struct RouterDescRequest {
436
    /// The descriptors to request.
437
    requested_descriptors: RequestedDescs,
438
}
439

            
440
/// Tracks the different router descriptor types.
441
#[derive(Debug, Clone)]
442
#[cfg(feature = "routerdesc")]
443
enum RequestedDescs {
444
    /// If this is set, we just ask for all the descriptors.
445
    AllDescriptors,
446
    /// A list of digests to download.
447
    Digests(Vec<RdDigest>),
448
}
449

            
450
#[cfg(feature = "routerdesc")]
451
impl Default for RouterDescRequest {
452
2
    fn default() -> Self {
453
2
        RouterDescRequest {
454
2
            requested_descriptors: RequestedDescs::Digests(Vec::new()),
455
2
        }
456
2
    }
457
}
458

            
459
#[cfg(feature = "routerdesc")]
460
impl RouterDescRequest {
461
    /// Construct a request for all router descriptors.
462
2
    pub fn all() -> Self {
463
2
        RouterDescRequest {
464
2
            requested_descriptors: RequestedDescs::AllDescriptors,
465
2
        }
466
2
    }
467
    /// Construct a new empty request.
468
    pub fn new() -> Self {
469
        RouterDescRequest::default()
470
    }
471
}
472

            
473
#[cfg(feature = "routerdesc")]
474
impl sealed::RequestableInner for RouterDescRequest {
475
6
    fn make_request(&self) -> Result<http::Request<String>> {
476
6
        let mut uri = "/tor/server/".to_string();
477
6

            
478
6
        match self.requested_descriptors {
479
4
            RequestedDescs::Digests(ref digests) => {
480
4
                uri.push_str("d/");
481
4
                let ids = digest_list_stringify(digests, hex::encode, "+")
482
4
                    .ok_or(RequestError::EmptyRequest)?;
483
4
                uri.push_str(&ids);
484
            }
485
2
            RequestedDescs::AllDescriptors => {
486
2
                uri.push_str("all");
487
2
            }
488
        }
489

            
490
6
        uri.push_str(".z");
491
6

            
492
6
        let req = http::Request::builder().method("GET").uri(uri);
493
6
        let req = add_common_headers(req, self.anonymized());
494
6

            
495
6
        Ok(req.body(String::new())?)
496
6
    }
497

            
498
6
    fn partial_response_body_ok(&self) -> bool {
499
6
        match self.requested_descriptors {
500
4
            RequestedDescs::Digests(ref digests) => digests.len() > 1,
501
2
            RequestedDescs::AllDescriptors => true,
502
        }
503
6
    }
504

            
505
4
    fn max_response_len(&self) -> usize {
506
4
        // TODO: Pick a more principled number; I just made these up.
507
4
        match self.requested_descriptors {
508
2
            RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
509
2
            RequestedDescs::AllDescriptors => 64 * 1024 * 1024, // big but not impossible
510
        }
511
4
    }
512

            
513
6
    fn anonymized(&self) -> AnonymizedRequest {
514
6
        AnonymizedRequest::Direct
515
6
    }
516
}
517

            
518
#[cfg(feature = "routerdesc")]
519
impl FromIterator<RdDigest> for RouterDescRequest {
520
6
    fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
521
6
        let digests = iter.into_iter().collect();
522
6

            
523
6
        RouterDescRequest {
524
6
            requested_descriptors: RequestedDescs::Digests(digests),
525
6
        }
526
6
    }
527
}
528

            
529
/// A request for the descriptor of whatever relay we are making the request to
530
#[derive(Debug, Clone, Default)]
531
#[cfg(feature = "routerdesc")]
532
#[non_exhaustive]
533
pub struct RoutersOwnDescRequest {}
534

            
535
#[cfg(feature = "routerdesc")]
536
impl RoutersOwnDescRequest {
537
    /// Construct a new request.
538
    pub fn new() -> Self {
539
        RoutersOwnDescRequest::default()
540
    }
541
}
542

            
543
#[cfg(feature = "routerdesc")]
544
impl sealed::RequestableInner for RoutersOwnDescRequest {
545
    fn make_request(&self) -> Result<http::Request<String>> {
546
        let uri = "/tor/server/authority.z";
547
        let req = http::Request::builder().method("GET").uri(uri);
548
        let req = add_common_headers(req, self.anonymized());
549

            
550
        Ok(req.body(String::new())?)
551
    }
552

            
553
    fn partial_response_body_ok(&self) -> bool {
554
        false
555
    }
556

            
557
    fn anonymized(&self) -> AnonymizedRequest {
558
        AnonymizedRequest::Direct
559
    }
560
}
561

            
562
/// A request to download a hidden service descriptor
563
///
564
/// rend-spec-v3 2.2.6
565
#[derive(Debug, Clone)]
566
#[cfg(feature = "hs-client")]
567
pub struct HsDescDownloadRequest {
568
    /// What hidden service?
569
    hsid: HsBlindId,
570
    /// What's the largest acceptable response length?
571
    max_len: usize,
572
}
573

            
574
#[cfg(feature = "hs-client")]
575
impl HsDescDownloadRequest {
576
    /// Construct a request for a single onion service descriptor by its
577
    /// blinded ID.
578
34
    pub fn new(hsid: HsBlindId) -> Self {
579
        /// Default maximum length to use when we have no other information.
580
        const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
581
34
        HsDescDownloadRequest {
582
34
            hsid,
583
34
            max_len: DEFAULT_HSDESC_MAX_LEN,
584
34
        }
585
34
    }
586

            
587
    /// Set the maximum acceptable response length.
588
32
    pub fn set_max_len(&mut self, max_len: usize) {
589
32
        self.max_len = max_len;
590
32
    }
591
}
592

            
593
#[cfg(feature = "hs-client")]
594
impl sealed::RequestableInner for HsDescDownloadRequest {
595
66
    fn make_request(&self) -> Result<http::Request<String>> {
596
66
        let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
597
66
        // We hardcode version 3 here; if we ever have a v4 onion service
598
66
        // descriptor, it will need a different kind of Request.
599
66
        let uri = format!("/tor/hs/3/{}", hsid);
600
66
        let req = http::Request::builder().method("GET").uri(uri);
601
66
        let req = add_common_headers(req, self.anonymized());
602
66
        Ok(req.body(String::new())?)
603
66
    }
604

            
605
34
    fn partial_response_body_ok(&self) -> bool {
606
34
        false
607
34
    }
608

            
609
34
    fn max_response_len(&self) -> usize {
610
34
        self.max_len
611
34
    }
612

            
613
98
    fn anonymized(&self) -> AnonymizedRequest {
614
98
        AnonymizedRequest::Anonymized
615
98
    }
616
}
617

            
618
/// A request to upload a hidden service descriptor
619
///
620
/// rend-spec-v3 2.2.6
621
#[derive(Debug, Clone)]
622
#[cfg(feature = "hs-service")]
623
pub struct HsDescUploadRequest(String);
624

            
625
#[cfg(feature = "hs-service")]
626
impl HsDescUploadRequest {
627
    /// Construct a request for uploading a single onion service descriptor.
628
2816
    pub fn new(hsdesc: String) -> Self {
629
2816
        HsDescUploadRequest(hsdesc)
630
2816
    }
631
}
632

            
633
#[cfg(feature = "hs-service")]
634
impl sealed::RequestableInner for HsDescUploadRequest {
635
2816
    fn make_request(&self) -> Result<http::Request<String>> {
636
        /// The upload URI.
637
        const URI: &str = "/tor/hs/3/publish";
638

            
639
2816
        let req = http::Request::builder().method("POST").uri(URI);
640
2816
        let req = add_common_headers(req, self.anonymized());
641
2816
        Ok(req.body(self.0.clone())?)
642
2816
    }
643

            
644
2816
    fn partial_response_body_ok(&self) -> bool {
645
2816
        false
646
2816
    }
647

            
648
2816
    fn max_response_len(&self) -> usize {
649
2816
        // We expect the response _body_ to be empty, but the max_response_len
650
2816
        // is not zero because it represents the _total_ length of the response
651
2816
        // (which includes the length of the status line and headers).
652
2816
        //
653
2816
        // A real Tor POST response will always be less than that length, which
654
2816
        // will fit into 3 DATA messages at most. (The reply will be a single
655
2816
        // HTTP line, followed by a Date header.)
656
2816
        1024
657
2816
    }
658

            
659
5632
    fn anonymized(&self) -> AnonymizedRequest {
660
5632
        AnonymizedRequest::Anonymized
661
5632
    }
662
}
663

            
664
/// Encodings that all Tor clients support.
665
const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
666

            
667
/// List all the encodings we accept
668
44
fn all_encodings() -> String {
669
44
    #[allow(unused_mut)]
670
44
    let mut encodings = UNIVERSAL_ENCODINGS.to_string();
671
44
    #[cfg(feature = "xz")]
672
44
    {
673
44
        encodings += ", x-tor-lzma";
674
44
    }
675
44
    #[cfg(feature = "zstd")]
676
44
    {
677
44
        encodings += ", x-zstd";
678
44
    }
679
44

            
680
44
    encodings
681
44
}
682

            
683
/// Add commonly used headers to the HTTP request.
684
///
685
/// (Right now, this is only Accept-Encoding.)
686
2914
fn add_common_headers(
687
2914
    req: http::request::Builder,
688
2914
    anon: AnonymizedRequest,
689
2914
) -> http::request::Builder {
690
2914
    // TODO: gzip, brotli
691
2914
    match anon {
692
        AnonymizedRequest::Anonymized => {
693
            // In an anonymized request, we do not admit to supporting any
694
            // encoding besides those that are always available.
695
2882
            req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
696
        }
697
32
        AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
698
    }
699
2914
}
700

            
701
#[cfg(test)]
702
mod test {
703
    // @@ begin test lint list maintained by maint/add_warning @@
704
    #![allow(clippy::bool_assert_comparison)]
705
    #![allow(clippy::clone_on_copy)]
706
    #![allow(clippy::dbg_macro)]
707
    #![allow(clippy::mixed_attributes_style)]
708
    #![allow(clippy::print_stderr)]
709
    #![allow(clippy::print_stdout)]
710
    #![allow(clippy::single_char_pattern)]
711
    #![allow(clippy::unwrap_used)]
712
    #![allow(clippy::unchecked_duration_subtraction)]
713
    #![allow(clippy::useless_vec)]
714
    #![allow(clippy::needless_pass_by_value)]
715
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
716
    use super::sealed::RequestableInner;
717
    use super::*;
718

            
719
    #[test]
720
    fn test_md_request() -> Result<()> {
721
        let d1 = b"This is a testing digest. it isn";
722
        let d2 = b"'t actually SHA-256.............";
723

            
724
        let mut req = MicrodescRequest::default();
725
        req.push(*d1);
726
        assert!(!req.partial_response_body_ok());
727
        req.push(*d2);
728
        assert!(req.partial_response_body_ok());
729
        assert_eq!(req.max_response_len(), 16 << 10);
730

            
731
        let req = crate::util::encode_request(&req.make_request()?);
732

            
733
        assert_eq!(req,
734
                   format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
735

            
736
        // Try it with FromIterator, and use some accessors.
737
        let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
738
        let ds: Vec<_> = req2.digests().collect();
739
        assert_eq!(ds, vec![d1, d2]);
740
        let req2 = crate::util::encode_request(&req2.make_request()?);
741
        assert_eq!(req, req2);
742

            
743
        Ok(())
744
    }
745

            
746
    #[test]
747
    fn test_cert_request() -> Result<()> {
748
        let d1 = b"This is a testing dn";
749
        let d2 = b"'t actually SHA-256.";
750
        let key1 = AuthCertKeyIds {
751
            id_fingerprint: (*d1).into(),
752
            sk_fingerprint: (*d2).into(),
753
        };
754

            
755
        let d3 = b"blah blah blah 1 2 3";
756
        let d4 = b"I like pizza from Na";
757
        let key2 = AuthCertKeyIds {
758
            id_fingerprint: (*d3).into(),
759
            sk_fingerprint: (*d4).into(),
760
        };
761

            
762
        let mut req = AuthCertRequest::default();
763
        req.push(key1);
764
        assert!(!req.partial_response_body_ok());
765
        req.push(key2);
766
        assert!(req.partial_response_body_ok());
767
        assert_eq!(req.max_response_len(), 32 << 10);
768

            
769
        let keys: Vec<_> = req.keys().collect();
770
        assert_eq!(keys, vec![&key1, &key2]);
771

            
772
        let req = crate::util::encode_request(&req.make_request()?);
773

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

            
777
        let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
778
        let req2 = crate::util::encode_request(&req2.make_request()?);
779
        assert_eq!(req, req2);
780

            
781
        Ok(())
782
    }
783

            
784
    #[test]
785
    fn test_consensus_request() -> Result<()> {
786
        let d1 = RsaIdentity::from_bytes(
787
            &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
788
        )
789
        .unwrap();
790

            
791
        let d2 = b"blah blah blah 12 blah blah blah";
792
        let d3 = SystemTime::now();
793
        let mut req = ConsensusRequest::default();
794

            
795
        let when = httpdate::fmt_http_date(d3);
796

            
797
        req.push_authority_id(d1);
798
        req.push_old_consensus_digest(*d2);
799
        req.set_last_consensus_date(d3);
800
        assert!(!req.partial_response_body_ok());
801
        assert_eq!(req.max_response_len(), (16 << 20) - 1);
802
        assert_eq!(req.old_consensus_digests().next(), Some(d2));
803
        assert_eq!(req.authority_ids().next(), Some(&d1));
804
        assert_eq!(req.last_consensus_date(), Some(d3));
805

            
806
        let req = crate::util::encode_request(&req.make_request()?);
807

            
808
        assert_eq!(req,
809
                   format!("GET /tor/status-vote/current/consensus-microdesc/03479e93ebf3ff2c58c1c9dbf2de9de9c2801b3e.z HTTP/1.0\r\naccept-encoding: {}\r\nif-modified-since: {}\r\nx-or-diff-from-consensus: 626c616820626c616820626c616820313220626c616820626c616820626c6168\r\n\r\n", all_encodings(), when));
810

            
811
        // Request without authorities
812
        let req = ConsensusRequest::default();
813
        let req = crate::util::encode_request(&req.make_request()?);
814
        assert_eq!(req,
815
                   format!("GET /tor/status-vote/current/consensus-microdesc.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
816

            
817
        Ok(())
818
    }
819

            
820
    #[test]
821
    #[cfg(feature = "routerdesc")]
822
    fn test_rd_request_all() -> Result<()> {
823
        let req = RouterDescRequest::all();
824
        assert!(req.partial_response_body_ok());
825
        assert_eq!(req.max_response_len(), 1 << 26);
826

            
827
        let req = crate::util::encode_request(&req.make_request()?);
828

            
829
        assert_eq!(
830
            req,
831
            format!(
832
                "GET /tor/server/all.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
833
                all_encodings()
834
            )
835
        );
836

            
837
        Ok(())
838
    }
839

            
840
    #[test]
841
    #[cfg(feature = "routerdesc")]
842
    fn test_rd_request() -> Result<()> {
843
        let d1 = b"at some point I got ";
844
        let d2 = b"of writing in hex...";
845

            
846
        let mut req = RouterDescRequest::default();
847

            
848
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
849
            digests.push(*d1);
850
        }
851
        assert!(!req.partial_response_body_ok());
852
        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
853
            digests.push(*d2);
854
        }
855
        assert!(req.partial_response_body_ok());
856
        assert_eq!(req.max_response_len(), 16 << 10);
857

            
858
        let req = crate::util::encode_request(&req.make_request()?);
859

            
860
        assert_eq!(req,
861
                   format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
862

            
863
        // Try it with FromIterator, and use some accessors.
864
        let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
865
        let ds: Vec<_> = match req2.requested_descriptors {
866
            RequestedDescs::Digests(ref digests) => digests.iter().collect(),
867
            RequestedDescs::AllDescriptors => Vec::new(),
868
        };
869
        assert_eq!(ds, vec![d1, d2]);
870
        let req2 = crate::util::encode_request(&req2.make_request()?);
871
        assert_eq!(req, req2);
872
        Ok(())
873
    }
874

            
875
    #[test]
876
    #[cfg(feature = "hs-client")]
877
    fn test_hs_desc_download_request() -> Result<()> {
878
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
879
        let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
880
        let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
881
        let hsid = HsBlindId::from(hsid);
882
        let req = HsDescDownloadRequest::new(hsid);
883
        assert!(!req.partial_response_body_ok());
884
        assert_eq!(req.max_response_len(), 50 * 1000);
885

            
886
        let req = crate::util::encode_request(&req.make_request()?);
887

            
888
        assert_eq!(
889
            req,
890
            format!("GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", UNIVERSAL_ENCODINGS)
891
        );
892

            
893
        Ok(())
894
    }
895
}