diff --git a/src/controllers/account_controller.rs b/src/controllers/account_controller.rs index b60e162..cbf5ba8 100644 --- a/src/controllers/account_controller.rs +++ b/src/controllers/account_controller.rs @@ -9,6 +9,7 @@ use crate::api_data::res_check_security_questions_exists::ResCheckSecurityQuesti use crate::api_data::res_get_security_questions::ResGetSecurityQuestions; use crate::constants::PASSWORD_RESET_TOKEN_LENGTH; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::error::ResultBoxError; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::new_account::NewAccount; diff --git a/src/controllers/calls_controller.rs b/src/controllers/calls_controller.rs index 86ede5a..b5c9893 100644 --- a/src/controllers/calls_controller.rs +++ b/src/controllers/calls_controller.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; /// Get legacy call configuration diff --git a/src/controllers/comments_controller.rs b/src/controllers/comments_controller.rs index 1eb9863..49dcbe8 100644 --- a/src/controllers/comments_controller.rs +++ b/src/controllers/comments_controller.rs @@ -13,6 +13,7 @@ use crate::data::post::PostAccessLevel; use crate::helpers::{comments_helper, notifications_helper, posts_helper}; use crate::utils::date_utils::time; use crate::utils::string_utils::remove_html_nodes; +use crate::data::base_request_handler::BaseRequestHandler; /// Create a new comment pub fn create(r: &mut HttpRequestHandler) -> RequestResult { diff --git a/src/controllers/conversations_controller.rs b/src/controllers/conversations_controller.rs index 555919a..0c7782a 100644 --- a/src/controllers/conversations_controller.rs +++ b/src/controllers/conversations_controller.rs @@ -12,6 +12,7 @@ use crate::api_data::res_count_unread_conversations::ResultCountUnreadConversati use crate::api_data::res_create_conversation::ResCreateConversation; use crate::api_data::res_find_private_conversations::ResFindPrivateConversations; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::new_conversation::NewConversation; use crate::data::new_conversation_message::NewConversationMessage; @@ -184,7 +185,7 @@ pub fn refresh_list(r: &mut HttpRequestHandler) -> RequestResult { r.forbidden(format!("Your do not belongs to conversation {} !", conv_id))?; } - let list_conv = conversations_helper::get_last_messages(conv_id , 10)?; + let list_conv = conversations_helper::get_last_messages(conv_id, 10)?; list.insert(conv_id as u64, list_conv); conversations_helper::mark_user_seen(conv_id as u64, &r.user_id()?)?; diff --git a/src/controllers/friends_controller.rs b/src/controllers/friends_controller.rs index 9a83bf9..d61ceeb 100644 --- a/src/controllers/friends_controller.rs +++ b/src/controllers/friends_controller.rs @@ -5,6 +5,7 @@ use crate::api_data::friend_api::FriendAPI; use crate::api_data::friendship_status_api::FriendshipStatusAPI; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::notification::NotifEventType; use crate::helpers::{account_helper, friends_helper, notifications_helper, user_helper}; diff --git a/src/controllers/groups_controller.rs b/src/controllers/groups_controller.rs index 0084dfa..0479a11 100644 --- a/src/controllers/groups_controller.rs +++ b/src/controllers/groups_controller.rs @@ -11,6 +11,7 @@ use crate::api_data::res_change_group_logo::ResChangeGroupLogo; use crate::api_data::res_create_group::GroupCreationResult; use crate::constants::{DEFAULT_GROUP_LOGO, PATH_GROUPS_LOGOS}; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::group::{Group, GroupAccessLevel, GroupPostsCreationLevel, GroupRegistrationLevel, GroupVisibilityLevel}; use crate::data::group_id::GroupID; use crate::data::group_member::{GroupMember, GroupMembershipLevel}; diff --git a/src/controllers/likes_controller.rs b/src/controllers/likes_controller.rs index c3e8082..a803ab1 100644 --- a/src/controllers/likes_controller.rs +++ b/src/controllers/likes_controller.rs @@ -3,6 +3,7 @@ //! @author Pierre Hubert use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::error::ExecError; use crate::data::group::GroupAccessLevel; use crate::data::http_request_handler::HttpRequestHandler; diff --git a/src/controllers/movies_controller.rs b/src/controllers/movies_controller.rs index 4791418..55a01a7 100644 --- a/src/controllers/movies_controller.rs +++ b/src/controllers/movies_controller.rs @@ -4,6 +4,7 @@ use crate::api_data::movie_api::MovieAPI; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; use crate::helpers::movies_helper; diff --git a/src/controllers/notifications_controller.rs b/src/controllers/notifications_controller.rs index 491ab00..1e6ee5a 100644 --- a/src/controllers/notifications_controller.rs +++ b/src/controllers/notifications_controller.rs @@ -6,6 +6,7 @@ use crate::api_data::notification_api::NotificationAPI; use crate::api_data::res_count_all_unreads::ResCountAllUnread; use crate::api_data::res_number_unread_notifications::ResNumberUnreadNotifications; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::error::ResultBoxError; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::notification::Notification; diff --git a/src/controllers/posts_controller.rs b/src/controllers/posts_controller.rs index 6d807f3..fdf2df2 100644 --- a/src/controllers/posts_controller.rs +++ b/src/controllers/posts_controller.rs @@ -7,6 +7,7 @@ use crate::api_data::posts_targets_api::PostsTargets; use crate::api_data::res_create_post::ResCreatePost; use crate::constants::{PATH_POST_IMAGES, PATH_POST_PDF}; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::error::{ExecError, ResultBoxError}; use crate::data::group::GroupAccessLevel; use crate::data::http_request_handler::HttpRequestHandler; diff --git a/src/controllers/search_controller.rs b/src/controllers/search_controller.rs index 3e44427..2e3a987 100644 --- a/src/controllers/search_controller.rs +++ b/src/controllers/search_controller.rs @@ -4,6 +4,7 @@ use crate::api_data::global_search_result_api::GlobalSearchResultAPI; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::global_search_result::GlobalSearchResult; use crate::data::http_request_handler::HttpRequestHandler; use crate::helpers::{groups_helper, user_helper}; diff --git a/src/controllers/server.rs b/src/controllers/server.rs index 665d4b4..9ff7b2e 100644 --- a/src/controllers/server.rs +++ b/src/controllers/server.rs @@ -16,8 +16,9 @@ use crate::api_data::http_error::HttpError; use crate::constants::MAX_REQUEST_SIZE; use crate::controllers::routes::{get_routes, RequestResult, Route}; use crate::controllers::routes::Method::{GET, POST}; +use crate::data::base_request_handler::{BaseRequestHandler, PostFile, RequestValue}; use crate::data::config::Config; -use crate::data::http_request_handler::{HttpRequestHandler, PostFile, RequestValue}; +use crate::data::http_request_handler::HttpRequestHandler; use crate::helpers::requests_limit_helper; /// Main server functions diff --git a/src/controllers/server_controller.rs b/src/controllers/server_controller.rs index 2b6015e..43a7504 100644 --- a/src/controllers/server_controller.rs +++ b/src/controllers/server_controller.rs @@ -1,11 +1,12 @@ -use crate::data::http_request_handler::HttpRequestHandler; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; +use crate::data::http_request_handler::HttpRequestHandler; /// Main server controller /// /// @author Pierre Hubert /// Root server index -pub fn main_index(request: &mut HttpRequestHandler) -> RequestResult { +pub fn main_index(request: &mut HttpRequestHandler) -> RequestResult { request.success("Comunic API server V3. (c) Pierre Hubert 2020") } \ No newline at end of file diff --git a/src/controllers/settings_controller.rs b/src/controllers/settings_controller.rs index 37ccecf..8032a9a 100644 --- a/src/controllers/settings_controller.rs +++ b/src/controllers/settings_controller.rs @@ -9,6 +9,7 @@ use crate::api_data::res_create_custom_emoji::ResCreateCustomEmoji; use crate::api_data::security_settings_api::SecuritySettingsAPI; use crate::constants::SUPPORTED_LANGUAGES; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::general_settings::GeneralSettings; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::lang_settings::LangSettings; diff --git a/src/controllers/surveys_controller.rs b/src/controllers/surveys_controller.rs index 3907e2e..337c467 100644 --- a/src/controllers/surveys_controller.rs +++ b/src/controllers/surveys_controller.rs @@ -5,6 +5,7 @@ use crate::api_data::survey_api::SurveyAPI; use crate::constants::MAXIMUM_NUMBER_SURVEY_CHOICES; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::error::ResultBoxError; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::post::PostAccessLevel; diff --git a/src/controllers/user_controller.rs b/src/controllers/user_controller.rs index 5c8c7c4..0eaa45f 100644 --- a/src/controllers/user_controller.rs +++ b/src/controllers/user_controller.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use crate::api_data::user_info::APIUserInfo; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::user::UserID; use crate::helpers::user_helper; diff --git a/src/controllers/virtual_directory_controller.rs b/src/controllers/virtual_directory_controller.rs index 473fa64..cb35edb 100644 --- a/src/controllers/virtual_directory_controller.rs +++ b/src/controllers/virtual_directory_controller.rs @@ -2,11 +2,12 @@ //! //! @author Pierre Hubert -use crate::data::http_request_handler::HttpRequestHandler; -use crate::controllers::routes::RequestResult; -use crate::helpers::{user_helper, groups_helper}; use crate::api_data::res_find_user_by_virtual_directory::FindUserByVirtualDirectoryAPIResult; use crate::api_data::res_find_virtual_directory::ResultFindVirtualDirectory; +use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; +use crate::data::http_request_handler::HttpRequestHandler; +use crate::helpers::{groups_helper, user_helper}; /// Find a user by its virtual directory pub fn find_user(r: &mut HttpRequestHandler) -> RequestResult { @@ -31,9 +32,7 @@ pub fn find(r: &mut HttpRequestHandler) -> RequestResult { println!("Find virtual directory errors:\n* User: {}\n* Group: {}", user.unwrap_err(), group.unwrap_err()); r.not_found("Specified user / group virtual directory not found !".to_string()) - } - - else { + } else { r.set_response(ResultFindVirtualDirectory::new(user, group)) } } \ No newline at end of file diff --git a/src/controllers/web_app_controller.rs b/src/controllers/web_app_controller.rs index 45e529f..781da34 100644 --- a/src/controllers/web_app_controller.rs +++ b/src/controllers/web_app_controller.rs @@ -4,6 +4,7 @@ use crate::api_data::user_membership_api::UserMembershipAPI; use crate::controllers::routes::RequestResult; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; use crate::helpers::webapp_helper; diff --git a/src/data/base_request_handler.rs b/src/data/base_request_handler.rs new file mode 100644 index 0000000..90a9cdc --- /dev/null +++ b/src/data/base_request_handler.rs @@ -0,0 +1,601 @@ +//! # Base Request Handler +//! +//! Base handling code for all user requests + + +use std::error::Error; + +use exif::In; +use image::{GenericImageView, ImageFormat}; +use serde::Serialize; + +use crate::api_data::http_error::HttpError; +use crate::constants::PASSWORD_MIN_LENGTH; +use crate::controllers::routes::RequestResult; +use crate::data::comment::Comment; +use crate::data::custom_emoji::CustomEmoji; +use crate::data::error::{ExecError, ResultBoxError}; +use crate::data::group::GroupAccessLevel; +use crate::data::group_id::GroupID; +use crate::data::post::{Post, PostAccessLevel}; +use crate::data::user::UserID; +use crate::helpers::{account_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, movies_helper, posts_helper, user_helper, virtual_directory_helper}; +use crate::helpers::virtual_directory_helper::VirtualDirType; +use crate::utils::pdf_utils::is_valid_pdf; +use crate::utils::string_utils::{check_emoji_code, check_string_before_insert, 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; + +#[derive(Serialize)] +struct SuccessMessage { + success: String +} + + +pub struct PostFile { + pub name: String, + pub buff: bytes::Bytes, +} + +/// Single request body value +pub enum RequestValue { + String(String), + File(PostFile), +} + + +pub trait BaseRequestHandler { + /// Get a parameter of the request + fn post_parameter_opt(&self, name: &str) -> Option<&RequestValue>; + + /// Set response + fn set_response(&mut self, data: T) -> RequestResult; + + /// Set an error + fn set_error(&mut self, error: HttpError); + + /// Get remote IP address + fn remote_ip(&self) -> String; + + /// Current user ID + fn user_id_opt_ref(&self) -> Option<&UserID>; + + + /// Success message + fn success(&mut self, message: &str) -> RequestResult { + self.set_response(SuccessMessage { + success: message.to_string() + }) + } + + /// Internal error response (500) + fn internal_error(&mut self, error: Box) -> RequestResult { + self.set_error(HttpError::internal_error("Internal server error.")); + Err(error) + } + + /// Bad request (400) + fn bad_request(&mut self, message: String) -> RequestResult { + self.set_error(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... + fn forbidden(&mut self, message: String) -> RequestResult { + self.set_error(HttpError::forbidden(&message)); + Err(Box::new(ExecError::new(&message))) + } + + /// Not found (404) + fn not_found(&mut self, message: String) -> RequestResult { + self.set_error(HttpError::not_found(&message)); + Err(Box::new(ExecError::new(&message))) + } + + /// Conflict (409) + fn conflict(&mut self, message: String) -> RequestResult { + self.set_error(HttpError::new(409, &message)); + Err(Box::new(ExecError::new(&message))) + } + + /// Too many requests (429) + fn too_many_requests(&mut self, message: &str) -> RequestResult { + self.set_error(HttpError::new(429, message)); + Err(Box::new(ExecError::new(message))) + } + + /// If result is not OK, return a bad request + 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 bad request + fn ok_or_forbidden(&mut self, res: ResultBoxError, msg: &str) -> ResultBoxError { + match res { + Ok(e) => Ok(e), + Err(err) => { + println!("Error leading to access forbidden: {}", err); + self.forbidden(msg.to_string())?; + unreachable!() + } + } + } + + /// If result is not OK, return a 404 not found error + 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 a user ID, if available + fn user_id_opt(&self) -> Option { + self.user_id_opt_ref().map(|u| u.clone()) + } + + /// Get user ID. This function assess that a user ID is available to continue + fn user_id(&self) -> ResultBoxError { + if let Some(id) = self.user_id_opt() { + return Ok(id.clone()); + } + + Err(ExecError::boxed_new("Could not get required user ID!")) + } + + /// Get current user ID, return invalid user id value if there is none + fn user_id_or_invalid(&self) -> UserID { + self.user_id_opt().unwrap_or(UserID::invalid()) + } + + /// Get user ID as a reference + fn user_id_ref(&self) -> ResultBoxError<&UserID> { + self.user_id_opt_ref().ok_or(ExecError::boxed_new("Could not get required user ID!")) + } + + + /// Check if a POST parameter was present in the request or not + fn has_post_parameter(&self, name: &str) -> bool { + self.post_parameter_opt(name).is_some() + } + + /// Get a post parameter + 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.post_parameter_opt(name).unwrap()) + } + + /// Get a post string + fn post_string(&mut self, name: &str) -> ResultBoxError { + self.post_string_opt(name, 1, true) + } + + /// Get a post string, specifying minimum length + 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 out whether a file was included in the request or not + fn has_file(&self, name: &str) -> bool { + self.post_parameter_opt(name) + .map(|f| matches!(f, RequestValue::File(_))) + .unwrap_or(false) + } + + /// Get a file included in the request + 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 + 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()) + } + + /// Save a pdf included in the request + fn save_post_pdf(&mut self, name: &str, folder: &str) -> ResultBoxError { + let file = self.post_file(name)?; + + if !is_valid_pdf(&file.buff)? { + self.bad_request(format!("Invalid PDF specified in {} !", name))?; + unreachable!(); + } + + // Avoid memory warnings + let copied_buff = file.buff.clone(); + + // Determine pdf file destination + let target_user_data_folder = prepare_file_creation(self.user_id_ref()?, folder)?; + let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "pdf")?; + let target_sys_path = user_data_path(target_file_path.as_path()); + + std::fs::write(target_sys_path, &copied_buff.as_ref())?; + + Ok(target_file_path.to_string_lossy().to_string()) + } + + /// Get an integer included in the POST request + 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 + 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) + } + } + + fn post_u64(&mut self, name: &str) -> ResultBoxError { + Ok(self.post_string(name)?.parse::()?) + } + + /// Get a boolean included in a POST request + fn post_bool(&mut self, name: &str) -> ResultBoxError { + Ok(self.post_string(name)?.eq("true")) + } + + /// Get an optional boolean included in post request + fn post_bool_opt(&mut self, name: &str, default: bool) -> bool { + self.post_bool(name).unwrap_or(default) + } + + + /// Get an email included in the request + 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 + 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 + 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 + 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 + fn post_virtual_directory(&mut self, name: &str) -> ResultBoxError { + let dir = self.post_string(name)?; + + if !virtual_directories_utils::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 + 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 optional string included in the request, with HTML codes removed + 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 + 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 + 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 + 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 + 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 + 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)) + } + + /// Get information about a post whose ID was specified in the request + fn post_post_with_access(&mut self, name: &str, min_level: PostAccessLevel) -> ResultBoxError { + let post_id = self.post_u64(name)?; + let post = self.ok_or_not_found( + posts_helper::get_single(post_id), + "Requested post not found!", + )?; + + if posts_helper::get_access_level(&post, &self.user_id_opt())? < min_level { + self.forbidden("You are not allowed to access this post information!".to_string())?; + } + + Ok(post) + } + + /// Get information about a comment whose ID is specified in the request + fn post_comment_with_access(&mut self, name: &str) -> ResultBoxError { + let comment_id = self.post_u64(name)?; + let comment = self.ok_or_not_found( + comments_helper::get_single(comment_id), + "Specified comment not found!", + )?; + + if comment.user_id != self.user_id_or_invalid() { + let post = posts_helper::get_single(comment.post_id)?; + if posts_helper::get_access_level(&post, &self.user_id_opt())? == PostAccessLevel::NO_ACCESS { + self.forbidden("You are not allowed to access this post information !".to_string())?; + } + } + + Ok(comment) + } + + /// Get information about a comment specified in the request for which user has full access + fn post_comment_with_full_access(&mut self, name: &str) -> ResultBoxError { + let comment = self.post_comment_with_access(name)?; + + if comment.user_id != self.user_id()? { + self.forbidden("You are not the owner of this comment!".to_string())?; + } + + Ok(comment) + } + + /// Get the ID of a movie included in the request + fn post_movie_id(&mut self, name: &str) -> ResultBoxError { + let movie_id = self.post_u64(name)?; + + if !movies_helper::does_user_has(self.user_id_ref()?, movie_id)? { + self.forbidden("You are not authorized to use this movie!".to_string())?; + } + + Ok(movie_id) + } + + /// Get a content of a post and satinize it + fn post_content(&mut self, name: &str, min_len: usize, required: bool) -> ResultBoxError { + let content = self.post_string_opt(name, min_len, required)?; + + if content.contains("data:image") { + self.forbidden("Please do not include inline images!".to_string())?; + } + + if min_len > 0 && required && !check_string_before_insert(&content) { + self.forbidden(format!("The content inside {} was rejected!", name))?; + } + + Ok(remove_html_nodes(&content)) + } + + /// Check the password of the current user + fn need_user_password(&mut self, field: &str) -> ResultBoxError { + let password = self.post_string_opt(field, PASSWORD_MIN_LENGTH, true)?; + + if !account_helper::check_user_password(self.user_id_ref()?, &password)? { + self.forbidden("Invalid password!".to_string())?; + } + + Ok(()) + } + + /// Get an emoji shortcut included in a POST request + fn post_emoji_shortcut(&mut self, field: &str) -> ResultBoxError { + let emoji_shortcut = self.post_string(field)?; + + if !check_emoji_code(&emoji_shortcut) { + self.bad_request("Invalid emoji shortcut code!".to_string())?; + } + + Ok(emoji_shortcut) + } + + /// Get information about an emoji included in a POST request + fn post_emoji_id(&mut self, field: &str) -> ResultBoxError { + let emoji_id = self.post_u64(field)?; + + let info = self.ok_or_not_found( + custom_emojies_helper::get_single(emoji_id), + "Requested emoji not found!", + )?; + + if &info.user_id != self.user_id_ref()? { + self.forbidden("You do not own this emoji!".to_string())?; + } + + Ok(info) + } +} \ No newline at end of file diff --git a/src/data/http_request_handler.rs b/src/data/http_request_handler.rs index 5468125..120eb91 100644 --- a/src/data/http_request_handler.rs +++ b/src/data/http_request_handler.rs @@ -1,53 +1,24 @@ 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 actix_web::dev::HttpResponseBuilder; +use actix_web::http::{HeaderName, HeaderValue, StatusCode}; use serde::Serialize; use crate::api_data::http_error::HttpError; -use crate::constants::PASSWORD_MIN_LENGTH; use crate::controllers::routes::RequestResult; use crate::data::api_client::APIClient; -use crate::data::comment::Comment; +use crate::data::base_request_handler::{BaseRequestHandler, RequestValue}; use crate::data::config::conf; -use crate::data::custom_emoji::CustomEmoji; -use crate::data::error::{ExecError, ResultBoxError}; -use crate::data::group::GroupAccessLevel; -use crate::data::group_id::GroupID; -use crate::data::post::{Post, PostAccessLevel}; +use crate::data::error::ResultBoxError; use crate::data::user::UserID; -use crate::helpers::{account_helper, api_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, movies_helper, posts_helper, user_helper, virtual_directory_helper}; -use crate::helpers::virtual_directory_helper::VirtualDirType; -use crate::utils::pdf_utils::is_valid_pdf; -use crate::utils::string_utils::{check_emoji_code, check_string_before_insert, 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; +use crate::helpers::{account_helper, api_helper}; /// 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, @@ -94,100 +65,6 @@ impl HttpRequestHandler { 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))) - } - - /// Conflict (409) - pub fn conflict(&mut self, message: String) -> RequestResult { - self.response = Some(HttpResponse::Conflict().json( - HttpError::new(409, &message))); - Err(Box::new(ExecError::new(&message))) - } - - /// Too many requests (429) - pub fn too_many_requests(&mut self, message: &str) -> RequestResult { - self.response = Some(HttpResponse::TooManyRequests().json( - HttpError::new(429, 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 bad request - pub fn ok_or_forbidden(&mut self, res: ResultBoxError, msg: &str) -> ResultBoxError { - match res { - Ok(e) => Ok(e), - Err(err) => { - println!("Error leading to access forbidden: {}", err); - self.forbidden(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 { @@ -199,71 +76,6 @@ impl HttpRequestHandler { self.client.as_ref().unwrap() } - /// Get the remote IP address - pub fn remote_ip(&self) -> String { - let mut ip = self.request.peer_addr().unwrap().ip().to_string(); - - // We check if the request comes from a trusted reverse proxy - if let Some(proxy) = conf().proxy.as_ref() { - if ip.eq(proxy) { - if let Some(header) = self.request.headers().get("X-Forwarded-For") { - let header: Vec = header - .to_str() - .unwrap() - .to_string() - .split(",") - .map(|f| f.to_string()) - .collect(); - - if header.len() > 0 { - ip = header[0].to_string(); - } - } - } - } - - ip - } - - /// 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")?; @@ -319,417 +131,53 @@ impl HttpRequestHandler { } } } +} - /// 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 } +impl BaseRequestHandler for HttpRequestHandler { + /// Get request parameter + fn post_parameter_opt(&self, name: &str) -> Option<&RequestValue> { + self.body.get(name) } - /// 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!(); + /// Set request response + fn set_response(&mut self, data: T) -> RequestResult { + self.response = Some(HttpResponse::Ok().json(data)); + Ok(()) } - /// 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 { + /// Set request error + fn set_error(&mut self, error: HttpError) { + self.response = Some(HttpResponseBuilder::new(StatusCode::from_u16(error.error.code).unwrap()) + .json(error)); + } - // Load image - let file = self.post_file(name)?; - let mut image = image::load_from_memory(file.buff.as_ref())?; + /// Get the remote IP address + fn remote_ip(&self) -> String { + let mut ip = self.request.peer_addr().unwrap().ip().to_string(); - if image.width() > max_w || image.height() > max_h { - image = image.resize(max_w, max_h, image::imageops::FilterType::Nearest); - } + // We check if the request comes from a trusted reverse proxy + if let Some(proxy) = conf().proxy.as_ref() { + if ip.eq(proxy) { + if let Some(header) = self.request.headers().get("X-Forwarded-For") { + let header: Vec = header + .to_str() + .unwrap() + .to_string() + .split(",") + .map(|f| f.to_string()) + .collect(); - // 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), - }; + if header.len() > 0 { + ip = header[0].to_string(); + } } } } - - // 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()) + ip } - /// Save a pdf included in the request - pub fn save_post_pdf(&mut self, name: &str, folder: &str) -> ResultBoxError { - let file = self.post_file(name)?; - - if !is_valid_pdf(&file.buff)? { - self.bad_request(format!("Invalid PDF specified in {} !", name))?; - unreachable!(); - } - - // Avoid memory warnings - let copied_buff = file.buff.clone(); - - // Determine pdf file destination - let target_user_data_folder = prepare_file_creation(self.user_id_ref()?, folder)?; - let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "pdf")?; - let target_sys_path = user_data_path(target_file_path.as_path()); - - std::fs::write(target_sys_path, &copied_buff.as_ref())?; - - 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)) - } - - /// Get information about a post whose ID was specified in the request - pub fn post_post_with_access(&mut self, name: &str, min_level: PostAccessLevel) -> ResultBoxError { - let post_id = self.post_u64(name)?; - let post = self.ok_or_not_found( - posts_helper::get_single(post_id), - "Requested post not found!", - )?; - - if posts_helper::get_access_level(&post, &self.user_id_opt())? < min_level { - self.forbidden("You are not allowed to access this post information!".to_string())?; - } - - Ok(post) - } - - /// Get information about a comment whose ID is specified in the request - pub fn post_comment_with_access(&mut self, name: &str) -> ResultBoxError { - let comment_id = self.post_u64(name)?; - let comment = self.ok_or_not_found( - comments_helper::get_single(comment_id), - "Specified comment not found!", - )?; - - if comment.user_id != self.user_id_or_invalid() { - let post = posts_helper::get_single(comment.post_id)?; - if posts_helper::get_access_level(&post, &self.user_id_opt())? == PostAccessLevel::NO_ACCESS { - self.forbidden("You are not allowed to access this post information !".to_string())?; - } - } - - Ok(comment) - } - - /// Get information about a comment specified in the request for which user has full access - pub fn post_comment_with_full_access(&mut self, name: &str) -> ResultBoxError { - let comment = self.post_comment_with_access(name)?; - - if comment.user_id != self.user_id()? { - self.forbidden("You are not the owner of this comment!".to_string())?; - } - - Ok(comment) - } - - /// Get the ID of a movie included in the request - pub fn post_movie_id(&mut self, name: &str) -> ResultBoxError { - let movie_id = self.post_u64(name)?; - - if !movies_helper::does_user_has(self.user_id_ref()?, movie_id)? { - self.forbidden("You are not authorized to use this movie!".to_string())?; - } - - Ok(movie_id) - } - - /// Get a content of a post and satinize it - pub fn post_content(&mut self, name: &str, min_len: usize, required: bool) -> ResultBoxError { - let content = self.post_string_opt(name, min_len, required)?; - - if content.contains("data:image") { - self.forbidden("Please do not include inline images!".to_string())?; - } - - if min_len > 0 && required && !check_string_before_insert(&content) { - self.forbidden(format!("The content inside {} was rejected!", name))?; - } - - Ok(remove_html_nodes(&content)) - } - - /// Check the password of the current user - pub fn need_user_password(&mut self, field: &str) -> ResultBoxError { - let password = self.post_string_opt(field, PASSWORD_MIN_LENGTH, true)?; - - if !account_helper::check_user_password(self.user_id_ref()?, &password)? { - self.forbidden("Invalid password!".to_string())?; - } - - Ok(()) - } - - /// Get an emoji shortcut included in a POST request - pub fn post_emoji_shortcut(&mut self, field: &str) -> ResultBoxError { - let emoji_shortcut = self.post_string(field)?; - - if !check_emoji_code(&emoji_shortcut) { - self.bad_request("Invalid emoji shortcut code!".to_string())?; - } - - Ok(emoji_shortcut) - } - - /// Get information about an emoji included in a POST request - pub fn post_emoji_id(&mut self, field: &str) -> ResultBoxError { - let emoji_id = self.post_u64(field)?; - - let info = self.ok_or_not_found( - custom_emojies_helper::get_single(emoji_id), - "Requested emoji not found!", - )?; - - if &info.user_id != self.user_id_ref()? { - self.forbidden("You do not own this emoji!".to_string())?; - } - - Ok(info) + fn user_id_opt_ref(&self) -> Option<&UserID> { + self.curr_user_id.as_ref() } } \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 3e76403..a899163 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,6 +1,7 @@ pub mod error; pub mod config; +pub mod base_request_handler; pub mod http_request_handler; pub mod api_client; diff --git a/src/helpers/requests_limit_helper.rs b/src/helpers/requests_limit_helper.rs index eb1ff7d..e6aaf6e 100644 --- a/src/helpers/requests_limit_helper.rs +++ b/src/helpers/requests_limit_helper.rs @@ -7,6 +7,7 @@ use std::sync::{Arc, Mutex}; use crate::constants::LIMIT_COUNTER_LIFETIME; use crate::controllers::routes::{LimitPolicy, Route}; +use crate::data::base_request_handler::BaseRequestHandler; use crate::data::error::{ExecError, ResultBoxError}; use crate::data::http_request_handler::HttpRequestHandler; use crate::utils::date_utils;