File size: 4,533 Bytes
d8435ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
112
113
114
115
116
use std::path::Path;

use actix_web::dev::HttpServiceFactory;
use actix_web::http::header::HeaderValue;
use actix_web::middleware::DefaultHeaders;
use actix_web::web;

use crate::settings::Settings;

const DEFAULT_STATIC_DIR: &str = "./static";
pub const WEB_UI_PATH: &str = "/dashboard";

pub fn web_ui_folder(settings: &Settings) -> Option<String> {
    let web_ui_enabled = settings.service.enable_static_content.unwrap_or(true);

    if web_ui_enabled {
        let static_folder = settings
            .service
            .static_content_dir
            .clone()
            .unwrap_or_else(|| DEFAULT_STATIC_DIR.to_string());
        let static_folder_path = Path::new(&static_folder);
        if !static_folder_path.exists() || !static_folder_path.is_dir() {
            // enabled BUT folder does not exist
            log::warn!(
                "Static content folder for Web UI '{}' does not exist",
                static_folder_path.display(),
            );
            None
        } else {
            // enabled AND folder exists
            Some(static_folder)
        }
    } else {
        // not enabled
        None
    }
}

pub fn web_ui_factory(static_folder: &str) -> impl HttpServiceFactory {
    web::scope(WEB_UI_PATH)
        .wrap(DefaultHeaders::new().add(("X-Frame-Options", HeaderValue::from_static("DENY"))))
        .service(actix_files::Files::new("/", static_folder).index_file("index.html"))
}

#[cfg(test)]
mod tests {
    use actix_web::http::header::{self, HeaderMap};
    use actix_web::http::StatusCode;
    use actix_web::test::{self, TestRequest};
    use actix_web::App;

    use super::*;

    fn assert_html_custom_headers(headers: &HeaderMap) {
        let content_type = header::HeaderValue::from_static("text/html; charset=utf-8");
        assert_eq!(headers.get(header::CONTENT_TYPE), Some(&content_type));
        let x_frame_options = header::HeaderValue::from_static("DENY");
        assert_eq!(headers.get(header::X_FRAME_OPTIONS), Some(&x_frame_options),);
    }

    #[actix_web::test]
    async fn test_web_ui() {
        let static_dir = String::from("static");
        let mut settings = Settings::new(None).unwrap();
        settings.service.static_content_dir = Some(static_dir.clone());

        let maybe_static_folder = web_ui_folder(&settings);
        if maybe_static_folder.is_none() {
            println!("Skipping test because the static folder was not found.");
            return;
        }

        let static_folder = maybe_static_folder.unwrap();
        let srv = test::init_service(App::new().service(web_ui_factory(&static_folder))).await;

        // Index path (no trailing slash)
        let req = TestRequest::with_uri(WEB_UI_PATH).to_request();
        let res = test::call_service(&srv, req).await;
        assert_eq!(res.status(), StatusCode::OK);
        let headers = res.headers();
        assert_html_custom_headers(headers);
        // Index path (trailing slash)
        let req = TestRequest::with_uri(format!("{WEB_UI_PATH}/").as_str()).to_request();
        let res = test::call_service(&srv, req).await;
        assert_eq!(res.status(), StatusCode::OK);
        let headers = res.headers();
        assert_html_custom_headers(headers);
        // Index path (index.html file)
        let req = TestRequest::with_uri(format!("{WEB_UI_PATH}/index.html").as_str()).to_request();
        let res = test::call_service(&srv, req).await;
        assert_eq!(res.status(), StatusCode::OK);
        let headers = res.headers();
        assert_html_custom_headers(headers);
        // Static asset (favicon.ico)
        let req = TestRequest::with_uri(format!("{WEB_UI_PATH}/favicon.ico").as_str()).to_request();
        let res = test::call_service(&srv, req).await;
        assert_eq!(res.status(), StatusCode::OK);
        let headers = res.headers();
        assert_eq!(
            headers.get(header::CONTENT_TYPE),
            Some(&header::HeaderValue::from_static("image/x-icon")),
        );
        // Non-existing path (404 Not Found)
        let fake_path = uuid::Uuid::new_v4().to_string();
        let srv = test::init_service(App::new().service(web_ui_factory(&fake_path))).await;

        let req = TestRequest::with_uri(WEB_UI_PATH).to_request();
        let res = test::call_service(&srv, req).await;
        assert_eq!(res.status(), StatusCode::NOT_FOUND);
        let headers = res.headers();
        assert_eq!(headers.get(header::CONTENT_TYPE), None);
        assert_eq!(headers.get(header::CONTENT_LENGTH), None);
    }
}