2020-05-23 07:37:21 +00:00
|
|
|
use std::collections::HashMap;
|
2020-05-24 16:33:47 +00:00
|
|
|
use std::error::Error;
|
2020-05-23 12:08:22 +00:00
|
|
|
use std::str::FromStr;
|
2020-05-24 16:33:47 +00:00
|
|
|
|
|
|
|
use actix_web::{HttpRequest, HttpResponse, web};
|
|
|
|
use actix_web::http::{HeaderName, HeaderValue};
|
|
|
|
use serde::Serialize;
|
|
|
|
|
2020-05-24 11:09:50 +00:00
|
|
|
use crate::api_data::http_error::HttpError;
|
2020-05-24 16:33:47 +00:00
|
|
|
use crate::controllers::routes::RequestResult;
|
|
|
|
use crate::data::api_client::APIClient;
|
|
|
|
use crate::data::config::conf;
|
|
|
|
use crate::data::error::{ExecError, ResultBoxError};
|
2020-05-24 15:57:47 +00:00
|
|
|
use crate::data::user::UserID;
|
2020-06-12 07:04:43 +00:00
|
|
|
use crate::helpers::{account_helper, api_helper, user_helper, conversations_helper};
|
2020-06-02 11:12:54 +00:00
|
|
|
use crate::utils::virtual_directories_utils::check_virtual_directory;
|
2020-05-21 13:28:07 +00:00
|
|
|
|
|
|
|
/// Http request handler
|
|
|
|
///
|
|
|
|
/// @author Pierre Hubert
|
|
|
|
|
2020-05-23 07:37:21 +00:00
|
|
|
/// Single request body value
|
|
|
|
pub struct RequestValue {
|
|
|
|
pub string: Option<String>
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RequestValue {
|
|
|
|
/// Build a string value
|
|
|
|
pub fn string(s: String) -> RequestValue {
|
|
|
|
RequestValue {
|
|
|
|
string: Some(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-21 13:28:07 +00:00
|
|
|
#[derive(Serialize)]
|
|
|
|
struct SuccessMessage {
|
|
|
|
success: String
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct HttpRequestHandler {
|
|
|
|
request: web::HttpRequest,
|
2020-05-23 07:37:21 +00:00
|
|
|
body: HashMap<String, RequestValue>,
|
|
|
|
response: Option<web::HttpResponse>,
|
2020-05-23 12:08:22 +00:00
|
|
|
headers: HashMap<String, String>,
|
|
|
|
client: Option<APIClient>,
|
2020-05-24 15:57:47 +00:00
|
|
|
curr_user_id: Option<UserID>,
|
2020-05-21 13:28:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl HttpRequestHandler {
|
|
|
|
/// Construct a new request handler
|
2020-05-23 07:37:21 +00:00
|
|
|
pub fn new(req: HttpRequest, body: HashMap<String, RequestValue>) -> HttpRequestHandler {
|
2020-05-21 13:28:07 +00:00
|
|
|
HttpRequestHandler {
|
|
|
|
request: req,
|
2020-05-23 07:37:21 +00:00
|
|
|
body,
|
|
|
|
response: None,
|
2020-05-23 12:08:22 +00:00
|
|
|
headers: HashMap::new(),
|
|
|
|
client: None,
|
2020-05-24 15:57:47 +00:00
|
|
|
curr_user_id: None,
|
2020-05-21 13:28:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check if a response has been set for this request
|
|
|
|
pub fn has_response(&self) -> bool {
|
|
|
|
self.response.is_some()
|
|
|
|
}
|
|
|
|
|
2020-05-23 07:54:13 +00:00
|
|
|
/// Get the response status code, eg. 200 or 404
|
|
|
|
pub fn response_status_code(&self) -> u16 {
|
|
|
|
self.response.as_ref().unwrap().status().as_u16()
|
|
|
|
}
|
|
|
|
|
2020-05-21 13:28:07 +00:00
|
|
|
/// Take the response from this struct
|
2020-05-23 12:08:22 +00:00
|
|
|
pub fn response(self) -> ResultBoxError<HttpResponse> {
|
|
|
|
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)
|
2020-05-21 13:28:07 +00:00
|
|
|
}
|
|
|
|
|
2020-05-23 17:17:48 +00:00
|
|
|
/// Set request response
|
|
|
|
pub fn set_response<T: Serialize>(&mut self, data: T) -> RequestResult {
|
|
|
|
self.response = Some(HttpResponse::Ok().json(data));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-05-21 13:28:07 +00:00
|
|
|
/// Success message
|
|
|
|
pub fn success(&mut self, message: &str) -> RequestResult {
|
|
|
|
self.response = Some(HttpResponse::Ok().json(SuccessMessage {
|
|
|
|
success: message.to_string()
|
|
|
|
}));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-05-23 17:17:48 +00:00
|
|
|
/// Internal error response (500)
|
2020-05-21 13:28:07 +00:00
|
|
|
pub fn internal_error(&mut self, error: Box<dyn Error>) -> RequestResult {
|
|
|
|
self.response = Some(HttpResponse::InternalServerError().json(
|
|
|
|
HttpError::internal_error("Internal server error.")));
|
2020-05-23 08:19:15 +00:00
|
|
|
Err(error)
|
2020-05-21 13:28:07 +00:00
|
|
|
}
|
2020-05-23 07:37:21 +00:00
|
|
|
|
2020-05-23 17:17:48 +00:00
|
|
|
/// Bad request (400)
|
2020-05-23 08:14:21 +00:00
|
|
|
pub fn bad_request(&mut self, message: String) -> RequestResult {
|
|
|
|
self.response = Some(HttpResponse::BadRequest().json(
|
|
|
|
HttpError::bad_request(&message)));
|
2020-05-23 08:19:15 +00:00
|
|
|
Err(Box::new(ExecError::new(&message)))
|
2020-05-23 08:14:21 +00:00
|
|
|
}
|
|
|
|
|
2020-05-23 17:17:48 +00:00
|
|
|
/// Forbidden (401)
|
2020-05-29 16:31:20 +00:00
|
|
|
///
|
|
|
|
/// 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...
|
2020-05-23 17:17:48 +00:00
|
|
|
pub fn forbidden(&mut self, message: String) -> RequestResult {
|
2020-05-29 16:31:20 +00:00
|
|
|
self.response = Some(HttpResponse::Unauthorized().json(
|
2020-05-23 17:17:48 +00:00
|
|
|
HttpError::forbidden(&message)));
|
|
|
|
Err(Box::new(ExecError::new(&message)))
|
|
|
|
}
|
|
|
|
|
2020-05-25 11:25:51 +00:00
|
|
|
/// 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)))
|
|
|
|
}
|
|
|
|
|
2020-05-23 09:00:53 +00:00
|
|
|
/// If result is not OK, return a bad request
|
|
|
|
pub 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!()
|
2020-05-23 12:08:22 +00:00
|
|
|
}
|
2020-05-23 09:00:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:20:08 +00:00
|
|
|
/// If result is not OK, return a 404 not found error
|
|
|
|
pub 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!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 07:54:13 +00:00
|
|
|
/// Get the path of the request
|
|
|
|
pub fn request_path(&self) -> String {
|
|
|
|
self.request.path().to_string()
|
|
|
|
}
|
2020-05-23 07:37:21 +00:00
|
|
|
|
2020-05-23 17:17:48 +00:00
|
|
|
/// Get information about the client which made the request
|
|
|
|
pub fn api_client(&self) -> &APIClient {
|
|
|
|
self.client.as_ref().unwrap()
|
|
|
|
}
|
|
|
|
|
2020-05-23 07:37:21 +00:00
|
|
|
/// 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
|
2020-05-23 08:14:21 +00:00
|
|
|
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())
|
2020-05-23 07:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a post string
|
2020-05-23 08:19:15 +00:00
|
|
|
pub fn post_string(&mut self, name: &str) -> ResultBoxError<String> {
|
2020-05-23 15:09:28 +00:00
|
|
|
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<String> {
|
2020-05-23 07:37:21 +00:00
|
|
|
let param = self.post_parameter(name)?;
|
|
|
|
|
2020-05-23 15:09:28 +00:00
|
|
|
match (¶m.string, required) {
|
|
|
|
(None, true) =>
|
|
|
|
Err(self.bad_request(format!("'{}' is not a string!", name)).unwrap_err()),
|
|
|
|
|
|
|
|
(None, false) => Ok(String::new()),
|
|
|
|
|
|
|
|
(Some(s), _) => {
|
|
|
|
if s.len() >= min_length {
|
|
|
|
Ok(s.to_string())
|
|
|
|
} else {
|
|
|
|
Err(self.bad_request(format!("'{}' is too short!", name)).unwrap_err())
|
|
|
|
}
|
2020-05-23 09:00:53 +00:00
|
|
|
}
|
2020-05-23 07:37:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-23 09:00:53 +00:00
|
|
|
|
2020-05-24 15:57:47 +00:00
|
|
|
/// Check API client tokens
|
|
|
|
pub fn check_client_token(&mut self) -> RequestResult {
|
2020-05-23 09:00:53 +00:00
|
|
|
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),
|
2020-05-23 12:08:22 +00:00
|
|
|
"Client not recognized!",
|
2020-05-23 09:00:53 +00:00
|
|
|
)?;
|
|
|
|
|
2020-05-23 12:08:22 +00:00
|
|
|
|
|
|
|
if let Some(domain) = &client.domain {
|
2020-05-23 15:09:28 +00:00
|
|
|
let allowed_origin = match conf().force_https {
|
2020-05-23 12:08:22 +00:00
|
|
|
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())?;
|
|
|
|
}
|
2020-05-23 15:09:28 +00:00
|
|
|
}
|
2020-05-23 12:08:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.headers.insert("Access-Control-Allow-Origin".to_string(), allowed_origin);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.client = Some(client);
|
|
|
|
|
2020-05-23 09:00:53 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-05-23 15:09:28 +00:00
|
|
|
|
2020-05-24 15:57:47 +00:00
|
|
|
/// Check login token
|
|
|
|
pub fn check_user_token(&mut self) -> RequestResult {
|
|
|
|
let token = self.post_string("userToken1")?;
|
|
|
|
|
|
|
|
// Find user
|
2020-05-24 16:33:47 +00:00
|
|
|
match account_helper::get_user_by_login_token(&token, self.api_client()) {
|
|
|
|
Ok(id) => {
|
|
|
|
self.curr_user_id = Some(id);
|
2020-05-24 15:57:47 +00:00
|
|
|
|
2020-05-24 16:33:47 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2020-05-24 15:57:47 +00:00
|
|
|
}
|
|
|
|
|
2020-05-25 11:25:51 +00:00
|
|
|
/// Get an integer included in the POST request
|
|
|
|
pub fn post_i64(&mut self, name: &str) -> ResultBoxError<i64> {
|
|
|
|
Ok(self.post_string(name)?.parse::<i64>()?)
|
|
|
|
}
|
|
|
|
|
2020-06-12 07:04:43 +00:00
|
|
|
pub fn post_u64(&mut self, name: &str) -> ResultBoxError<u64> {
|
|
|
|
Ok(self.post_string(name)?.parse::<u64>()?)
|
|
|
|
}
|
|
|
|
|
2020-06-03 16:34:01 +00:00
|
|
|
/// Get a boolean included in a POST request
|
|
|
|
pub fn post_bool(&mut self, name: &str) -> ResultBoxError<bool> {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-05-24 15:57:47 +00:00
|
|
|
/// Get user ID. This function assess that a user ID is available to continue
|
|
|
|
pub fn user_id(&self) -> ResultBoxError<UserID> {
|
|
|
|
match self.curr_user_id {
|
|
|
|
Some(s) => Ok(s),
|
|
|
|
None => Err(ExecError::boxed_new("Could not get required user ID!"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 11:53:24 +00:00
|
|
|
/// Get a user ID, if available
|
|
|
|
pub fn user_id_opt(&self) -> Option<UserID> {
|
|
|
|
self.curr_user_id
|
|
|
|
}
|
|
|
|
|
2020-05-23 15:09:28 +00:00
|
|
|
/// Get an email included in the request
|
|
|
|
pub 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)
|
|
|
|
}
|
2020-05-27 11:20:08 +00:00
|
|
|
|
|
|
|
/// Get a list of integers included in the request
|
|
|
|
pub fn post_numbers_list(&mut self, name: &str, min_len: usize) -> ResultBoxError<Vec<i64>> {
|
|
|
|
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::<i64>()?);
|
|
|
|
}
|
|
|
|
|
|
|
|
if list.len() < min_len {
|
|
|
|
self.bad_request(format!("Not enough entries in '{}'!", name))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(list)
|
|
|
|
}
|
2020-05-29 16:15:24 +00:00
|
|
|
|
|
|
|
/// Get the ID of a user included in a POST request
|
|
|
|
pub fn post_user_id(&mut self, name: &str) -> ResultBoxError<UserID> {
|
|
|
|
let user_id = self.post_i64(name)?;
|
|
|
|
|
|
|
|
if user_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))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(user_id)
|
|
|
|
}
|
2020-06-02 11:12:54 +00:00
|
|
|
|
|
|
|
/// Get a virtual directory included in a POST request
|
|
|
|
pub fn post_virtual_directory(&mut self, name: &str) -> ResultBoxError<String> {
|
|
|
|
let dir = self.post_string(name)?;
|
|
|
|
|
|
|
|
if !check_virtual_directory(&dir) {
|
|
|
|
self.bad_request(format!("Invalid virtual directory specified in '{}' !", name))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(dir)
|
|
|
|
}
|
2020-06-12 07:04:43 +00:00
|
|
|
|
|
|
|
/// Get & return the ID of the conversation included in the POST request
|
|
|
|
pub fn post_conv_id(&mut self, name: &str) -> ResultBoxError<u64> {
|
|
|
|
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)
|
|
|
|
}
|
2020-05-21 13:28:07 +00:00
|
|
|
}
|