use std::collections::HashMap; use std::error::Error; use std::str::FromStr; use actix_web::{HttpRequest, HttpResponse, web}; use actix_web::http::{HeaderName, HeaderValue}; use bytes::Bytes; use exif::In; use image::{GenericImageView, ImageFormat}; use serde::Serialize; use crate::api_data::http_error::HttpError; use crate::controllers::routes::RequestResult; use crate::data::api_client::APIClient; use crate::data::config::conf; use crate::data::error::{ExecError, ResultBoxError}; use crate::data::group::GroupAccessLevel; use crate::data::group_id::GroupID; use crate::data::user::UserID; use crate::helpers::{account_helper, api_helper, conversations_helper, friends_helper, groups_helper, user_helper, virtual_directory_helper}; use crate::helpers::virtual_directory_helper::VirtualDirType; use crate::utils::string_utils::{check_url, remove_html_nodes}; use crate::utils::user_data_utils::{generate_new_user_data_file_name, prepare_file_creation, user_data_path}; use crate::utils::virtual_directories_utils::check_virtual_directory; /// Http request handler /// /// @author Pierre Hubert pub struct PostFile { pub name: String, pub buff: Bytes, } /// Single request body value pub enum RequestValue { String(String), File(PostFile), } #[derive(Serialize)] struct SuccessMessage { success: String } pub struct HttpRequestHandler { request: web::HttpRequest, body: HashMap, response: Option, headers: HashMap, client: Option, curr_user_id: Option, } impl HttpRequestHandler { /// Construct a new request handler pub fn new(req: HttpRequest, body: HashMap) -> HttpRequestHandler { HttpRequestHandler { request: req, body, response: None, headers: HashMap::new(), client: None, curr_user_id: None, } } /// Check if a response has been set for this request pub fn has_response(&self) -> bool { self.response.is_some() } /// Get the response status code, eg. 200 or 404 pub fn response_status_code(&self) -> u16 { self.response.as_ref().unwrap().status().as_u16() } /// Take the response from this struct pub fn response(self) -> ResultBoxError { let mut response = self.response.unwrap(); // Put additional headers if required for (k, v) in &self.headers { response.headers_mut().insert(HeaderName::from_str(k)?, HeaderValue::from_str(v)?, ); } Ok(response) } /// Set request response pub fn set_response(&mut self, data: T) -> RequestResult { self.response = Some(HttpResponse::Ok().json(data)); Ok(()) } /// Success message pub fn success(&mut self, message: &str) -> RequestResult { self.response = Some(HttpResponse::Ok().json(SuccessMessage { success: message.to_string() })); Ok(()) } /// Internal error response (500) pub fn internal_error(&mut self, error: Box) -> RequestResult { self.response = Some(HttpResponse::InternalServerError().json( HttpError::internal_error("Internal server error."))); Err(error) } /// Bad request (400) pub fn bad_request(&mut self, message: String) -> RequestResult { self.response = Some(HttpResponse::BadRequest().json( HttpError::bad_request(&message))); Err(Box::new(ExecError::new(&message))) } /// Forbidden (401) /// /// I did not the HTTP official specs when I made this choice of using Unauthorized instead /// of Forbidden. Today it would be really complicated to come back... pub fn forbidden(&mut self, message: String) -> RequestResult { self.response = Some(HttpResponse::Unauthorized().json( HttpError::forbidden(&message))); Err(Box::new(ExecError::new(&message))) } /// Not found (404) pub fn not_found(&mut self, message: String) -> RequestResult { self.response = Some(HttpResponse::NotFound().json( HttpError::not_found(&message))); Err(Box::new(ExecError::new(&message))) } /// If result is not OK, return a bad request pub fn ok_or_bad_request(&mut self, res: ResultBoxError, msg: &str) -> ResultBoxError { match res { Ok(e) => Ok(e), Err(err) => { println!("Error leading to bad request: {}", err); self.bad_request(msg.to_string())?; unreachable!() } } } /// If result is not OK, return a 404 not found error pub fn ok_or_not_found(&mut self, res: ResultBoxError, msg: &str) -> ResultBoxError { match res { Ok(e) => Ok(e), Err(err) => { println!("Error leading to 404 not found: {}", err); self.not_found(msg.to_string())?; unreachable!() } } } /// Get the path of the request pub fn request_path(&self) -> String { self.request.path().to_string() } /// Get information about the client which made the request pub fn api_client(&self) -> &APIClient { self.client.as_ref().unwrap() } /// Check if a POST parameter was present in the request or not pub fn has_post_parameter(&self, name: &str) -> bool { self.body.contains_key(name) } /// Get a post parameter pub fn post_parameter(&mut self, name: &str) -> ResultBoxError<&RequestValue> { if !self.has_post_parameter(name) { self.bad_request(format!("POST parameter '{}' not found in request!", name))?; } Ok(self.body.get(name).unwrap()) } /// Get a post string pub fn post_string(&mut self, name: &str) -> ResultBoxError { self.post_string_opt(name, 1, true) } /// Get a post string, specifying minimum length pub fn post_string_opt(&mut self, name: &str, min_length: usize, required: bool) -> ResultBoxError { let param = self.post_parameter(name)?; match (¶m, required) { (RequestValue::String(s), _) => { if s.len() >= min_length { Ok(s.to_string()) } else { Err(self.bad_request(format!("'{}' is too short!", name)).unwrap_err()) } } (_, false) => Ok(String::new()), (_, true) => Err(self.bad_request(format!("'{}' is not a string!", name)).unwrap_err()), } } /// Check API client tokens pub fn check_client_token(&mut self) -> RequestResult { let api_name = self.post_string("serviceName")?; let api_token = self.post_string("serviceToken")?; let client = self.ok_or_bad_request( api_helper::get_client(&api_name, &api_token), "Client not recognized!", )?; if let Some(domain) = &client.domain { let allowed_origin = match conf().force_https { true => format!("https://{}", domain), false => format!("http://{}", domain) }; match self.request.headers().get("Referer") { None => self.bad_request("Unknown origin!".to_string())?, Some(s) => { if !s.to_str()?.starts_with(&allowed_origin) { self.bad_request("Use of this client is prohibited from this domain!".to_string())?; } } } self.headers.insert("Access-Control-Allow-Origin".to_string(), allowed_origin); } self.client = Some(client); Ok(()) } /// Check login token pub fn check_user_token(&mut self) -> RequestResult { let token = self.post_string("userToken1")?; // Find user match account_helper::get_user_by_login_token(&token, self.api_client()) { Ok(id) => { self.curr_user_id = Some(id); Ok(()) } Err(e) => { println!("Error marking login tokens as invalid: {}", e); self.response = Some( actix_web::HttpResponse:: build(actix_web::http::StatusCode::from_u16(412)?) .json(HttpError::new(412, "Please check your login tokens!"))); Err(e) } } } /// Check out whether a file was included in the request or not pub fn has_file(&self, name: &str) -> bool { if let Some(RequestValue::File(_)) = self.body.get(name) { true } else { false } } /// Get a file included in the request pub fn post_file(&mut self, name: &str) -> ResultBoxError<&PostFile> { if self.has_file(name) { if let RequestValue::File(f) = self.post_parameter(name)? { return Ok(f); } } else { self.bad_request(format!("File {} not included in request!", name))?; } unreachable!(); } /// Save an image in user data directory pub fn save_post_image(&mut self, name: &str, folder: &str, max_w: u32, max_h: u32) -> ResultBoxError { // Load image let file = self.post_file(name)?; let mut image = image::load_from_memory(file.buff.as_ref())?; if image.width() > max_w || image.height() > max_h { image = image.resize(max_w, max_h, image::imageops::FilterType::Nearest); } // Read EXIF information in case of JPEG image, if possible if let Ok(ImageFormat::Jpeg) = image::guess_format(file.buff.as_ref()) { let mut reader = std::io::BufReader::new(file.buff.as_ref()); if let Ok(exif_attr) = exif::get_exif_attr_from_jpeg(&mut reader) { let exif_reader = exif::Reader::new(); let exif = exif_reader.read_raw(exif_attr)?; if let Some(v) = exif.get_field(exif::Tag::Orientation, In::PRIMARY) { match v.value.get_uint(0) { Some(1) => { /* row 0 is top and column 0 is left */ } //Some(2) => println!("row 0 at top and column 0 at right"), Some(3) => { /* row 0 at bottom and column 0 at right */ image = image.rotate180() } //Some(4) => println!("row 0 at bottom and column 0 at left"), //Some(5) => println!("row 0 at left and column 0 at top"), Some(6) => { /* row 0 is right and column 0 is top */ image = image.rotate90(); } //Some(7) => println!("row 0 at right and column 0 at bottom"), Some(8) => { /* row 0 is left and column 0 is bottom */ image = image.rotate270(); } v => println!("Unhandled EXIF Orientation: {:?}", v), }; } } } // Determine image destination let target_user_data_folder = prepare_file_creation(&self.user_id()?, folder)?; let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "png")?; let target_sys_path = user_data_path(target_file_path.as_path()); // Save image image.save_with_format(target_sys_path, ImageFormat::Png)?; Ok(target_file_path.to_string_lossy().to_string()) } /// Get an integer included in the POST request pub fn post_i64(&mut self, name: &str) -> ResultBoxError { Ok(self.post_string(name)?.parse::()?) } /// Get an optional number in the request. If none found, return a default value pub fn post_u64_opt(&mut self, name: &str, default: u64) -> ResultBoxError { if self.has_post_parameter(name) { Ok(self.post_string(name)?.parse::()?) } else { Ok(default) } } pub fn post_u64(&mut self, name: &str) -> ResultBoxError { Ok(self.post_string(name)?.parse::()?) } /// Get a boolean included in a POST request pub fn post_bool(&mut self, name: &str) -> ResultBoxError { Ok(self.post_string(name)?.eq("true")) } /// Get an optional boolean included in post request pub fn post_bool_opt(&mut self, name: &str, default: bool) -> bool { self.post_bool(name).unwrap_or(default) } /// Get user ID. This function assess that a user ID is available to continue pub fn user_id(&self) -> ResultBoxError { match self.curr_user_id.clone() { Some(s) => Ok(s), None => Err(ExecError::boxed_new("Could not get required user ID!")) } } /// Get a user ID, if available pub fn user_id_opt(&self) -> Option { self.curr_user_id.clone() } /// Get current user ID, return invalid user id value if there is none pub fn user_id_or_invalid(&self) -> UserID { self.user_id_opt().unwrap_or(UserID::invalid()) } /// Get user ID as a reference pub fn user_id_ref(&self) -> ResultBoxError<&UserID> { match self.curr_user_id.as_ref() { Some(s) => Ok(s), None => Err(ExecError::boxed_new("Could not get required user ID!")) } } /// Get an email included in the request pub fn post_email(&mut self, name: &str) -> ResultBoxError { let mail = self.post_string(name)?; if !mailchecker::is_valid(&mail) { self.bad_request("Invalid email address!".to_string())?; } Ok(mail) } /// Get a list of integers included in the request pub fn post_numbers_list(&mut self, name: &str, min_len: usize) -> ResultBoxError> { let param = self.post_string_opt(name, min_len, min_len != 0)?; let mut list = vec![]; for split in param.split::<&str>(",") { if split.is_empty() { continue; } list.push(split.parse::()?); } if list.len() < min_len { self.bad_request(format!("Not enough entries in '{}'!", name))?; } Ok(list) } /// Get the ID of a user included in a POST request pub fn post_user_id(&mut self, name: &str) -> ResultBoxError { let user_id = UserID::new(self.post_u64(name)?); if user_id.id() < 1 { self.bad_request(format!("Invalid user specified in '{}'!", name))?; } if !user_helper::exists(&user_id)? { self.not_found(format!("User with ID {} not found!", user_id.id()))?; } Ok(user_id) } /// Get the ID of a friend included in a POST request /// /// *Note :* This function does not check whether the user exists or not before checking if the /// two users are friend because as it is not possible to be friend with a non existent person /// A single check is enough pub fn post_friend_id(&mut self, name: &str) -> ResultBoxError { let friend_id = UserID::new(self.post_u64(name)?); if !friends_helper::are_friend(&friend_id, self.user_id_ref()?)? { self.forbidden("You are not friend with this person!".to_string())?; } Ok(friend_id) } /// Get a virtual directory included in a POST request pub fn post_virtual_directory(&mut self, name: &str) -> ResultBoxError { let dir = self.post_string(name)?; if !check_virtual_directory(&dir) { self.bad_request(format!("Invalid virtual directory specified in '{}' !", name))?; } Ok(dir) } /// Get a string included in the request, with HTML codes removed pub fn post_string_without_html(&mut self, name: &str, min_length: usize, required: bool) -> ResultBoxError { Ok(remove_html_nodes(self.post_string_opt(name, min_length, required)?.as_str())) } /// Get an optionnal string included in the request, with HTML codes removed pub fn post_string_without_html_opt(&mut self, name: &str, min_length: usize) -> ResultBoxError> { if !self.has_post_parameter(name) { Ok(None) } else { Ok(Some(remove_html_nodes(self.post_string_opt(name, min_length, true)?.as_str()))) } } /// Get & return the ID of the conversation included in the POST request pub fn post_conv_id(&mut self, name: &str) -> ResultBoxError { let conv_id = self.post_u64(name)?; if !conversations_helper::does_user_belongs_to(&self.user_id()?, conv_id)? { self.forbidden(format!("You do not belong to conversation {} !", conv_id))?; } Ok(conv_id) } /// Get the ID pub fn post_group_id(&mut self, name: &str) -> ResultBoxError { let group_id = GroupID::new(self.post_u64(name)?); if !groups_helper::exists(&group_id)? { self.not_found("Specified group not found!".to_string())?; } Ok(group_id) } /// Get the ID of a group included in a request with a check for access level of current user pub fn post_group_id_with_access(&mut self, name: &str, min_level: GroupAccessLevel) -> ResultBoxError { let group_id = self.post_group_id(name)?; let access_level = groups_helper::get_access_level(&group_id, self.user_id_opt())?; if access_level == GroupAccessLevel::NO_ACCESS { self.not_found("Specified group not found!".to_string())?; } if access_level < min_level { self.forbidden("You do not have enough rights to perform what you intend to do on this group!".to_string())?; } Ok(group_id) } /// Get an URL included in the request pub fn post_url_opt(&mut self, name: &str, required: bool) -> ResultBoxError> { let url = self.post_string_opt(name, 0, required)?; if url.is_empty() && !required { Ok(None) } else { if !check_url(&url) { self.bad_request(format!("Invalid url specified in {} !", name))?; } Ok(Some(url)) } } /// Get an optional virtual directory included in the request pub fn post_checked_virtual_directory_opt(&mut self, name: &str, target_id: u64, target_type: VirtualDirType) -> ResultBoxError> { let dir = self.post_string_opt(name, 0, false)?; if dir.is_empty() { return Ok(None); } if !virtual_directory_helper::check_availability(&dir, target_id, target_type)? { self.forbidden("Requested virtual directory is not available!".to_string())?; } Ok(Some(dir)) } }