diff --git a/Cargo.lock b/Cargo.lock index cc1b8e9..7f47935 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,25 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-multipart" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4397935fca2a37a5353f94faa758fb176712806f605466b5a60373b204f0d836" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "bytes", + "derive_more", + "futures", + "httparse", + "log", + "mime", + "time", + "twoway", +] + [[package]] name = "actix-router" version = "0.2.4" @@ -507,6 +526,7 @@ dependencies = [ name = "comunic_server" version = "0.1.0" dependencies = [ + "actix-multipart", "actix-rt", "actix-web", "chrono", @@ -1901,6 +1921,16 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "twoway" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc" +dependencies = [ + "memchr", + "unchecked-index", +] + [[package]] name = "twox-hash" version = "1.5.0" @@ -1916,6 +1946,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + [[package]] name = "unicode-bidi" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index a3bc8dd..d004a55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ yaml-rust = "0.4.3" mysql = "18.2.0" actix-web = "2.0.0" actix-rt = "1.1.1" +actix-multipart = "0.2.0" serde = "1.0.110" serde_json = "1.0.53" futures = "0.3.5" diff --git a/src/constants.rs b/src/constants.rs index 7b26b0f..3c2f767 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -37,4 +37,7 @@ pub mod database_tables_names { pub const DEFAULT_ACCOUNT_IMAGE: &str = "avatars/0Reverse.png"; /// The account image to show for users who are not allowed to access other users account images -pub const ERROR_ACCOUNT_IMAGE: &str = "avatars/0Red.png"; \ No newline at end of file +pub const ERROR_ACCOUNT_IMAGE: &str = "avatars/0Red.png"; + +/// Maximum requests size (50 Mo) +pub const MAX_REQUEST_SIZE: u64 = 50000000; \ No newline at end of file diff --git a/src/controllers/server.rs b/src/controllers/server.rs index 37a4c64..6404df9 100644 --- a/src/controllers/server.rs +++ b/src/controllers/server.rs @@ -1,24 +1,42 @@ -use actix_web::{App, HttpResponse, HttpServer, web, http, FromRequest, HttpRequest, HttpMessage}; - -use crate::controllers::routes::{get_routes, Route, RequestResult}; -use crate::data::config::Config; -use crate::controllers::routes::Method::{GET, POST}; -use crate::data::http_request_handler::{HttpRequestHandler, RequestValue}; -use actix_web::dev::{PayloadStream, Payload, Decompress}; -use actix_web::web::{BytesMut}; -use futures::future::{LocalBoxFuture}; -use futures::{FutureExt, StreamExt}; -use actix_web::error::{ErrorBadRequest}; -use encoding_rs::UTF_8; use std::collections::HashMap; -use percent_encoding::{percent_decode_str}; -use crate::api_data::http_error::HttpError; +use std::io::Take; +use std::pin::Pin; +use actix_web::{App, FromRequest, http, HttpMessage, HttpRequest, HttpResponse, HttpServer, web}; +use actix_web::dev::{Decompress, Payload, PayloadStream}; +use actix_web::error::{ErrorBadRequest, PayloadError}; +use actix_web::web::{Bytes, BytesMut}; +use encoding_rs::UTF_8; +use futures::{FutureExt, Stream, StreamExt}; +use futures::future::LocalBoxFuture; +use futures::task::{Context, Poll}; +use percent_encoding::percent_decode_str; + +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::config::Config; +use crate::data::http_request_handler::{HttpRequestHandler, RequestValue}; /// Main server functions /// /// @author Pierre Hubert +/// Custom stream to give it a limit +struct LimitedStream { + stream: Box> + Unpin + 'static> +} + +impl<'a> Stream for LimitedStream +{ + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(self.stream.as_mut()).poll_next(cx) + } +} + /// Custom request value struct CustomRequest { req: web::HttpRequest, @@ -33,9 +51,25 @@ impl FromRequest for CustomRequest { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req = req.clone(); - let payload = payload.take(); + let payload = Box::new(payload.take()); + + let payload = LimitedStream { + stream: payload + }; + async move { + + // Check the size, if provided + if req.headers().contains_key("Content-Length") { + if let Some(v) = req.headers().get("Content-Length") { + if String::from_utf8_lossy(v.as_bytes()).parse::().unwrap_or(0) > MAX_REQUEST_SIZE { + return Err(actix_web::error::ErrorBadRequest("Request too big!")); + } + } + } + + let mut body_args = HashMap::new(); // Process "application/x-www-form-urlencoded" requests @@ -88,12 +122,21 @@ impl FromRequest for CustomRequest { // Add the value to the body body_args.insert( percent_decode_str(args[0]).decode_utf8_lossy().to_string(), - RequestValue::string(percent_decode_str(args[1]).decode_utf8_lossy().to_string()) + RequestValue::string(percent_decode_str(args[1]).decode_utf8_lossy().to_string()), ); } } } + // Process "multipart/form-data" request + else if req.content_type().starts_with("multipart/form-data") { + let mut req = actix_multipart::Multipart::new(req.headers(), payload); + + while let Some(el) = req.next().await { + let mut field = el?; + } + } + Ok(CustomRequest { req: req.clone(), body: body_args, @@ -153,8 +196,7 @@ async fn process_request(custom_req: CustomRequest) -> HttpResponse { // Set default error response if required Err(e) => { - - let err_msg= e.to_string(); + let err_msg = e.to_string(); if !request.has_response() { request.internal_error(e).unwrap_err();