1
//! Module for helping with dirserver's HTTP interface.
2
//!
3
//! This module is unfortunately necessary as a middleware due to some obscure
4
//! things in Tor, most notably the ".z" extensions.
5

            
6
use cache::StoreCache;
7
use r2d2::Pool;
8
use r2d2_sqlite::SqliteConnectionManager;
9
#[cfg(feature = "dir-plugin-backend")]
10
use tor_dircommon::dir_plugin_backend::DirBackendPlugin;
11
use tor_error::internal;
12

            
13
use std::{
14
    collections::VecDeque,
15
    convert::Infallible,
16
    panic::{AssertUnwindSafe, catch_unwind},
17
    str::FromStr,
18
    sync::Arc,
19
    task::{Context, Poll},
20
    time::Duration,
21
};
22

            
23
use bytes::Bytes;
24
use futures::{Stream, StreamExt};
25
use http::{Method, Request, Response, StatusCode, header};
26
use http_body::{Body, Frame};
27
use hyper::{
28
    body::Incoming,
29
    server::conn::http1::{self},
30
    service::service_fn,
31
};
32
use hyper_util::rt::TokioIo;
33
use rusqlite::{Transaction, params};
34
use tokio::{
35
    io::{AsyncRead, AsyncWrite},
36
    task::JoinSet,
37
    time,
38
};
39
use tracing::warn;
40

            
41
#[cfg(feature = "dir-plugin-backend")]
42
use http_body_util::Full;
43
#[cfg(feature = "dir-plugin-backend")]
44
use std::io::Cursor;
45

            
46
use crate::database::{self, ContentEncoding, DocumentId, sql};
47

            
48
mod cache;
49

            
50
/// A type alias for the functions implementing endpoint logic.
51
///
52
/// An endpoint function is a function of the following form:
53
/// ```rust,ignore
54
/// fn get_consensus(
55
///     tx: &Transaction<'_>,
56
///     requ: &Request<Incoming>
57
/// ) -> Result<Response<Vec<DocumentId>>, Box<dyn std::error::Error + Send>>;
58
/// ```
59
///
60
/// The arguments give the endpoint function access to fixed state of the
61
/// database ([`Transaction`]) and the incoming [`Request`].  The return type is
62
/// a [`Result`] with an arbitrary error that implements [`Send`] and gets logged
63
/// but not returned to the client, which will just receive an `Internal Server Error`.
64
/// The [`Ok`] type of the [`Result`] is a [`Vec`] consisting of [`DocumentId`]
65
/// hashsums identifying (uncompressed) objects in the `store` table.
66
///
67
/// Changes to the database within the [`Transaction`] will (for now) get rolled
68
/// back, thereby giving the endpoint functions just read-only access to the
69
/// database.
70
///
71
/// TODO DIRMIRROR: Document the responsibilities here.
72
///
73
/// TODO DIRMIRROR: The error handling of endpoint functions may need further
74
/// discussions.  Maybe take a look at what other frameworks do?
75
type EndpointFn = fn(
76
    &Transaction,
77
    &Request<Incoming>,
78
) -> Result<Response<Vec<DocumentId>>, Box<dyn std::error::Error + Send>>;
79

            
80
/// A type that implements [`Body`] for a list of [`Arc<[u8]>`] data.
81
///
82
/// This is required because we use the reference counts as first-level return
83
/// types in order to avoid duplicate entries of the same data in memory.
84
/// See the documentation of [`StoreCache`] for more information on that.
85
struct DocumentBody(VecDeque<Arc<[u8]>>);
86

            
87
/// Representation of an endpoint, uniquely identified by a [`Method`] and path
88
/// pair followed by an appropriate [`EndpointFn`].
89
///
90
/// The path itself is a special string that refers to the endpoint at which this
91
/// resource should be available.  It supports a pattern-matching like syntax
92
/// through the use of the asterisk `*` character.
93
///
94
/// For example:
95
/// `/tor/status-vote/current/consensus` will match the URL exactly, whereas
96
/// `/tor/status-vote/current/*` will match every string that is in the
97
/// fourth component; such as `/tor/status-vote/current/consensus` or
98
/// `/tor/status-vote/current/consensus-microdesc`; it will however not
99
/// match in a prefix-like syntax, such as
100
/// `/tor/status-vote/current/consensus-microdesc/diff`.
101
///
102
/// In the case of non-unique matches, the first match wins.  Also, because
103
/// of wildcards, matching takes place in a `O(n)` fashion, so be sure to
104
/// to keep the `n` at a reasonable size.  This should not be much of a
105
/// problem for Tor applications though, because the list of endpoints is
106
/// reasonable (less than 30).
107
///
108
/// TODO: The entire asterisk matching is not so super nice, primarily because
109
/// it removes compile-time semantic checks; however, I cannot really think
110
/// of a much cleaner way that would not involve lots of boilerplate.
111
/// The most minimal "clean" way could be to do `path: &Option<&'static str>`
112
/// but I am not sure if this overhead is worth it, i.e.:
113
/// * `/tor/status-vote/current/*/diff/*/*`
114
/// * `[Some(""), Some("tor"), Some("status-vote"), Some("current"), None, ...]`
115
///   Maybe a macro could help here though ...
116
type Endpoint = (Method, &'static str, EndpointFn);
117

            
118
/// Representation of the core HTTP server.
119
#[derive(Debug)]
120
pub(crate) struct HttpServer {
121
    /// List of [`Endpoint`] entries.
122
    endpoints: Vec<Endpoint>,
123
    /// Access to the database pool.
124
    pool: Pool<SqliteConnectionManager>,
125
}
126

            
127
impl Body for DocumentBody {
128
    type Data = Bytes;
129
    type Error = Infallible;
130

            
131
8
    fn poll_frame(
132
8
        mut self: std::pin::Pin<&mut Self>,
133
8
        _cx: &mut Context<'_>,
134
8
    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
135
        Poll::Ready(
136
8
            self.0
137
8
                .pop_front()
138
10
                .map(|bytes| Ok(Frame::data(Bytes::from_owner(bytes)))),
139
        )
140
8
    }
141
}
142

            
143
impl HttpServer {
144
    /// Creates a new [`HttpServer`] with a given [`Vec`] of [`Endpoint`] entries
145
    /// alongside access to the database [`Pool`].
146
2
    pub(crate) fn new(endpoints: Vec<Endpoint>, pool: Pool<SqliteConnectionManager>) -> Self {
147
2
        Self { endpoints, pool }
148
2
    }
149

            
150
    /// Bluntly launches an HTTP server only serving from the given backend.
151
    ///
152
    /// Absolutely not suited for anything in production as it comes with
153
    /// various limitations.  Primarily intended as an intermediate abstraction
154
    /// for relay development.
155
    #[cfg(feature = "dir-plugin-backend")]
156
    pub(crate) async fn serve_backend<I, S, E, B>(
157
        mut listener: I,
158
        backend: B,
159
    ) -> Result<(), tor_error::Bug>
160
    where
161
        I: Stream<Item = Result<S, E>> + Unpin,
162
        S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
163
        E: std::error::Error,
164
        B: DirBackendPlugin,
165
    {
166
        // Creates a failing HTTP resposne while satisfying the hyper requirements.
167
        let failure = |code| -> _ {
168
            Response::builder()
169
                .status(code)
170
                .body(Default::default())
171
                .expect("response builder should not fail")
172
        };
173

            
174
        // We need to wrap the backend as an Arc, as the value would otherwise
175
        // not live long enough.
176
        let backend = Arc::new(backend);
177
        let mut tasks: JoinSet<Result<(), hyper::Error>> = JoinSet::new();
178
        loop {
179
            tokio::select! {
180
                res = listener.next() => match res {
181
                    // Connection successfully accepted.
182
                    Some(Ok(s)) => {
183
                        let stream = TokioIo::new(s);
184

            
185
                        // Two Arc clones required.  First is to be able to run
186
                        // this in an endless loop and second one is required
187
                        // because hyper requires the function to be Fn, i.e.
188
                        // meaning it may not capture from it's surrounding
189
                        // state.
190
                        let backend = backend.clone();
191
                        let service = service_fn(move |requ: Request<Incoming>| {
192
                            let backend = backend.clone();
193
                            async move {
194
                                if requ.method() != Method::GET {
195
                                    warn!("Unsupported method: {}", requ.method());
196
                                    // dir-spec does not allow StatusCode::METHOD_NOT_ALLOWED.
197
                                    return Ok(failure(StatusCode::BAD_REQUEST));
198
                                }
199
                                if !requ.body().is_end_stream() {
200
                                    warn!("HTTP GET with non-empty body?");
201
                                    return Ok(failure(StatusCode::BAD_REQUEST));
202
                                }
203
                                // Convert Request::<Incoming> to Request::<()>.
204
                                let requ = requ.map(|_| ());
205

            
206
                                // Convert the Box<[u8]> to something hyper accepts.
207
                                backend
208
                                    .get(&requ)
209
                                    .map(|resp| resp.map(|body| Full::new(Cursor::new(body))))
210
                            }
211
                        });
212
                        tasks.spawn(http1::Builder::new().serve_connection(stream, service));
213
                    },
214

            
215
                    // There has been an error in accepting the connection.
216
                    Some(Err(e)) => {
217
                        warn!("listener accept failure: {e}");
218
                        continue;
219
                    }
220

            
221
                    // This should not happen due to ownership.
222
                    None => return Err(internal!("listener was closed externally?")),
223
                },
224

            
225
                // A hyper task we monitored in our tasks has exiteed.
226
                Some(res) = tasks.join_next() => match res {
227
                    Ok(Ok(())) => {},
228
                    Ok(Err(e)) => warn!("client task encountered an error: {e}"),
229
                    Err(e) => warn!("client task exited ungracefully: {e}"),
230
                },
231

            
232
            }
233
        }
234
    }
235

            
236
    /// Runs the server endlessly in the current task.
237
    ///
238
    /// This function does not fail, because all errors that could potentially
239
    /// occur, occur in further sub-tasks spawned by it and handled appropriately,
240
    /// that is usually logging the error and continuing the execution.
241
2
    pub(crate) async fn serve<I, S, E>(self, mut listener: I) -> Result<(), tor_error::Bug>
242
2
    where
243
2
        I: Stream<Item = Result<S, E>> + Unpin,
244
2
        S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
245
2
        E: std::error::Error,
246
2
    {
247
2
        let cache = Arc::new(StoreCache::new());
248
2
        let endpoints: Arc<[Endpoint]> = self.endpoints.into();
249
2
        let pool = self.pool;
250

            
251
        // We operate exclusively in JoinSets so that everything gets aborted
252
        // nicely in order without causing any sort of leaks.
253
2
        let mut hyper_tasks: JoinSet<Result<(), hyper::Error>> = JoinSet::new();
254
2
        let mut misc_tasks: JoinSet<()> = JoinSet::new();
255

            
256
        // Spawn a simple garbage collection task that periodically removes
257
        // dead references, just in case, from the StoreCache.
258
2
        misc_tasks.spawn({
259
2
            let cache = cache.clone();
260
2
            async move {
261
                loop {
262
2
                    cache.gc();
263
2
                    time::sleep(Duration::from_secs(60)).await;
264
                }
265
            }
266
        });
267

            
268
        loop {
269
4
            tokio::select! {
270
4
                res = listener.next() => match res {
271
                    // Connection successfully accepted.
272
2
                    Some(Ok(s)) => Self::dispatch_stream(&cache, &endpoints, &pool, &mut hyper_tasks, s),
273

            
274
                    // There has been an error in accepting the connection.
275
                    Some(Err(e)) => {
276
                        warn!("listener accept failure: {e}");
277
                        continue;
278
                    }
279

            
280
                    // This should not happen due to ownership.
281
                    None => return Err(internal!("listener was closed externally?")),
282
                },
283

            
284
                // A hyper task we monitored in our tasks has exiteed.
285
                //
286
                // We distinguish between graceful and ungraceful errors, with
287
                // the latter one being errors related to a failure in tokio's
288
                // joining itself, such as if the underlying task panic'ed;
289
                // whereas graceful errors are logical application level errors.
290
4
                Some(res) = hyper_tasks.join_next() => match res {
291
                    Ok(Ok(())) => {},
292
                    Ok(Err(e)) => warn!("client task encountered an error: {e}"),
293
                    Err(e) => warn!("client task exited ungracefully: {e}"),
294
                },
295

            
296
            }
297
        }
298
    }
299

            
300
    /// Dispatches a new [`Stream`] into an existing [`JoinSet`].
301
2
    fn dispatch_stream<S: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
302
2
        cache: &Arc<StoreCache>,
303
2
        endpoints: &Arc<[Endpoint]>,
304
2
        pool: &Pool<SqliteConnectionManager>,
305
2
        tasks: &mut JoinSet<Result<(), hyper::Error>>,
306
2
        stream: S,
307
2
    ) {
308
2
        let stream = TokioIo::new(stream);
309

            
310
        // Create the `service_fn` to pass to `hyper`.
311
        //
312
        // Unfortunately, we have to clone the reference counter of all shared
313
        // objects two times here.  The first clone is required to not move
314
        // it into the `service_fn`, the second one is required to
315
        // circumvent a hyper limitation, namely that a service function
316
        // requires a `Fn`, not an `FnMut`, which would allow capturing values
317
        // from the environment natively.
318
2
        let cache = cache.clone();
319
2
        let endpoints = endpoints.clone();
320
2
        let pool = pool.clone();
321
4
        let service = service_fn(move |requ| {
322
4
            let cache = cache.clone();
323
4
            let endpoints = endpoints.clone();
324
4
            let pool = pool.clone();
325
4
            async move { Self::handler(cache, endpoints, pool, requ).await }
326
4
        });
327

            
328
2
        tasks.spawn(http1::Builder::new().serve_connection(stream, service));
329
2
    }
330

            
331
    /// A small wrapper function that creates a read-only or read-write
332
    /// [`Transaction`] based upon the [`Method`] and continues execution in
333
    /// [`Self::handler_tx`].
334
    #[allow(clippy::unused_async)] // TODO
335
4
    async fn handler(
336
4
        cache: Arc<StoreCache>,
337
4
        endpoints: Arc<[Endpoint]>,
338
4
        pool: Pool<SqliteConnectionManager>,
339
4
        requ: Request<Incoming>,
340
6
    ) -> Result<Response<DocumentBody>, Infallible> {
341
        // TODO: This would be the place to either use read_tx or rw_tx depending
342
        // on the method, but given that this is all GET at the moment, just go
343
        // with read_tx.
344
        Ok(
345
4
            database::read_tx(&pool, |tx| Self::handler_tx(&cache, &endpoints, tx, &requ))
346
4
                .unwrap_or_else(|e| {
347
                    warn!("database error: {e}");
348
                    Self::empty_response(StatusCode::INTERNAL_SERVER_ERROR)
349
                }),
350
        )
351
4
    }
352

            
353
    /// A big monolithic function that handles incoming request with a consist
354
    /// view upon the database.
355
    ///
356
    /// The function works in eight steps which are documented with more detail
357
    /// within the code:
358
    /// 1. Determine the compression algorithm
359
    /// 2. Select an [`EndpointFn`] by matching the path component
360
    /// 3. Call the [`EndpointFn`] to obtain various [`DocumentId`]s
361
    /// 4. Map the [`DocumentId`]s to their compressed counterpart
362
    /// 5. Query the [`StoreCache`] with the [`DocumentId`] and [`Transaction`]
363
    ///    handle to store the document ref
364
    /// 6. Compose the [`Response`]
365
    ///
366
    /// TODO DIRMIRROR: Implement [`Method::HEAD`].
367
4
    fn handler_tx(
368
4
        cache: &Arc<StoreCache>,
369
4
        endpoints: &[Endpoint],
370
4
        tx: &Transaction,
371
4
        requ: &Request<Incoming>,
372
4
    ) -> Response<DocumentBody> {
373
        // (1) Determine the compression algorithm
374
        //
375
        // This step determines the compression algorithm, according to:
376
        // https://spec.torproject.org/dir-spec/standards-compliance.html#http-headers.
377
4
        let (encoding, advertise_encoding) = Self::determine_encoding(requ);
378

            
379
        // (2) Select an `EndpointFn` by matching the path component
380
4
        let endpoint_fn = match Self::match_endpoint(endpoints, requ) {
381
4
            Some((_, _, endpoint_fn)) => endpoint_fn,
382
            None => return Self::empty_response(StatusCode::NOT_FOUND),
383
        };
384

            
385
        // (3) Call the `EndpointFn` to obtain various `DocumentId`s
386
6
        let endpoint_fn_resp = match catch_unwind(AssertUnwindSafe(|| endpoint_fn(tx, requ))) {
387
            // Everything went successful.
388
4
            Ok(Ok(r)) => r,
389

            
390
            // The endpoint function gracefully failed with an error.
391
            Ok(Err(e)) => {
392
                warn!(
393
                    "{} {}: endpoint function failed: {e}",
394
                    requ.method(),
395
                    requ.uri()
396
                );
397
                return Self::empty_response(StatusCode::INTERNAL_SERVER_ERROR);
398
            }
399

            
400
            // The endpoint function unexpectedly crashed.
401
            Err(_) => {
402
                warn!(
403
                    "{} {}: endpoint function crashed",
404
                    requ.method(),
405
                    requ.uri()
406
                );
407
                return Self::empty_response(StatusCode::INTERNAL_SERVER_ERROR);
408
            }
409
        };
410
4
        let (endpoint_fn_parts, docids) = endpoint_fn_resp.into_parts();
411

            
412
        // (4) Map the docids to their compressed counterpart
413
4
        let docids = docids
414
4
            .into_iter()
415
6
            .map(|docid| Self::map_encoding(tx, docid, encoding))
416
4
            .collect::<Result<Vec<_>, _>>();
417
4
        let docids = match docids {
418
4
            Ok(s) => s,
419
            Err(e) => {
420
                warn!(
421
                    "{} {}: unable to find compressed document: {e}",
422
                    requ.method(),
423
                    requ.uri()
424
                );
425
                return Self::empty_response(StatusCode::INTERNAL_SERVER_ERROR);
426
            }
427
        };
428

            
429
        // (5) Query the [`StoreCache`] with the [`DocumentId`] and
430
        //     [`Transaction`] handle to store the document ref.
431
4
        let mut documents = VecDeque::new();
432
4
        for docid in docids {
433
4
            let document = match cache.get(tx, docid) {
434
4
                Ok(document) => document,
435
                Err(e) => {
436
                    warn!(
437
                        "{} {}: unable to access the cache: {e}",
438
                        requ.method(),
439
                        requ.uri()
440
                    );
441
                    return Self::empty_response(StatusCode::INTERNAL_SERVER_ERROR);
442
                }
443
            };
444

            
445
4
            documents.push_back(document);
446
        }
447

            
448
        // (6) Compose the `Response`.
449
        //
450
        // The composing primarily consists of building a response from the parts
451
        // of the intermediate response plus optionally adding a Content-Encoding
452
        // header.
453
4
        let mut resp = Response::from_parts(endpoint_fn_parts, DocumentBody(documents));
454
4
        if advertise_encoding {
455
            // Add the Content-Encoding header, if necessary.
456
            resp.headers_mut().insert(
457
                header::CONTENT_ENCODING,
458
                encoding
459
                    .to_string()
460
                    .try_into()
461
                    .expect("strum serialized a non-valid header?!?"),
462
            );
463
4
        }
464

            
465
4
        resp
466
4
    }
467

            
468
    /// Determines the [`ContentEncoding`] based on the path and the value of [`header::ACCEPT_ENCODING`].
469
    ///
470
    /// This function returns a tuple containing the determined [`ContentEncoding`]
471
    /// alongside a boolean that indicates whether [`header::CONTENT_ENCODING`]
472
    /// should be set or not with the value of the just determined
473
    /// [`ContentEncoding`].
474
16
    fn determine_encoding<B: Body>(requ: &Request<B>) -> (ContentEncoding, bool) {
475
16
        let z_suffix = requ.uri().path().ends_with(".z");
476

            
477
        // TODO: Refactor this in a flat fashion once we get stable If-Let-Chains
478
        // by upgrading MSVC to 1.88.
479
        //
480
        // This works by branching the parameters into the following four branches:
481
        // 1. Accept-Encoding && ".z" URL
482
        // 2. Accept-Encoding && No ".z" URL
483
        // 3. No Accept-Encoding && ".z" URL
484
        // 4. No Accept-Encoding && No "z" URL
485

            
486
        // Technically we could use an else-if here, but given the branching
487
        // I explained above, I would like to keep it in the nested fashion
488
        // once we got stable If-Let.
489
        #[allow(clippy::collapsible_else_if)]
490
16
        if let Some(accept_encoding) = requ.headers().get(header::ACCEPT_ENCODING) {
491
            // Parse the accept_encoding value by splitting it at "," and then
492
            // parse each trimmed component as a ContentEncoding.  Unsupported
493
            // ContentEncodings are ignored.
494
8
            let encodings = accept_encoding
495
8
                .to_str()
496
8
                .unwrap_or("")
497
8
                .split(",")
498
14
                .filter_map(|encoding| ContentEncoding::from_str(encoding.trim()).ok())
499
8
                .collect::<Vec<_>>();
500

            
501
8
            if z_suffix {
502
                // (1) Accept-Encoding && ".z" URL
503
                //
504
                // From the specification:
505
                // > If the client does send an Accept-Encoding header along with
506
                // > a .z URL, the server SHOULD treat the request the same way
507
                // > as for the URL without the .z.  If deflate is included in the
508
                // > Accept-Encoding, the response MUST be encoded, once, with
509
                // > an encoding advertised by the client, and be accompanied by
510
                // > an appropriate Content-Encoding.
511

            
512
                // We do not check whether Accept-Encoding contains deflate,
513
                // because the specification gives us the assurance.
514
                // TODO: Maybe we should?
515
2
                (ContentEncoding::Deflate, true)
516
            } else {
517
                // (2) Accept-Encoding && No ".z" URL
518
6
                if let Some(encoding) = encodings.first() {
519
                    // Pick the first found encoding and include it in the header,
520
                    // if it is not the identity encoding.
521
4
                    let include_in_header = *encoding != ContentEncoding::Identity;
522
4
                    (*encoding, include_in_header)
523
                } else {
524
                    // No supported encodings were found, fallback to identity
525
                    // and do not provide a Content-Encoding header.
526
                    // This is effectively equivalent to (4).
527
2
                    (ContentEncoding::Identity, false)
528
                }
529
            }
530
        } else {
531
8
            if z_suffix {
532
                // (3) No Accept-Encoding && ".z" URL
533
                //
534
                // From the specification:
535
                // > If the client does not send an Accept-Encoding header along
536
                // > with a .z URL, the server MUST send the response compressed
537
                // > with deflate and SHOULD NOT send a Content-Encoding header.
538
4
                (ContentEncoding::Deflate, false)
539
            } else {
540
                // (4) No Accept-Encoding && No ".z" URL
541
4
                (ContentEncoding::Identity, false)
542
            }
543
        }
544
16
    }
545

            
546
    /// Matches an incoming request to an existing endpoint.
547
    ///
548
    /// The matching works in a first-match wins fashion.
549
    /// An endpoint is said to be matched when the following two properties for
550
    /// the incoming request hold true:
551
    /// * Both [`Method`] values are the same.
552
    /// * Each component of the URL path is equal at the respective position or,
553
    ///   in the case of the endpoint path, is a wildcard.
554
40
    fn match_endpoint<'a, B: Body>(
555
40
        endpoints: &'a [Endpoint],
556
40
        requ: &Request<B>,
557
40
    ) -> Option<&'a Endpoint> {
