File size: 6,540 Bytes
dc34d51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa57233
dc34d51
fa57233
dc34d51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa57233
 
 
 
dc34d51
 
 
 
 
 
 
 
fa57233
dc34d51
 
 
fa57233
dc34d51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa57233
dc34d51
 
 
 
fa57233
dc34d51
 
fa57233
dc34d51
fa57233
dc34d51
 
fa57233
dc34d51
fa57233
dc34d51
 
fa57233
dc34d51
fa57233
 
 
 
 
dc34d51
 
 
 
fa57233
dc34d51
fa57233
 
dc34d51
 
 
 
fa57233
 
 
dc34d51
 
fa57233
dc34d51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa57233
dc34d51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa57233
 
dc34d51
 
 
fa57233
dc34d51
 
 
fa57233
 
 
dc34d51
 
 
 
 
 
 
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! This module handles the settings and download route of the search engine website.

use crate::{
    handler::{file_path, FileType},
    models::{self, server_models},
    Config,
};
use actix_multipart::form::{tempfile::TempFile, MultipartForm};
use actix_web::{
    cookie::{
        time::{Duration, OffsetDateTime},
        Cookie,
    },
    get, post, web, HttpRequest, HttpResponse,
};
use std::borrow::Cow;
use std::io::Read;

use tokio::fs::read_dir;

/// A helper function that helps in building the list of all available colorscheme/theme/animation
/// names present in the colorschemes, animations and themes folder respectively by excluding the
/// ones that have already been selected via the config file.
///
/// # Arguments
///
/// * `style_type` - It takes the style type of the values `theme` and `colorscheme` as an
/// argument.
///
/// # Error
///
/// Returns a list of colorscheme/theme names as a vector of tuple strings on success otherwise
/// returns a standard error message.
async fn style_option_list<'a>(
    style_type: &'a str,
) -> Result<Box<[Cow<'a, str>]>, Box<dyn std::error::Error>> {
    let mut style_options = Vec::new();
    let mut dir = read_dir(format!(
        "{}static/{}/",
        file_path(FileType::Theme)?,
        style_type,
    ))
    .await?;
    while let Some(file) = dir.next_entry().await? {
        let style_name = file.file_name().to_str().unwrap().replace(".css", "");
        style_options.push(Cow::Owned(style_name));
    }

    if style_type == "animations" {
        style_options.push(Cow::default())
    }

    Ok(style_options.into_boxed_slice())
}

/// A helper function which santizes user provided json data from the input file.
///
/// # Arguments
///
/// * `config` - It takes the config struct as an argument.
/// * `setting_value` - It takes the cookie struct as an argument.
///
/// # Error
///
/// returns a standard error message on failure otherwise it returns the unit type.
async fn sanitize(
    config: web::Data<&'static Config>,
    setting_value: &mut models::server_models::Cookie<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
    // Check whether the theme, colorscheme and animation option is valid by matching it against
    // the available option list. If the option provided by the user via the JSON file is invalid
    // then replace the user provided by the default one used by the server via the config file.

    if !style_option_list("themes")
        .await?
        .contains(&setting_value.theme)
    {
        setting_value.theme = Cow::Borrowed(&config.style.theme)
    } else if !style_option_list("colorschemes")
        .await?
        .contains(&setting_value.colorscheme)
    {
        setting_value.colorscheme = Cow::Borrowed(&config.style.colorscheme)
    } else if !style_option_list("animations")
        .await?
        .contains(setting_value.animation.as_ref().unwrap())
    {
        setting_value.animation = config
            .style
            .animation
            .as_ref()
            .map(|str| Cow::Borrowed(str.as_str()));
    }

    // Filters out any engines in the list that are invalid by matching each engine against the
    // available engine list.
    let engines: Vec<_> = setting_value
        .engines
        .iter()
        .cloned()
        .filter_map(|engine| {
            config
                .upstream_search_engines
                .keys()
                .cloned()
                .any(|other_engine| *engine == other_engine)
                .then_some(engine.clone())
        })
        .collect();
    setting_value.engines = Cow::Owned(engines);

    setting_value.safe_search_level = match setting_value.safe_search_level {
        0..2 => setting_value.safe_search_level,
        _ => u8::default(),
    };

    Ok(())
}

/// A multipart struct which stores user provided input file data in memory.
#[derive(MultipartForm)]
struct File {
    /// It stores the input file data in memory.
    file: TempFile,
}

/// Handles the route of the post settings page.
#[post("/settings")]
pub async fn set_settings(
    config: web::Data<&'static Config>,
    MultipartForm(mut form): MultipartForm<File>,
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
    if let Some(file_name) = form.file.file_name {
        let file_name_parts = file_name.split(".");
        if let 2 = file_name_parts.clone().count() {
            if let Some("json") = file_name_parts.last() {
                if let 0 = form.file.size {
                    return Ok(HttpResponse::BadRequest().finish());
                } else {
                    let mut data = String::new();
                    form.file.file.read_to_string(&mut data).unwrap();

                    let mut unsanitized_json_data: models::server_models::Cookie<'_> =
                        serde_json::from_str(&data)?;

                    sanitize(config, &mut unsanitized_json_data).await?;

                    let sanitized_json_data: String =
                        serde_json::json!(unsanitized_json_data).to_string();

                    return Ok(HttpResponse::Ok()
                        .cookie(
                            Cookie::build("appCookie", sanitized_json_data)
                                .expires(
                                    OffsetDateTime::now_utc().saturating_add(Duration::weeks(52)),
                                )
                                .finish(),
                        )
                        .finish());
                }
            }
        }
    }
    Ok(HttpResponse::Ok().finish())
}

/// Handles the route of the download page.
#[get("/download")]
pub async fn download(
    config: web::Data<&'static Config>,
    req: HttpRequest,
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
    let cookie = req.cookie("appCookie");

    // Get search settings using the user's cookie or from the server's config
    let preferences: server_models::Cookie<'_> = cookie
        .as_ref()
        .and_then(|cookie_value| serde_json::from_str(cookie_value.value()).ok())
        .unwrap_or_else(|| {
            server_models::Cookie::build(
                &config.style,
                config
                    .upstream_search_engines
                    .iter()
                    .filter_map(|(engine, enabled)| {
                        enabled.then_some(Cow::Borrowed(engine.as_str()))
                    })
                    .collect(),
                u8::default(),
            )
        });

    Ok(HttpResponse::Ok().json(preferences))
}