mirror of
https://github.com/neon-mmd/websurfx.git
synced 2024-12-22 20:38:22 -05:00
✨ feat(routes): add new route /download
& update the /settings
route to handle post requests (#427)
- 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.
This commit is contained in:
parent
ba0431dc8d
commit
65fabe5209
@ -110,6 +110,7 @@ pub fn run(
|
||||
.service(server::routes::search::search) // search page
|
||||
.service(router::about) // about page
|
||||
.service(router::settings) // settings page
|
||||
.service(server::routes::export_import::download) // download page
|
||||
.default_service(web::route().to(router::not_found)) // error page
|
||||
})
|
||||
.workers(config.threads as usize)
|
||||
|
180
src/server/routes/export_import.rs
Normal file
180
src/server/routes/export_import.rs
Normal file
@ -0,0 +1,180 @@
|
||||
//! 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::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(style_type: &str) -> Result<Box<[String]>, Box<dyn std::error::Error>> {
|
||||
let mut style_options: Vec<String> = 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(style_name.clone());
|
||||
}
|
||||
|
||||
if style_type == "animations" {
|
||||
style_options.push(String::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.to_string())
|
||||
{
|
||||
setting_value.theme = config.style.theme.clone()
|
||||
} else if !style_option_list("colorschemes")
|
||||
.await?
|
||||
.contains(&setting_value.colorscheme.to_string())
|
||||
{
|
||||
setting_value.colorscheme = config.style.colorscheme.clone()
|
||||
} else if !style_option_list("animations")
|
||||
.await?
|
||||
.contains(&setting_value.animation.clone().unwrap_or_default())
|
||||
{
|
||||
setting_value.animation = config.style.animation.clone()
|
||||
}
|
||||
|
||||
// Filters out any engines in the list that are invalid by matching each engine against the
|
||||
// available engine list.
|
||||
setting_value.engines = setting_value
|
||||
.engines
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter_map(|engine| {
|
||||
config
|
||||
.upstream_search_engines
|
||||
.keys()
|
||||
.any(|other_engine| &engine == other_engine)
|
||||
.then_some(engine)
|
||||
})
|
||||
.collect();
|
||||
|
||||
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
|
||||
.and_then(|cookie_value| serde_json::from_str(cookie_value.value()).ok())
|
||||
.unwrap_or_else(|| {
|
||||
server_models::Cookie::build(
|
||||
config.style.clone(),
|
||||
config
|
||||
.upstream_search_engines
|
||||
.iter()
|
||||
.filter_map(|(engine, enabled)| enabled.then_some(engine.clone()))
|
||||
.collect(),
|
||||
u8::default(),
|
||||
)
|
||||
});
|
||||
|
||||
Ok(HttpResponse::Ok().json(preferences))
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
//! This module provides modules to handle various routes in the search engine website.
|
||||
|
||||
pub mod export_import;
|
||||
pub mod search;
|
||||
|
Loading…
Reference in New Issue
Block a user