558
40
        let requ_path = requ.uri().path();
559
40
        let requ_path = requ_path.strip_suffix(".z").unwrap_or(requ_path);
560
40
        let requ_path = requ_path.split('/').collect::<Vec<_>>();
561
40
        let mut res = None;
562
116
        for tuple in endpoints.iter() {
563
116
            let (method, path, _endpoint_fn) = tuple;
564
116
            let path = path.split('/').collect::<Vec<_>>();
565

            
566
            // Filter the method out first.
567
116
            if requ.method() != method {
568
                continue;
569
116
            }
570

            
571
            // Now that the method is filtered out, perform the path matching
572
            // algorithm.
573
            //
574
            // The path algorithm works as follows:
575
            // 1. Check whether `path.len() == requ_path.len()`, for a match,
576
            //    two paths must have the same number of path components.
577
            // 2. Initialize `is_match = true`.
578
            // 3. Walk over the path components in pairs (i.e. compare first
579
            //    component of `path` with the first component of `requ_path`, ...)
580
            //    and check for each component tuple, whether they are equal or
581
            //    whether the component at the current position in path is a
582
            //    wildcard component, that is, a component that equals `*`.
583
            //
584
            //    Stop immediately the moment
585
            //    `path[i] == requ_path[i] || path[i] == "*"` yields `false`;
586
            //     set `is_match = false`.
587
            // 4. Check the result of `is_match`.
588

            
589
            // Paths must have the same number of components in order to match.
590
            // An inequality here means instant disqualification.
591
116
            if path.len() != requ_path.len() {
592
64
                continue;
593
52
            }
594

            
595
            // Iterate over the path component for component until we disqualify
596
            // for a match.
597
52
            let mut is_match = true;
598
164
            for (this, incoming) in path.iter().zip(&requ_path) {
599
164
                if this == incoming || *this == "*" {
600
138
                    continue;
601
                } else {
602
26
                    is_match = false;
603
26
                    break;
604
                }
605
            }
606

            
607
            // Stop on the first match, propagate the match to the outside.
608
52
            if is_match {
609
26
                res = Some(tuple);
610
26
                break;
611
26
            }
612
        }
