mirror of
https://github.com/neon-mmd/websurfx.git
synced 2024-11-22 14:08: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:
parent
ba0431dc8d
commit
65fabe5209
@ -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)
|
||||||
|
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.
|
//! This module provides modules to handle various routes in the search engine website.
|
||||||
|
|
||||||
|
pub mod export_import;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
Loading…
Reference in New Issue
Block a user