0
0
mirror of https://github.com/neon-mmd/websurfx.git synced 2024-11-22 22:18:23 -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:
neon_arch 2024-11-02 21:52:02 +03:00
parent ba0431dc8d
commit 65fabe5209
3 changed files with 182 additions and 0 deletions

View File

@ -110,6 +110,7 @@ pub fn run(
.service(server::routes::search::search) // search page .service(server::routes::search::search) // search page
.service(router::about) // about page .service(router::about) // about page
.service(router::settings) // settings page .service(router::settings) // settings page
.service(server::routes::export_import::download) // download page
.default_service(web::route().to(router::not_found)) // error page .default_service(web::route().to(router::not_found)) // error page
}) })
.workers(config.threads as usize) .workers(config.threads as usize)

View 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))
}

View File

@ -1,3 +1,4 @@
//! This module provides modules to handle various routes in the search engine website. //! This module provides modules to handle various routes in the search engine website.
pub mod export_import;
pub mod search; pub mod search;