mcaptcha/static_assets/
static_files.rs

1// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
2// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6use std::borrow::Cow;
7
8use actix_web::body::BoxBody;
9use actix_web::{get, http::header, web, HttpResponse, Responder};
10use log::debug;
11use mime_guess::from_path;
12use rust_embed::RustEmbed;
13
14use crate::CACHE_AGE;
15
16pub mod assets {
17    use lazy_static::lazy_static;
18
19    use crate::FILES;
20
21    type Img = (&'static str, &'static str);
22
23    lazy_static! {
24        pub static ref KEY: Img =
25            (FILES.get("./static/cache/img/svg/key.svg").unwrap(), "key");
26        pub static ref GITHUB: Img = (
27            FILES.get("./static/cache/img/svg/github.svg").unwrap(),
28            "Source code"
29        );
30        pub static ref HOME: Img = (
31            FILES.get("./static/cache/img/svg/home.svg").unwrap(),
32            "Home"
33        );
34        pub static ref SETTINGS_ICON: Img = (
35            FILES.get("./static/cache/img/svg/settings.svg").unwrap(),
36            "Settings"
37        );
38        pub static ref CREDIT_CARD: Img = (
39            FILES.get("./static/cache/img/svg/credit-card.svg").unwrap(),
40            "Payment"
41        );
42        pub static ref HELP_CIRCLE: Img = (
43            FILES.get("./static/cache/img/svg/help-circle.svg").unwrap(),
44            "Help"
45        );
46        pub static ref MESSAGE: Img = (
47            FILES
48                .get("./static/cache/img/svg/message-square.svg")
49                .unwrap(),
50            "Message"
51        );
52        pub static ref DOCS_ICON: Img = (
53            FILES.get("./static/cache/img/svg/file-text.svg").unwrap(),
54            "Documentation"
55        );
56        pub static ref MCAPTCHA_TRANS_ICON: Img = (
57            FILES.get("./static/cache/img/icon-trans.png").unwrap(),
58            "Logo"
59        );
60        pub static ref BAR_CHART: Img = (
61            FILES.get("./static/cache/img/svg/bar-chart.svg").unwrap(),
62            "Statistics"
63        );
64    }
65}
66
67#[derive(RustEmbed)]
68#[folder = "assets/"]
69struct Asset;
70
71fn handle_assets(path: &str) -> HttpResponse {
72    match Asset::get(path) {
73        Some(content) => {
74            let body: BoxBody = match content.data {
75                Cow::Borrowed(bytes) => BoxBody::new(bytes),
76                Cow::Owned(bytes) => BoxBody::new(bytes),
77            };
78
79            HttpResponse::Ok()
80                .insert_header(header::CacheControl(vec![
81                    header::CacheDirective::Public,
82                    header::CacheDirective::Extension("immutable".into(), None),
83                    header::CacheDirective::MaxAge(CACHE_AGE),
84                ]))
85                .content_type(from_path(path).first_or_octet_stream().as_ref())
86                .body(body)
87        }
88        None => HttpResponse::NotFound().body("404 Not Found"),
89    }
90}
91
92#[get("/assets/{_:.*}")]
93pub async fn static_files(path: web::Path<String>) -> impl Responder {
94    handle_assets(&path)
95}
96
97#[derive(RustEmbed)]
98#[folder = "static/favicons/"]
99struct Favicons;
100
101fn handle_favicons(path: &str) -> HttpResponse {
102    match Favicons::get(path) {
103        Some(content) => {
104            let body: BoxBody = match content.data {
105                Cow::Borrowed(bytes) => BoxBody::new(bytes),
106                Cow::Owned(bytes) => BoxBody::new(bytes),
107            };
108
109            HttpResponse::Ok()
110                .insert_header(header::CacheControl(vec![
111                    header::CacheDirective::Public,
112                    header::CacheDirective::Extension("immutable".into(), None),
113                    header::CacheDirective::MaxAge(CACHE_AGE),
114                ]))
115                .content_type(from_path(path).first_or_octet_stream().as_ref())
116                .body(body)
117        }
118        None => HttpResponse::NotFound().body("404 Not Found"),
119    }
120}
121
122#[get("/{file}")]
123pub async fn favicons(path: web::Path<String>) -> impl Responder {
124    debug!("searching favicons");
125    handle_favicons(&path)
126}
127
128#[cfg(test)]
129mod tests {
130    use actix_web::http::StatusCode;
131    use actix_web::test;
132
133    use crate::*;
134
135    #[actix_rt::test]
136    async fn static_assets_work() {
137        let app = get_app!().await;
138
139        let urls = [
140            *crate::JS,
141            *crate::VERIFICATIN_WIDGET_JS,
142            *crate::VERIFICATIN_WIDGET_CSS,
143            crate::FILES
144                .get("./static/cache/img/icon-trans.png")
145                .unwrap(),
146            "/favicon.ico",
147        ];
148
149        for u in urls.iter() {
150            println!("[*] Testing static asset at URL: {u}");
151            let resp =
152                test::call_service(&app, test::TestRequest::get().uri(u).to_request())
153                    .await;
154            assert_eq!(resp.status(), StatusCode::OK);
155        }
156    }
157}