about summary refs log tree commit diff
path: root/kittybox-rs/src/lib.rs
blob: 1800b5b82cdfc017f823400e3e6ea39822eb235a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#![forbid(unsafe_code)]
#![warn(clippy::todo)]

pub mod metrics;
/// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database.
pub mod database;
pub mod micropub;
pub mod media;
pub mod indieauth;
pub mod frontend;

pub(crate) mod rejections {
    #[derive(Debug)]
    pub(crate) struct UnacceptableContentType;
    impl warp::reject::Reject for UnacceptableContentType {}

    #[derive(Debug)]
    pub(crate) struct HostHeaderUnset;
    impl warp::reject::Reject for HostHeaderUnset {}
}

pub static MICROPUB_CLIENT: &[u8] = include_bytes!("./index.html");

pub mod util {
    use warp::{Filter, host::Authority};
    use super::rejections;

    pub fn require_host() -> impl Filter<Extract = (Authority,), Error = warp::Rejection> + Copy {
        warp::host::optional()
            .and_then(|authority: Option<Authority>| async move {
                authority.ok_or_else(|| warp::reject::custom(rejections::HostHeaderUnset))
            })
    }

    pub fn parse_accept() -> impl Filter<Extract = (http_types::Mime,), Error = warp::Rejection> + Copy {
        warp::header::value("Accept").and_then(|accept: warp::http::HeaderValue| async move {
            let mut accept: http_types::content::Accept = {
                // This is unneccesarily complicated because I want to reuse some http-types parsing
                // and http-types has constructor for Headers private so I need to construct
                // a mock Request to reason about headers... this is so dumb wtf
                // so much for zero-cost abstractions, huh
                let bytes: &[u8] = accept.as_bytes();
                let value = http_types::headers::HeaderValue::from_bytes(bytes.to_vec()).unwrap();
                let values: http_types::headers::HeaderValues = vec![value].into();
                let mut request = http_types::Request::new(http_types::Method::Get, "http://example.com/");
                request.append_header("Accept".parse::<http_types::headers::HeaderName>().unwrap(), &values);
                http_types::content::Accept::from_headers(&request).unwrap().unwrap()
            };

            // This code is INCREDIBLY dumb, honestly...
            // why did I even try to use it?
            // TODO vendor this stuff in so I can customize it
            match accept.negotiate(&[
                "text/html; encoding=\"utf-8\"".into(),
                "application/json; encoding=\"utf-8\"".into(),
                "text/html".into(),
                "application/json".into(),

            ]) {
                Ok(mime) => {
                    Ok(http_types::Mime::from(mime.value().as_str()))
                },
                Err(err) => {
                    log::error!("Content-Type negotiation error: {:?}, accepting: {:?}", err, accept);
                    Err(warp::reject::custom(rejections::UnacceptableContentType))
                }
            }
        })
    }

    mod tests {
        #[tokio::test]
        async fn test_require_host_with_host() {
            use super::require_host;

            let filter = require_host();

            let res = warp::test::request()
                .path("/")
                .header("Host", "localhost:8080")
                .filter(&filter)
                .await
                .unwrap();

            assert_eq!(res, "localhost:8080");
            
        }

        #[tokio::test]
        async fn test_require_host_no_host() {
            use super::require_host;

            let filter = require_host();

            let res = warp::test::request()
                .path("/")
                .filter(&filter)
                .await;

            assert!(res.is_err());
        }
    }
}