Spaces:
Running
Running
neon_arch
commited on
:sparkles: feat(routes): add new route `/download` & update the `/settings` route to handle post requests (#427)
Browse files- Add a new route `/download` which handles the export functionality of
the user preferences as a `json` file.
- Update the `/settings` route to handle post requests for handling the
import functionality of the user preferences. Also, taking care of the
sanitization of the user provided `json` values.
- src/lib.rs +1 -0
- src/server/routes/export_import.rs +180 -0
- src/server/routes/mod.rs +1 -0
src/lib.rs
CHANGED
@@ -110,6 +110,7 @@ pub fn run(
|
|
110 |
.service(server::routes::search::search) // search page
|
111 |
.service(router::about) // about page
|
112 |
.service(router::settings) // settings page
|
|
|
113 |
.default_service(web::route().to(router::not_found)) // error page
|
114 |
})
|
115 |
.workers(config.threads as usize)
|
|
|
110 |
.service(server::routes::search::search) // search page
|
111 |
.service(router::about) // about page
|
112 |
.service(router::settings) // settings page
|
113 |
+
.service(server::routes::export_import::download) // download page
|
114 |
.default_service(web::route().to(router::not_found)) // error page
|
115 |
})
|
116 |
.workers(config.threads as usize)
|
src/server/routes/export_import.rs
ADDED
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
//! This module handles the settings and download route of the search engine website.
|
2 |
+
|
3 |
+
use crate::{
|
4 |
+
handler::{file_path, FileType},
|
5 |
+
models::{self, server_models},
|
6 |
+
Config,
|
7 |
+
};
|
8 |
+
use actix_multipart::form::{tempfile::TempFile, MultipartForm};
|
9 |
+
use actix_web::{
|
10 |
+
cookie::{
|
11 |
+
time::{Duration, OffsetDateTime},
|
12 |
+
Cookie,
|
13 |
+
},
|
14 |
+
get, post, web, HttpRequest, HttpResponse,
|
15 |
+
};
|
16 |
+
use std::io::Read;
|
17 |
+
use tokio::fs::read_dir;
|
18 |
+
|
19 |
+
/// A helper function that helps in building the list of all available colorscheme/theme/animation
|
20 |
+
/// names present in the colorschemes, animations and themes folder respectively by excluding the
|
21 |
+
/// ones that have already been selected via the config file.
|
22 |
+
///
|
23 |
+
/// # Arguments
|
24 |
+
///
|
25 |
+
/// * `style_type` - It takes the style type of the values `theme` and `colorscheme` as an
|
26 |
+
/// argument.
|
27 |
+
///
|
28 |
+
/// # Error
|
29 |
+
///
|
30 |
+
/// Returns a list of colorscheme/theme names as a vector of tuple strings on success otherwise
|
31 |
+
/// returns a standard error message.
|
32 |
+
async fn style_option_list(style_type: &str) -> Result<Box<[String]>, Box<dyn std::error::Error>> {
|
33 |
+
let mut style_options: Vec<String> = Vec::new();
|
34 |
+
let mut dir = read_dir(format!(
|
35 |
+
"{}static/{}/",
|
36 |
+
file_path(FileType::Theme)?,
|
37 |
+
style_type,
|
38 |
+
))
|
39 |
+
.await?;
|
40 |
+
while let Some(file) = dir.next_entry().await? {
|
41 |
+
let style_name = file.file_name().to_str().unwrap().replace(".css", "");
|
42 |
+
style_options.push(style_name.clone());
|
43 |
+
}
|
44 |
+
|
45 |
+
if style_type == "animations" {
|
46 |
+
style_options.push(String::default())
|
47 |
+
}
|
48 |
+
|
49 |
+
Ok(style_options.into_boxed_slice())
|
50 |
+
}
|
51 |
+
|
52 |
+
/// A helper function which santizes user provided json data from the input file.
|
53 |
+
///
|
54 |
+
/// # Arguments
|
55 |
+
///
|
56 |
+
/// * `config` - It takes the config struct as an argument.
|
57 |
+
/// * `setting_value` - It takes the cookie struct as an argument.
|
58 |
+
///
|
59 |
+
/// # Error
|
60 |
+
///
|
61 |
+
/// returns a standard error message on failure otherwise it returns the unit type.
|
62 |
+
async fn sanitize(
|
63 |
+
config: web::Data<&'static Config>,
|
64 |
+
setting_value: &mut models::server_models::Cookie,
|
65 |
+
) -> Result<(), Box<dyn std::error::Error>> {
|
66 |
+
// Check whether the theme, colorscheme and animation option is valid by matching it against
|
67 |
+
// the available option list. If the option provided by the user via the JSON file is invalid
|
68 |
+
// then replace the user provided by the default one used by the server via the config file.
|
69 |
+
if !style_option_list("themes")
|
70 |
+
.await?
|
71 |
+
.contains(&setting_value.theme.to_string())
|
72 |
+
{
|
73 |
+
setting_value.theme = config.style.theme.clone()
|
74 |
+
} else if !style_option_list("colorschemes")
|
75 |
+
.await?
|
76 |
+
.contains(&setting_value.colorscheme.to_string())
|
77 |
+
{
|
78 |
+
setting_value.colorscheme = config.style.colorscheme.clone()
|
79 |
+
} else if !style_option_list("animations")
|
80 |
+
.await?
|
81 |
+
.contains(&setting_value.animation.clone().unwrap_or_default())
|
82 |
+
{
|
83 |
+
setting_value.animation = config.style.animation.clone()
|
84 |
+
}
|
85 |
+
|
86 |
+
// Filters out any engines in the list that are invalid by matching each engine against the
|
87 |
+
// available engine list.
|
88 |
+
setting_value.engines = setting_value
|
89 |
+
.engines
|
90 |
+
.clone()
|
91 |
+
.into_iter()
|
92 |
+
.filter_map(|engine| {
|
93 |
+
config
|
94 |
+
.upstream_search_engines
|
95 |
+
.keys()
|
96 |
+
.any(|other_engine| &engine == other_engine)
|
97 |
+
.then_some(engine)
|
98 |
+
})
|
99 |
+
.collect();
|
100 |
+
|
101 |
+
setting_value.safe_search_level = match setting_value.safe_search_level {
|
102 |
+
0..2 => setting_value.safe_search_level,
|
103 |
+
_ => u8::default(),
|
104 |
+
};
|
105 |
+
|
106 |
+
Ok(())
|
107 |
+
}
|
108 |
+
|
109 |
+
/// A multipart struct which stores user provided input file data in memory.
|
110 |
+
#[derive(MultipartForm)]
|
111 |
+
struct File {
|
112 |
+
/// It stores the input file data in memory.
|
113 |
+
file: TempFile,
|
114 |
+
}
|
115 |
+
|
116 |
+
/// Handles the route of the post settings page.
|
117 |
+
#[post("/settings")]
|
118 |
+
pub async fn set_settings(
|
119 |
+
config: web::Data<&'static Config>,
|
120 |
+
MultipartForm(mut form): MultipartForm<File>,
|
121 |
+
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
|
122 |
+
if let Some(file_name) = form.file.file_name {
|
123 |
+
let file_name_parts = file_name.split(".");
|
124 |
+
if let 2 = file_name_parts.clone().count() {
|
125 |
+
if let Some("json") = file_name_parts.last() {
|
126 |
+
if let 0 = form.file.size {
|
127 |
+
return Ok(HttpResponse::BadRequest().finish());
|
128 |
+
} else {
|
129 |
+
let mut data = String::new();
|
130 |
+
form.file.file.read_to_string(&mut data).unwrap();
|
131 |
+
|
132 |
+
let mut unsanitized_json_data: models::server_models::Cookie =
|
133 |
+
serde_json::from_str(&data)?;
|
134 |
+
|
135 |
+
sanitize(config, &mut unsanitized_json_data).await?;
|
136 |
+
|
137 |
+
let sanitized_json_data: String =
|
138 |
+
serde_json::json!(unsanitized_json_data).to_string();
|
139 |
+
|
140 |
+
return Ok(HttpResponse::Ok()
|
141 |
+
.cookie(
|
142 |
+
Cookie::build("appCookie", sanitized_json_data)
|
143 |
+
.expires(
|
144 |
+
OffsetDateTime::now_utc().saturating_add(Duration::weeks(52)),
|
145 |
+
)
|
146 |
+
.finish(),
|
147 |
+
)
|
148 |
+
.finish());
|
149 |
+
}
|
150 |
+
}
|
151 |
+
}
|
152 |
+
}
|
153 |
+
Ok(HttpResponse::Ok().finish())
|
154 |
+
}
|
155 |
+
|
156 |
+
/// Handles the route of the download page.
|
157 |
+
#[get("/download")]
|
158 |
+
pub async fn download(
|
159 |
+
config: web::Data<&'static Config>,
|
160 |
+
req: HttpRequest,
|
161 |
+
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
|
162 |
+
let cookie = req.cookie("appCookie");
|
163 |
+
|
164 |
+
// Get search settings using the user's cookie or from the server's config
|
165 |
+
let preferences: server_models::Cookie = cookie
|
166 |
+
.and_then(|cookie_value| serde_json::from_str(cookie_value.value()).ok())
|
167 |
+
.unwrap_or_else(|| {
|
168 |
+
server_models::Cookie::build(
|
169 |
+
config.style.clone(),
|
170 |
+
config
|
171 |
+
.upstream_search_engines
|
172 |
+
.iter()
|
173 |
+
.filter_map(|(engine, enabled)| enabled.then_some(engine.clone()))
|
174 |
+
.collect(),
|
175 |
+
u8::default(),
|
176 |
+
)
|
177 |
+
});
|
178 |
+
|
179 |
+
Ok(HttpResponse::Ok().json(preferences))
|
180 |
+
}
|
src/server/routes/mod.rs
CHANGED
@@ -1,3 +1,4 @@
|
|
1 |
//! This module provides modules to handle various routes in the search engine website.
|
2 |
|
|
|
3 |
pub mod search;
|
|
|
1 |
//! This module provides modules to handle various routes in the search engine website.
|
2 |
|
3 |
+
pub mod export_import;
|
4 |
pub mod search;
|