credence_lib/middleware/
facade.rs

1use super::{
2    super::{configuration::*, render::*, util::*},
3    constants::*,
4    defer::*,
5};
6
7use {::axum::extract::*, kutil::http::*};
8
9//
10// FacadeMiddleware
11//
12
13/// Axum middleware that handles the URI facade.
14///
15/// * Redirects trailing slashes (301)
16/// * Hides paths (404) that end with `.html`, `.r.*`, or other configured suffixes
17/// * Rewrites to `.html` or `.r.*` files if they exist
18/// * Otherwise if it's a directory rewrites to `index.html` or `index.r.*` if they exist in it
19///
20/// All of the above is handled by attaching a [DeferredResponse] to the *request* (not the
21/// response). We do this because request mapping middleware cannot return a normal response. Thus,
22/// we also need to install [CatchMiddleware](super::CatchMiddleware), which "catches" the
23/// deferment and generates the actual response.
24///
25/// (Remember that axum runs through layers in the *reverse* order in which they are
26/// programmatically added, so add [CatchMiddleware](super::CatchMiddleware) first *and then* add
27/// [FacadeMiddleware].)
28///
29/// We referred to "normal" responses above, because actually request mapping can return "abnormal"
30/// responses: as a [Result::Err]. We can consider them abnormal because they circumvent the
31/// remaining middleware, thus making that codepath unsuitable for us.
32#[derive(Clone, Debug)]
33pub struct FacadeMiddleware {
34    /// Configuration.
35    pub configuration: CredenceConfiguration,
36}
37
38impl FacadeMiddleware {
39    /// Constructor.
40    pub fn new(configuration: CredenceConfiguration) -> Self {
41        Self { configuration }
42    }
43
44    /// To be used with [map_request_with_state](::axum::middleware::map_request_with_state).
45    pub async fn function(State(state_self): State<Self>, mut request: Request) -> Request {
46        let uri_path = match request.uri().decoded_path() {
47            Some(uri_path) => uri_path,
48            None => {
49                // Cannot decode path
50                return request;
51            }
52        };
53
54        let original_uri_path = uri_path.clone();
55        let mut uri_path = uri_path.as_ref();
56        let mut new_uri_path = None;
57
58        // Redirect
59
60        if let Some((uri_path, status_code)) = state_self.configuration.urls.redirect(uri_path) {
61            // Note that since RFC 7231 relative `Location` is allowed
62            tracing::info!("redirect to: {} {}", status_code.as_u16(), uri_path);
63            return request.with_deferred_redirect_to(uri_path.into(), status_code);
64        }
65
66        // Hide
67
68        if state_self.configuration.hide(uri_path) {
69            tracing::info!("hide: {}", uri_path);
70            return request.with_deferred_hide();
71        }
72
73        // Protect
74
75        if let Some(protect) = state_self.configuration.urls.protect(uri_path)
76            && let Some(authenticate) = protect.authorized(request.headers())
77        {
78            return request.with_deferred_authenticate(authenticate);
79        }
80
81        // Rewrite
82
83        let mut asset_path = state_self.configuration.files.asset(uri_path);
84
85        // If it's a directory then switch to the index file in the directory
86        if asset_path.is_dir() {
87            asset_path = asset_path.join(INDEX);
88            new_uri_path = Some(uri_path_join(uri_path, INDEX));
89            uri_path = new_uri_path.as_ref().unwrap();
90        }
91
92        let mut html_asset_path = asset_path.clone();
93        html_asset_path.as_mut_os_string().push(HTML_SUFFIX);
94        if html_asset_path.exists() {
95            // {path}.html
96            new_uri_path = Some(String::from(uri_path) + HTML_SUFFIX);
97        } else if let Some(rendered_page_uri_path) = match state_self.configuration.rendered_page_uri_path(uri_path) {
98            Ok(uri_path) => uri_path,
99            Err(_error) => {
100                return request.with_deferred_error("rendered page URI path".into());
101            }
102        } {
103            // {path}.r.*
104            new_uri_path = Some(rendered_page_uri_path);
105        }
106
107        if let Some(new_uri_path) = new_uri_path {
108            tracing::debug!("rewriting: {} to {}", original_uri_path, new_uri_path);
109            let original_uri_path = original_uri_path.into_owned().into();
110            if let Err(_error) = request.set_uri_path(&new_uri_path) {
111                return request.with_deferred_error("set URI path".into());
112            }
113            return request.with_deferred_rewrite_from(original_uri_path);
114        }
115
116        request
117    }
118}