tag2upload_service_manager/
ui_vhost.rs

1
2use crate::prelude::*;
3
4use rocket::{Request, request::{FromRequest, Outcome}};
5use rocket::http::uncased::UncasedStr;
6
7const HEADER: &str = "Host";
8
9/// Virtual hosts we expect
10///
11/// Each entry is a list of strings, being the expected vhosts.
12///
13/// `"*"` means any vhost is allowed
14/// (and its presence renders any other values redundant).
15#[derive(Deserialize, Debug, Deftly)]
16#[derive_deftly(Guards)]
17pub struct Vhosts {
18    pub webhook: Vec<String>,
19    pub ui: Vec<String>,
20}
21
22pub struct Is<S>(S);
23
24/// Request guard, embodying a `Result` -- **unchecked**
25///
26/// Call `check`.
27///
28/// This is needed because a `FromRequest` impl cannot
29/// readily generate a custom error document,
30/// so instead we return the `WrongHost` to the route implementation.
31pub struct ResultGuard<S>(Result<Is<S>, WrongVhost>);
32
33impl<S> ResultGuard<S> {
34    pub fn check(
35        self,
36        map_err: impl FnOnce(AE) -> WebError,
37    ) -> Result<Is<S>, WE> {
38        self.0.map_err(|e| map_err(e.0))
39    }
40}
41
42#[derive(Error, Debug)]
43#[error("{0}")]
44pub struct WrongVhost(AE);
45
46fn check_core(supplied: &UncasedStr, configured: &[String])
47              -> Result<(), WrongVhost>
48{
49    if !configured.iter().any({
50        |c| c == "*" || c == supplied}
51    ) {
52        return Err(WrongVhost(anyhow!(
53 "wrong server name (vhost) requested: {HEADER:?} was {:?}, expected {}",
54            supplied.as_str(), configured.join(" / "),
55        )));
56    }
57
58    Ok(())
59}
60
61pub trait Selector: Default {
62    fn configured(vhosts: &Vhosts) -> &[String];
63}
64
65impl<S: Selector> Is<S> {
66    pub async fn from_request<'r>(req: &'r Request<'_>)
67                              -> Result<Self, WrongVhost>
68    {
69        let supplied: &rocket::http::uri::Host = match {
70            FromRequest::from_request(req).await
71        } {
72            Outcome::Success(y) => Ok(y),
73            x @ Outcome::Forward(..) => Err(WrongVhost(
74                internal!("internal error {x:?} from Host!")
75                    .into()
76            )),
77            Outcome::Error((_s, i)) => match i {},
78        }?;
79
80        let gl = globals();
81        let configured = S::configured(&gl.config.vhosts);
82        let () = check_core(supplied.domain(), configured)?;
83        Ok(Is(S::default()))
84    }
85}
86
87#[async_trait]
88impl<'r, S: Selector> FromRequest<'r> for ResultGuard<S> {
89    type Error = String;
90
91    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, String> {
92        Outcome::Success(ResultGuard(Is::from_request(req).await))
93    }
94}
95
96define_derive_deftly! {
97    Guards:
98
99    ${define V $<${pascal_case $fname}>}
100    ${define S $<$V Selector>}
101
102    $(
103        /// Token indicating that the vhost is correct
104        pub type $<Is $V> = Is<$S>;
105
106        /// Request guard, embodying a `Result` - **unchecked**, call `check`
107        pub type $<$V Result> = ResultGuard<$S>;
108
109        #[derive(Default)]
110        pub struct $S;
111
112        impl Selector for $S {
113            fn configured(vhosts: &Vhosts) -> &[String] {
114                &vhosts.$fname
115            }
116        }
117    )
118}
119use derive_deftly_template_Guards;