613

            
614
40
        res
615
40
    }
616

            
617
    /// Looks up the corresponding [`DocumentId`] for a given [`DocumentId`] and
618
    /// a [`ContentEncoding`].
619
14
    fn map_encoding(
620
14
        tx: &Transaction,
621
14
        docid: DocumentId,
622
14
        encoding: ContentEncoding,
623
14
    ) -> Result<DocumentId, rusqlite::Error> {
624
        // If the encoding is the identity, do not bother about it any further.
625
14
        if encoding == ContentEncoding::Identity {
626
4
            return Ok(docid);
627
10
        }
628

            
629
10
        let mut stmt = tx.prepare_cached(sql!(
630
10
            "
631
10
            SELECT compressed_docid
632
10
            FROM compressed_document
633
10
              WHERE identity_docid = ?1
634
10
                AND algorithm = ?2
635
10
            "
636
10
        ))?;
637
10
        let compressed_docid =
638
15
            stmt.query_one(params![docid, encoding.to_string()], |row| row.get(0))?;
639

            
640
10
        Ok(compressed_docid)
641
14
    }
642

            
643
    /// Generates an empty response with a given [`StatusCode`].
644
    fn empty_response(status: StatusCode) -> Response<DocumentBody> {
645
        // TODO DIRMIRROR: Statically assert that.
646
        Response::builder()
647
            .status(status)
648
            .body(DocumentBody(VecDeque::new()))
649
            .expect("response builder for empty response failed?!?")
650
    }
