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, warn_report};
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 + 'static,
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_report!(e, "listener accept failure");
218
                        continue;
219
                    }
220

            
221
                    // This should not happen due to ownership.
222
                    None => return Err(internal!("listener was closed externally?")),
223
                },
224
                // A hyper task we monitored in our tasks has exiteed.
225
                Some(res) = tasks.join_next() => match res {
226
                    Ok(Ok(())) => {},
227
                    Ok(Err(e)) => warn_report!(e, "client task encountered an error"),
228
                    Err(e) => warn_report!(e, "client task exited ungracefully"),
229
                },
230

            
231
            }
232
        }
233
    }
234

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

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

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

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

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

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

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

            
295
            }
296
        }
297
    }
298

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

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

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

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

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

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

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

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

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

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

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

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

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

            
464
4
        resp
465
4
    }
466

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

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

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

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

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

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

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

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

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

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

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

            
613
40
        res
614
40
    }
615

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

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

            
639
10
        Ok(compressed_docid)
640
14
    }
641

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

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

            
670
    use super::*;
671

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
995
2
        task::spawn(async move {
996
2
            if let Err(e) = conn.await {
997
                println!("Connection failed: {e:?}");
998
            }
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
    }
}