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
104
105
106
107
108
109
110
111
|
#![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 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 template<R>(
template: R
) -> impl warp::Reply
where
R: markup::Render + std::fmt::Display
{
warp::reply::html(template.to_string())
}
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());
}
}
}
|