651
}
652

            
653
#[cfg(test)]
654
pub(in crate::http) mod test {
655
    // @@ begin test lint list maintained by maint/add_warning @@
656
    #![allow(clippy::bool_assert_comparison)]
657
    #![allow(clippy::clone_on_copy)]
658
    #![allow(clippy::dbg_macro)]
659
    #![allow(clippy::mixed_attributes_style)]
660
    #![allow(clippy::print_stderr)]
661
    #![allow(clippy::print_stdout)]
662
    #![allow(clippy::single_char_pattern)]
663
    #![allow(clippy::unwrap_used)]
664
    #![allow(clippy::unchecked_time_subtraction)]
665
    #![allow(clippy::useless_vec)]
666
    #![allow(clippy::needless_pass_by_value)]
667
    #![allow(clippy::string_slice)] // See arti#2571
668
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
669
    use crate::database;
670

            
671
    use super::*;
672

            
673
    use std::{
674
        io::{Cursor, Write},
675
        str::FromStr,
676
    };
677

            
678
    use flate2::{
679
        Compression,
680
        write::{DeflateDecoder, DeflateEncoder, GzEncoder},
681
    };
682
    use http::Version;
683
    use http_body_util::{BodyExt, Empty};
684
    use lazy_static::lazy_static;
685
    use tokio::{
686
        net::{TcpListener, TcpStream},
687
        task,
688
    };
689
    use tokio_stream::wrappers::TcpListenerStream;
690

            
691
    pub(in crate::http) const IDENTITY: &str = "Lorem ipsum dolor sit amet.";
692

            
693
    lazy_static! {
694
        pub(in crate::http) static ref IDENTITY_DOCID: DocumentId =
695
            hex_to_docid("DD14CBBF0E74909AAC7F248A85D190AFD8DA98265CEF95FC90DFDDABEA7C2E66");
696
        pub(in crate::http) static ref DEFLATE_DOCID: DocumentId =
697
            hex_to_docid("07564DD13A7F4A6AD98B997F2938B1CEE11F8C7F358C444374521BA54D50D05E");
698
        pub(in crate::http) static ref GZIP_DOCID: DocumentId =
699
            hex_to_docid("1518107D3EF1EC6EAC3F3249DF26B2F845BC8226C326309F4822CAEF2E664104");
700
        pub(in crate::http) static ref XZ_STD_DOCID: DocumentId =
701
            hex_to_docid("17416948501F8E627CC9A8F7EFE7A2F32788D53CB84A5F67AC8FD4C1B59184CF");
702
        pub(in crate::http) static ref X_TOR_LZMA_DOCID: DocumentId =
703
            hex_to_docid("B5549F79A69113BDAF3EF0AD1D7D339D0083BC31400ECEE1B673F331CF26E239");
704
    }
705

            
706
6
    pub(in crate::http) fn create_test_db_pool() -> Pool<SqliteConnectionManager> {
707
6
        let pool = database::open("").unwrap();
708
6
        database::rw_tx(&pool, init_test_db).unwrap();
709
6
        pool
710
6
    }
711

            
712
10
    fn hex_to_docid(s: &str) -> DocumentId {
713
10
        let data: [u8; 32] = hex::decode(s).unwrap().try_into().unwrap();
714
10
        data.into()
715
10
    }
716

            
717
6
    fn init_test_db(tx: &Transaction) {
718
6
        assert_eq!(DocumentId::digest(IDENTITY.as_bytes()), *IDENTITY_DOCID);
719

            
720
6
        let deflate = {
721
6
            let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
722
6
            encoder.write_all(IDENTITY.as_bytes()).unwrap();
723
6
            encoder.finish().unwrap()
724
        };
725
6
        assert_eq!(DocumentId::digest(&deflate), *DEFLATE_DOCID);
726

            
727
6
        let gzip = {
728
6
            let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
729
6
            encoder.write_all(IDENTITY.as_bytes()).unwrap();
730
6
            encoder.finish().unwrap()
731
        };
732
6
        assert_eq!(DocumentId::digest(&gzip), *GZIP_DOCID);
733

            
734
6
        let xz_std = zstd::encode_all(IDENTITY.as_bytes(), 3).unwrap();
735
6
        assert_eq!(DocumentId::digest(&xz_std), *XZ_STD_DOCID);
736

            
737
6
        let mut x_tor_lzma = Vec::new();
738
6
        lzma_rs::lzma_compress(&mut Cursor::new(IDENTITY), &mut x_tor_lzma).unwrap();
739
6
        assert_eq!(DocumentId::digest(&x_tor_lzma), *X_TOR_LZMA_DOCID);
740

            
741
6
        tx.execute(
742
6
            sql!(
743
6
                "
744
6
                INSERT INTO store(docid, content) VALUES
745
6
                (?1, ?2), -- identity
746
6
                (?3, ?4), -- deflate
747
6
                (?5, ?6), -- gzip
748
6
                (?7, ?8), -- xzstd
749
6
                (?9, ?10) -- lzma
750
6
                "
751
6
            ),
752
6
            params![
753
6
                *IDENTITY_DOCID,
754
6
                IDENTITY.as_bytes().to_vec(),
755
6
                *DEFLATE_DOCID,
756
6
                deflate,
757
6
                *GZIP_DOCID,
758
6
                gzip,
759
6
                *XZ_STD_DOCID,
760
6
                xz_std,
761
6
                *X_TOR_LZMA_DOCID,
762
6
                x_tor_lzma
763
6
            ],
764
6
        )
765
6
        .unwrap();
766

            
767
6
        tx.execute(
768
6
            sql!(
769
6
                "
770
6
                INSERT INTO compressed_document(algorithm, identity_docid, compressed_docid) VALUES
771
6
                ('deflate', ?1, ?2),
772
6
                ('gzip', ?1, ?3),
773
6
                ('x-zstd', ?1, ?4),
774
6
                ('x-tor-lzma', ?1, ?5)
775
6
                "
776
6
            ),
777
6
            params![
778
6
                *IDENTITY_DOCID,
779
6
                *DEFLATE_DOCID,
780
6
                *GZIP_DOCID,
781
6
                *XZ_STD_DOCID,
782
6
                *X_TOR_LZMA_DOCID
783
6
            ],
784
6
        )
785
6
        .unwrap();
786
6
    }
787

            
788
    #[test]
789
2
    fn content_encoding() {
790
2
        assert_eq!(ContentEncoding::Identity.to_string(), "identity");
791
2
        assert_eq!(
792
2
            ContentEncoding::from_str("identity").unwrap(),
793
            ContentEncoding::Identity
794
        );
795

            
796
2
        assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
797
2
        assert_eq!(
798
2
            ContentEncoding::from_str("DeFlaTe").unwrap(),
799
            ContentEncoding::Deflate
800
        );
801

            
802
2
        assert_eq!(ContentEncoding::Gzip.to_string(), "gzip");
803
2
        assert_eq!(
804
2
            ContentEncoding::from_str("GzIP").unwrap(),
805
            ContentEncoding::Gzip
806
        );
807
2
        assert_eq!(ContentEncoding::XZstd.to_string(), "x-zstd");
808
2
        assert_eq!(
809
2
            ContentEncoding::from_str("x-zStD").unwrap(),
810
            ContentEncoding::XZstd
811
        );
812

            
813
2
        assert_eq!(ContentEncoding::XTorLzma.to_string(), "x-tor-lzma");
814
2
        assert_eq!(
815
2
            ContentEncoding::from_str("x-tOr-lzMa").unwrap(),
816
            ContentEncoding::XTorLzma
817
        );
818
2
    }
819

            
820
    #[test]
821
2
    fn determine_encoding() {
822
        // 1. Accept-Encoding && ".z" URL.
823
2
        let requ = Request::builder()
824
2
            .header("Accept-Encoding", "deflate,identity  ,  gzip")
825
2
            .uri("/foo.z")
826
2
            .body(String::new())
827
2
            .unwrap();
828
2
        assert_eq!(
829
2
            HttpServer::determine_encoding(&requ),
830
            (ContentEncoding::Deflate, true)
831
        );
832

            
833
        // 2a. Valid Accept-Encoding && No ".z" URL.
834
2
        let requ = Request::builder()
835
2
            .header("Accept-Encoding", "  gzip   ")
836
2
            .uri("/foo")
837
2
            .body(String::new())
838
2
            .unwrap();
839
2
        assert_eq!(
840
2
            HttpServer::determine_encoding(&requ),
841
            (ContentEncoding::Gzip, true)
842
        );
843

            
844
        // 2b. Identity Accept-Encoding && No ".z" URL.
845
2
        let requ = Request::builder()
846
2
            .header("Accept-Encoding", "identity")
847
2
            .uri("/foo")
848
2
            .body(String::new())
849
2
            .unwrap();
850
2
        assert_eq!(
851
2
            HttpServer::determine_encoding(&requ),
852
            (ContentEncoding::Identity, false)
853
        );
854

            
855
        // 2c. Invalid Accept-Encoding && No ".z" URL.
856
2
        let requ = Request::builder()
857
2
            .header("Accept-Encoding", "  unSuppOrtEd_EncODing_SCHEMA , yeah   ")
858
2
            .uri("/foo")
859
2
            .body(String::new())
860
2
            .unwrap();
861
2
        assert_eq!(
862
2
            HttpServer::determine_encoding(&requ),
863
            (ContentEncoding::Identity, false)
864
        );
865

            
866
        // 3. No Accept-Encoding && ".z" URL
867
2
        let requ = Request::builder()
868
2
            .uri("/foo.z")
869
2
            .body(String::new())
870
2
            .unwrap();
871
2
        assert_eq!(
872
2
            HttpServer::determine_encoding(&requ),
873
            (ContentEncoding::Deflate, false)
874
        );
875

            
876
        // 4. No Accept-Encoding && No ".z" URL
877
2
        let requ = Request::builder().uri("/foo").body(String::new()).unwrap();
878
2
        assert_eq!(
879
2
            HttpServer::determine_encoding(&requ),
880
            (ContentEncoding::Identity, false)
881
        );
882
2
    }
883

            
884
    #[test]
885
2
    fn match_endpoint() {
886
        /// Dummy call back that does nothing and is not even called.
887
        fn dummy(
888
            _: &Transaction,
889
            _: &Request<Incoming>,
890
        ) -> Result<Response<Vec<DocumentId>>, Box<dyn std::error::Error + Send>> {
891
            todo!()
892
        }
893

            
894
2
        let endpoints: Vec<Endpoint> = vec![
895
2
            (Method::GET, "/foo/bar/baz", dummy),
896
2
            (Method::GET, "/foo/*/baz", dummy),
897
2
            (Method::GET, "/bar/*", dummy),
898
2
            (Method::GET, "/", dummy),
899
        ];
900

            
901
        /// Basically a domain specific [`assert_eq`] that works by comparing
902
        /// pointers instead of a deep comparison.
903
        macro_rules! check_match {
904
            ($uri:literal, $endpoint:literal) => {
905
                let requ = Request::builder().uri($uri).body(String::new()).unwrap();
906
                let left: *const Endpoint = HttpServer::match_endpoint(&endpoints, &requ).unwrap();
907
                let right: *const Endpoint = &endpoints[$endpoint];
908
                assert_eq!(left, right);
909
            };
910
        }
911

            
912
        macro_rules! check_no_match {
913
            ($uri:literal) => {
914
                let requ = Request::builder().uri($uri).body(String::new()).unwrap();
915
                assert!(HttpServer::match_endpoint(&endpoints, &requ).is_none());
916
            };
917
        }
918

            
919
2
        check_match!("/foo/bar/baz", 0);
920
2
        check_match!("/foo/bar/baz.z", 0);
921
2
        check_no_match!("/foo/bar/baz1");
922
2
        check_no_match!("/foo/bar/baz/");
923

            
924
2
        check_match!("/foo/I_DONT_CARE/baz", 1);
925
2
        check_match!("/foo/I_DONT_CARE/baz.z", 1);
926
2
        check_match!("/foo//baz", 1);
927
2
        check_no_match!("/foo/");
928
2
        check_no_match!("/foo/foo");
929
2
        check_no_match!("/foo/foo/foo");
930

            
931
2
        check_match!("/bar/", 2);
932
2
        check_match!("/bar/.z", 2);
933
2
        check_match!("/bar/foo", 2);
934
2
        check_match!("/bar/foo.z", 2);
935
2
        check_no_match!("/bar/foo/");
936
2
        check_no_match!("/bar/foo/foo");
937

            
938
2
        check_match!("/", 3);
939
2
        check_match!("/.z", 3);
940
2
    }
941

            
942
    #[test]
943
2
    fn map_encoding() {
944
2
        let pool = create_test_db_pool();
945

            
946
2
        let data = [
947
2
            (ContentEncoding::Identity, *IDENTITY_DOCID),
948
2
            (ContentEncoding::Deflate, *DEFLATE_DOCID),
949
2
            (ContentEncoding::Gzip, *GZIP_DOCID),
950
2
            (ContentEncoding::XZstd, *XZ_STD_DOCID),
951
2
            (ContentEncoding::XTorLzma, *X_TOR_LZMA_DOCID),
952
2
        ];
953

            
954
3
        database::read_tx(&pool, |tx| {
955
10
            for (encoding, compressed_docid) in data {
956
10
                println!("{encoding}");
957
10
                assert_eq!(
958
10
                    HttpServer::map_encoding(tx, *IDENTITY_DOCID, encoding).unwrap(),
959
                    compressed_docid
960
                );
961
            }
962
2
        })
963
2
        .unwrap();
964
2
    }
965

            
966
    #[tokio::test]
967
3
    async fn basic_http_server() {
968
        // This is a stupid clippy false positive.
969
        #[allow(clippy::unnecessary_wraps)]
970
4
        fn identity(
971
4
            _tx: &Transaction<'_>,
972
4
            _requ: &Request<Incoming>,
973
4
        ) -> Result<Response<Vec<DocumentId>>, Box<dyn std::error::Error + Send>> {
974
4
            Ok(Response::new(vec![*IDENTITY_DOCID]))
975
4
        }
976

            
977
2
        let pool = create_test_db_pool();
978
2
        let server = HttpServer::new(
979
2
            vec![(Method::GET, "/tor/status-vote/current/consensus", identity)],
980
2
            pool,
981
        );
982

            
983
2
        let listener = TcpListener::bind("[::1]:0").await.unwrap();
984
2
        let local_addr = listener.local_addr().unwrap();
985
2
        let listener = TcpListenerStream::new(listener);
986

            
987
2
        task::spawn(async move {
988
2
            server.serve(listener).await.unwrap();
989
        });
990

            
991
2
        let stream = TcpStream::connect(local_addr).await.unwrap();
992
2
        let (mut sender, conn) = hyper::client::conn::http1::handshake(TokioIo::new(stream))
993
2
            .await
994
2
            .unwrap();
995

            
996
2
        task::spawn(async move {
997
2
            if let Err(e) = conn.await {
998
                println!("Connection failed: {e:?}");
999
            }
        });
        // Perform a simple request.
        // TODO: Put this into one function for making requests or use reqwest.
2
        let requ = Request::builder()
2
            .version(Version::HTTP_11)
2
            .uri("/tor/status-vote/current/consensus")
2
            .body(Empty::<Bytes>::new())
2
            .unwrap();
2
        let mut resp = sender.send_request(requ).await.unwrap();
2
        let mut resp_body: Vec<u8> = Vec::new();
4
        while let Some(next) = resp.frame().await {
2
            resp_body.append(&mut next.unwrap().data_ref().unwrap().as_ref().to_vec());
2
        }
2
        assert_eq!(IDENTITY, String::from_utf8_lossy(&resp_body));
        // Perform a ".z" request.
2
        let requ = Request::builder()
2
            .version(Version::HTTP_11)
2
            .uri("/tor/status-vote/current/consensus.z")
2
            .body(Empty::<Bytes>::new())
2
            .unwrap();
2
        let mut resp = sender.send_request(requ).await.unwrap();
2
        let mut resp_body: Vec<u8> = Vec::new();
4
        while let Some(next) = resp.frame().await {
2
            resp_body.append(&mut next.unwrap().data_ref().unwrap().as_ref().to_vec());
2
        }
2
        let mut decoder = DeflateDecoder::new(Vec::new());
2
        decoder.write_all(&resp_body).unwrap();
2
        let decoded_resp = decoder.finish().unwrap();
3
        assert_eq!(IDENTITY, String::from_utf8_lossy(&decoded_resp));
2
    }
}