2021-02-05 08:11:30 +00:00
|
|
|
//! # Base Request Handler
|
|
|
|
//!
|
|
|
|
//! Base handling code for all user requests
|
|
|
|
|
|
|
|
|
2021-03-06 08:35:36 +00:00
|
|
|
use std::collections::HashSet;
|
2021-02-05 08:11:30 +00:00
|
|
|
use std::error::Error;
|
2021-03-06 17:09:50 +00:00
|
|
|
use std::str::from_utf8;
|
2021-02-05 08:11:30 +00:00
|
|
|
|
|
|
|
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::data::comment::Comment;
|
2021-03-04 17:51:52 +00:00
|
|
|
use crate::data::conversation::{ConversationMember, ConvID};
|
2021-02-05 08:11:30 +00:00
|
|
|
use crate::data::custom_emoji::CustomEmoji;
|
2021-02-13 13:37:15 +00:00
|
|
|
use crate::data::error::{ExecError, Res, ResultBoxError};
|
2021-02-05 08:11:30 +00:00
|
|
|
use crate::data::group::GroupAccessLevel;
|
|
|
|
use crate::data::group_id::GroupID;
|
|
|
|
use crate::data::post::{Post, PostAccessLevel};
|
|
|
|
use crate::data::user::UserID;
|
2021-02-13 13:37:15 +00:00
|
|
|
use crate::data::user_token::UserAccessToken;
|
2021-02-13 10:12:17 +00:00
|
|
|
use crate::helpers::{account_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, posts_helper, user_helper, virtual_directory_helper};
|
2021-02-05 08:11:30 +00:00
|
|
|
use crate::helpers::virtual_directory_helper::VirtualDirType;
|
2021-02-13 15:15:25 +00:00
|
|
|
use crate::routes::RequestResult;
|
2021-03-06 08:35:36 +00:00
|
|
|
use crate::utils::mp3_utils::is_valid_mp3;
|
|
|
|
use crate::utils::mp4_utils::is_valid_mp4;
|
2021-02-05 08:11:30 +00:00
|
|
|
use crate::utils::pdf_utils::is_valid_pdf;
|
2021-03-06 08:35:36 +00:00
|
|
|
use crate::utils::string_utils::{check_emoji_code, check_html_color, check_string_before_insert, check_url, remove_html_nodes};
|
2021-02-05 08:11:30 +00:00
|
|
|
use crate::utils::user_data_utils::{generate_new_user_data_file_name, prepare_file_creation, user_data_path};
|
|
|
|
use crate::utils::virtual_directories_utils;
|
2021-03-06 08:35:36 +00:00
|
|
|
use crate::utils::zip_utils::is_valid_zip;
|
2021-02-05 08:11:30 +00:00
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
struct SuccessMessage {
|
|
|
|
success: String
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub struct PostFile {
|
|
|
|
pub name: String,
|
2021-02-19 16:08:13 +00:00
|
|
|
pub buff: actix_web::web::Bytes,
|
2021-02-05 08:11:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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<T: Serialize>(&mut self, data: T) -> RequestResult;
|
|
|
|
|
|
|
|
/// Set an error
|
|
|
|
fn set_error(&mut self, error: HttpError);
|
|
|
|
|
|
|
|
/// Get remote IP address
|
|
|
|
fn remote_ip(&self) -> String;
|
|
|
|
|
2021-02-13 13:37:15 +00:00
|
|
|
/// Current user access token
|
|
|
|
fn user_access_token(&self) -> Option<&UserAccessToken>;
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// Current user ID
|
2021-02-13 13:37:15 +00:00
|
|
|
fn user_id_opt_ref(&self) -> Option<&UserID> {
|
|
|
|
self.user_access_token().map(|u| &u.user_id)
|
|
|
|
}
|
2021-02-05 08:11:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
/// Success message
|
|
|
|
fn success(&mut self, message: &str) -> RequestResult {
|
|
|
|
self.set_response(SuccessMessage {
|
|
|
|
success: message.to_string()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-03-06 17:09:50 +00:00
|
|
|
/// Success without message
|
|
|
|
fn ok(&mut self) -> RequestResult {
|
|
|
|
self.set_response(SuccessMessage {
|
|
|
|
success: "OK.".to_string()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// Internal error response (500)
|
|
|
|
fn internal_error(&mut self, error: Box<dyn Error>) -> 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<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
|
|
|
|
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<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
|
|
|
|
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<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
|
|
|
|
match res {
|
|
|
|
Ok(e) => Ok(e),
|
|
|
|
Err(err) => {
|
|
|
|
println!("Error leading to 404 not found: {}", err);
|
|
|
|
self.not_found(msg.to_string())?;
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 13:37:15 +00:00
|
|
|
/// Unwrap an option, returning an error if none is returned
|
|
|
|
fn some_or_internal_error<E>(&mut self, opt: Option<E>, msg: &str) -> Res<E> {
|
|
|
|
match opt {
|
|
|
|
None => {
|
|
|
|
self.internal_error(ExecError::boxed_new(msg))?;
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
Some(e) => Ok(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
|
|
|
|
/// Get a user ID, if available
|
|
|
|
fn user_id_opt(&self) -> Option<UserID> {
|
|
|
|
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<UserID> {
|
|
|
|
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<String> {
|
|
|
|
self.post_string_opt(name, 1, true)
|
|
|
|
}
|
|
|
|
|
2021-02-13 13:37:15 +00:00
|
|
|
/// Get a post string with a given name. If the value is not found, attempt to get the value
|
|
|
|
/// with another name
|
|
|
|
///
|
|
|
|
/// This function is useful to upgrade system
|
|
|
|
fn post_string_with_fallback(&mut self, name: &str, fallback: &str) -> Res<String> {
|
|
|
|
if self.has_post_parameter(name) {
|
|
|
|
self.post_string(name)
|
|
|
|
} else {
|
|
|
|
self.post_string(fallback)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// Get a post string, specifying minimum length
|
|
|
|
fn post_string_opt(&mut self, name: &str, min_length: usize, required: bool)
|
|
|
|
-> ResultBoxError<String> {
|
|
|
|
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()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-04 17:51:52 +00:00
|
|
|
/// Get a string included in the request. If none found, or if string is empty, returns [None]
|
|
|
|
fn post_string_optional(&mut self, name: &str) -> Option<String> {
|
|
|
|
match self.post_parameter_opt(name) {
|
|
|
|
Some(RequestValue::String(s)) => {
|
|
|
|
if s.is_empty() {
|
|
|
|
None
|
|
|
|
} else { Some(s.to_string()) }
|
|
|
|
}
|
|
|
|
_ => None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// 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!();
|
|
|
|
}
|
|
|
|
|
2021-03-06 08:35:36 +00:00
|
|
|
/// Get the mime type of a file included in the request
|
|
|
|
fn post_file_type(&mut self, name: &str) -> Res<String> {
|
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
let filetype = mime_guess::from_path(&file.name)
|
|
|
|
.first()
|
|
|
|
.map(|m| format!("{}/{}", m.type_(), m.subtype()));
|
|
|
|
|
|
|
|
if let None = filetype {
|
|
|
|
self.bad_request(format!("Could not determine file type in '{}' !", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(filetype.unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the extension of a file included in the request
|
|
|
|
fn post_file_ext(&mut self, name: &str, default: &str) -> Res<String> {
|
|
|
|
let suffix = self.post_file_type(name)?
|
|
|
|
.parse::<mime_guess::mime::Mime>()?
|
|
|
|
.suffix()
|
|
|
|
.map(|s| s.as_str())
|
|
|
|
.unwrap_or(default)
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
Ok(suffix)
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// Save an image in user data directory
|
|
|
|
fn save_post_image(&mut self, name: &str, folder: &str, max_w: u32, max_h: u32) -> ResultBoxError<String> {
|
|
|
|
|
|
|
|
// 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())
|
|
|
|
}
|
|
|
|
|
2021-03-06 08:35:36 +00:00
|
|
|
/// Save a file included in the request
|
|
|
|
fn save_post_file(&mut self, name: &str, folder: &str, ext: &str) -> Res<String> {
|
2021-02-05 08:11:30 +00:00
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
// 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)?;
|
2021-03-06 08:35:36 +00:00
|
|
|
let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), ext)?;
|
2021-02-05 08:11:30 +00:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2021-03-06 08:35:36 +00:00
|
|
|
/// Save a pdf included in the request
|
|
|
|
fn save_post_pdf(&mut self, name: &str, folder: &str) -> Res<String> {
|
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
if !is_valid_pdf(&file.buff)? {
|
|
|
|
self.bad_request(format!("Invalid PDF specified in {} !", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.save_post_file(name, folder, "pdf")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Save a mp3 file included in the request
|
|
|
|
fn save_post_mp3(&mut self, name: &str, folder: &str) -> Res<String> {
|
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
if !is_valid_mp3(&file.buff) {
|
|
|
|
self.bad_request(format!("Invalid MP3 file specified in {} !", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.save_post_file(name, folder, "mp3")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Save a mp4 file included in the request
|
|
|
|
fn save_post_mp4(&mut self, name: &str, folder: &str) -> Res<String> {
|
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
if !is_valid_mp4(&file.buff) {
|
|
|
|
self.bad_request(format!("Invalid MP4 file specified in {} !", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.save_post_file(name, folder, "mp4")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Save a zip file included in the request
|
|
|
|
fn save_post_zip(&mut self, name: &str, folder: &str) -> Res<String> {
|
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
if !is_valid_zip(&file.buff) {
|
|
|
|
self.bad_request(format!("Invalid ZIP archive file specified in {} !", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.save_post_file(name, folder, "zip")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Save an office document included in the request
|
|
|
|
fn save_post_office_doc(&mut self, name: &str, folder: &str) -> Res<String> {
|
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
if !is_valid_zip(&file.buff) {
|
|
|
|
self.bad_request(format!("Invalid ZIP archive file specified in {} !", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mime = self.post_file_type(name)?;
|
|
|
|
|
|
|
|
if !mime.starts_with("application/") {
|
|
|
|
self.bad_request(format!("Invalid file name in {} !", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
let ext = &self.post_file_ext(name, "zip")?;
|
|
|
|
self.save_post_file(name, folder, ext)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Save a simple text document included in the request
|
|
|
|
fn save_post_txt_doc(&mut self, name: &str, folder: &str) -> Res<String> {
|
|
|
|
let file = self.post_file(name)?;
|
|
|
|
|
|
|
|
if from_utf8(&file.buff).is_err() {
|
|
|
|
self.bad_request(format!("File in {} is not UTF-8!", name))?;
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
let ext = &self.post_file_ext(name, "txt")?;
|
|
|
|
self.save_post_file(name, folder, ext)
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// Get an integer included in the POST request
|
|
|
|
fn post_i64(&mut self, name: &str) -> ResultBoxError<i64> {
|
|
|
|
Ok(self.post_string(name)?.parse::<i64>()?)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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<u64> {
|
|
|
|
if self.has_post_parameter(name) {
|
|
|
|
Ok(self.post_string(name)?.parse::<u64>()?)
|
|
|
|
} else {
|
|
|
|
Ok(default)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn post_u64(&mut self, name: &str) -> ResultBoxError<u64> {
|
|
|
|
Ok(self.post_string(name)?.parse::<u64>()?)
|
|
|
|
}
|
|
|
|
|
2021-02-15 18:29:43 +00:00
|
|
|
fn post_positive_u64_opt(&mut self, name: &str) -> Res<Option<u64>> {
|
2021-02-16 15:15:51 +00:00
|
|
|
match self.post_u64_opt(name, 0)? {
|
2021-02-15 18:29:43 +00:00
|
|
|
0 => Ok(None),
|
|
|
|
val => Ok(Some(val))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// Get a boolean included in a POST request
|
|
|
|
fn post_bool(&mut self, name: &str) -> ResultBoxError<bool> {
|
|
|
|
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<String> {
|
|
|
|
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<Vec<u64>> {
|
|
|
|
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::<u64>()?);
|
|
|
|
}
|
|
|
|
|
|
|
|
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<UserID> {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-03-04 17:51:52 +00:00
|
|
|
/// Get a list of users ID included in the request
|
|
|
|
fn post_users_id(&mut self, name: &str) -> ResultBoxError<HashSet<UserID>> {
|
|
|
|
let users = self.post_numbers_list(name, 1)?
|
|
|
|
.iter()
|
|
|
|
.map(|u| UserID::new(u.clone()))
|
|
|
|
.collect::<HashSet<UserID>>();
|
|
|
|
|
|
|
|
for user in &users {
|
|
|
|
if !user_helper::exists(user)? {
|
|
|
|
self.not_found(format!("User {} not found!", user.id()))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(users)
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// 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<UserID> {
|
|
|
|
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<String> {
|
|
|
|
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<String> {
|
|
|
|
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<Option<String>> {
|
|
|
|
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
|
2021-03-04 17:51:52 +00:00
|
|
|
fn post_conv(&mut self, name: &str) -> ResultBoxError<ConversationMember> {
|
|
|
|
let conv_id = ConvID::new(self.post_u64(name)?);
|
|
|
|
let membership = self.ok_or_forbidden(
|
|
|
|
conversations_helper::get_user_membership(&self.user_id()?, conv_id),
|
|
|
|
&format!("You do not belong to conversation {} !", conv_id.id()),
|
|
|
|
)?;
|
2021-02-05 08:11:30 +00:00
|
|
|
|
2021-03-04 17:51:52 +00:00
|
|
|
Ok(membership)
|
2021-02-05 08:11:30 +00:00
|
|
|
}
|
|
|
|
|
2021-03-07 14:06:44 +00:00
|
|
|
/// Get information about a conversation included in the request. The user must be an admin
|
|
|
|
/// of the target conversation
|
|
|
|
fn post_conv_admin(&mut self, name: &str) -> ResultBoxError<ConversationMember> {
|
|
|
|
let conv = self.post_conv(name)?;
|
|
|
|
|
|
|
|
if !conv.is_admin {
|
|
|
|
self.forbidden("You are not an administrator of this conversation!".to_string())?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(conv)
|
|
|
|
}
|
|
|
|
|
2021-02-05 08:11:30 +00:00
|
|
|
/// Get the ID
|
|
|
|
fn post_group_id(&mut self, name: &str) -> ResultBoxError<GroupID> {
|
|
|
|
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<GroupID> {
|
|
|
|
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<Option<String>> {
|
|
|
|
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<Option<String>> {
|
|
|
|
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<Post> {
|
|
|
|
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<Comment> {
|
|
|
|
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<Comment> {
|
|
|
|
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 a content of a post and satinize it
|
|
|
|
fn post_content(&mut self, name: &str, min_len: usize, required: bool) -> ResultBoxError<String> {
|
|
|
|
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<String> {
|
|
|
|
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<CustomEmoji> {
|
|
|
|
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)
|
|
|
|
}
|
2021-03-04 17:51:52 +00:00
|
|
|
|
|
|
|
/// Get a color included in the request
|
|
|
|
fn post_color_opt(&mut self, field: &str) -> Res<Option<String>> {
|
|
|
|
let color = self.post_string_optional(field);
|
|
|
|
|
|
|
|
match color {
|
|
|
|
None => Ok(None),
|
|
|
|
Some(color) => {
|
|
|
|
if !check_html_color(&color) {
|
|
|
|
self.bad_request(format!("Invalid color specified in '{}' !", field))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Some(color))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-05 08:11:30 +00:00
|
|
|
}
|