diff --git a/.gitignore b/.gitignore index 5538bde..5c95bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ upload.png # any dotenv files .env + +# file uploads +/tmp diff --git a/Cargo.lock b/Cargo.lock index 9125591..7aca074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3419,7 +3419,7 @@ dependencies = [ "actix-web 3.3.2", "bytes 0.5.6", "env_logger 0.8.4", - "futures", + "futures-util", "log", "serde 1.0.130", "serde_json", @@ -3951,8 +3951,9 @@ dependencies = [ "actix-multipart", "actix-web 3.3.2", "async-std", - "futures", + "futures-util", "sanitize-filename", + "uuid", ] [[package]] @@ -3961,8 +3962,9 @@ version = "0.3.0" dependencies = [ "actix-multipart", "actix-web 3.3.2", - "futures", + "futures-util", "sanitize-filename", + "uuid", ] [[package]] diff --git a/forms/multipart-async-std/Cargo.toml b/forms/multipart-async-std/Cargo.toml index ec7072c..15cfdb1 100644 --- a/forms/multipart-async-std/Cargo.toml +++ b/forms/multipart-async-std/Cargo.toml @@ -12,6 +12,7 @@ readme = "README.md" [dependencies] actix-web = "3" actix-multipart = "0.3" -futures = "0.3.5" -async-std = "1.8.0" +futures-util = "0.3" +async-std = "1.8" sanitize-filename = "0.2" +uuid = { version = "0.8", features = ["v4"] } diff --git a/forms/multipart-async-std/src/main.rs b/forms/multipart-async-std/src/main.rs index 7e24d0c..3fb4ad1 100644 --- a/forms/multipart-async-std/src/main.rs +++ b/forms/multipart-async-std/src/main.rs @@ -1,24 +1,30 @@ use actix_multipart::Multipart; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; use async_std::prelude::*; -use futures::{StreamExt, TryStreamExt}; +use futures_util::TryStreamExt as _; +use uuid::Uuid; async fn save_file(mut payload: Multipart) -> Result { // iterate over multipart stream while let Ok(Some(mut field)) = payload.try_next().await { - let content_type = field - .content_disposition().ok_or(actix_web::error::ParseError::Incomplete)?; - let filename = content_type - .get_filename().ok_or(actix_web::error::ParseError::Incomplete)?; + let content_disposition = field + .content_disposition() + .ok_or_else(|| HttpResponse::BadRequest().finish())?; + + let filename = content_disposition.get_filename().map_or_else( + || Uuid::new_v4().to_string(), + |f| sanitize_filename::sanitize(f), + ); + let filepath = format!("./tmp/{}", sanitize_filename::sanitize(&filename)); let mut f = async_std::fs::File::create(filepath).await?; // Field in turn is stream of *Bytes* object - while let Some(chunk) = field.next().await { - let data = chunk.unwrap(); - f.write_all(&data).await?; + while let Some(chunk) = field.try_next().await? { + f.write_all(&chunk).await?; } } + Ok(HttpResponse::Ok().into()) } @@ -40,11 +46,9 @@ fn index() -> HttpResponse { #[actix_web::main] async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); + std::env::set_var("RUST_LOG", "info"); async_std::fs::create_dir_all("./tmp").await?; - let ip = "0.0.0.0:3000"; - HttpServer::new(|| { App::new().wrap(middleware::Logger::default()).service( web::resource("/") @@ -52,7 +56,7 @@ async fn main() -> std::io::Result<()> { .route(web::post().to(save_file)), ) }) - .bind(ip)? + .bind(("127.0.0.1", 8080))? .run() .await } diff --git a/forms/multipart/Cargo.toml b/forms/multipart/Cargo.toml index 440aa21..44448e0 100644 --- a/forms/multipart/Cargo.toml +++ b/forms/multipart/Cargo.toml @@ -12,5 +12,7 @@ readme = "README.md" [dependencies] actix-multipart = "0.3" actix-web = "3" -futures = "0.3.1" + +futures-util = "0.3" sanitize-filename = "0.2" +uuid = { version = "0.8", features = ["v4"] } diff --git a/forms/multipart/src/main.rs b/forms/multipart/src/main.rs index 7e1a03b..62cf4d7 100644 --- a/forms/multipart/src/main.rs +++ b/forms/multipart/src/main.rs @@ -2,27 +2,33 @@ use std::io::Write; use actix_multipart::Multipart; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; -use futures::{StreamExt, TryStreamExt}; +use futures_util::TryStreamExt as _; +use uuid::Uuid; async fn save_file(mut payload: Multipart) -> Result { // iterate over multipart stream - while let Ok(Some(mut field)) = payload.try_next().await { - let content_type = field.content_disposition().unwrap(); - let filename = content_type.get_filename().unwrap(); - let filepath = format!("./tmp/{}", sanitize_filename::sanitize(&filename)); + while let Some(mut field) = payload.try_next().await? { + // A multipart/form-data stream has to contain `content_disposition` + let content_disposition = field + .content_disposition() + .ok_or_else(|| HttpResponse::BadRequest().finish())?; + + let filename = content_disposition.get_filename().map_or_else( + || Uuid::new_v4().to_string(), + |f| sanitize_filename::sanitize(f), + ); + let filepath = format!("./tmp/{}", filename); // File::create is blocking operation, use threadpool - let mut f = web::block(|| std::fs::File::create(filepath)) - .await - .unwrap(); + let mut f = web::block(|| std::fs::File::create(filepath)).await?; // Field in turn is stream of *Bytes* object - while let Some(chunk) = field.next().await { - let data = chunk.unwrap(); + while let Some(chunk) = field.try_next().await? { // filesystem operations are blocking, we have to use threadpool - f = web::block(move || f.write_all(&data).map(|_| f)).await?; + f = web::block(move || f.write_all(&chunk).map(|_| f)).await?; } } + Ok(HttpResponse::Ok().into()) } @@ -37,17 +43,13 @@ fn index() -> HttpResponse { "#; - HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html) + HttpResponse::Ok().body(html) } #[actix_web::main] async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); - std::fs::create_dir_all("./tmp").unwrap(); - - let ip = "0.0.0.0:3000"; + std::env::set_var("RUST_LOG", "info"); + std::fs::create_dir_all("./tmp")?; HttpServer::new(|| { App::new().wrap(middleware::Logger::default()).service( @@ -56,7 +58,7 @@ async fn main() -> std::io::Result<()> { .route(web::post().to(save_file)), ) }) - .bind(ip)? + .bind(("127.0.0.1", 8080))? .run() .await } diff --git a/json/jsonrpc/Cargo.toml b/json/jsonrpc/Cargo.toml index ac092c9..d4b9e22 100644 --- a/json/jsonrpc/Cargo.toml +++ b/json/jsonrpc/Cargo.toml @@ -6,9 +6,10 @@ edition = "2018" [dependencies] actix-web = "3" + bytes = "0.5" env_logger = "0.8" -futures = "0.3.1" +futures-util = "0.3" log = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/json/jsonrpc/src/main.rs b/json/jsonrpc/src/main.rs index f377465..336c1dd 100644 --- a/json/jsonrpc/src/main.rs +++ b/json/jsonrpc/src/main.rs @@ -1,14 +1,17 @@ // Allow this lint since it's fine to use type directly in the short example. #![allow(clippy::type_complexity)] -use std::error; -use std::pin::Pin; -use std::sync::{Arc, RwLock}; -use std::time::Duration; +use std::{ + error, + future::Future, + pin::Pin, + sync::{Arc, RwLock}, + time::Duration, +}; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; use bytes::Bytes; -use futures::{Future, FutureExt}; +use futures_util::FutureExt as _; use serde_json::Value; #[allow(dead_code)]