diff --git a/actix-multipart/src/form/json.rs b/actix-multipart/src/form/json.rs index fb90a82b9..bb4e03bf6 100644 --- a/actix-multipart/src/form/json.rs +++ b/actix-multipart/src/form/json.rs @@ -131,14 +131,13 @@ impl Default for JsonConfig { #[cfg(test)] mod tests { - use std::{collections::HashMap, io::Cursor}; + use std::collections::HashMap; - use actix_multipart_rfc7578::client::multipart; use actix_web::{http::StatusCode, web, App, HttpResponse, Responder}; + use bytes::Bytes; use crate::form::{ json::{Json, JsonConfig}, - tests::send_form, MultipartForm, }; @@ -155,6 +154,8 @@ mod tests { HttpResponse::Ok().finish() } + const TEST_JSON: &str = r#"{"key1": "value1", "key2": "value2"}"#; + #[actix_rt::test] async fn test_json_without_content_type() { let srv = actix_test::start(|| { @@ -163,10 +164,16 @@ mod tests { .app_data(JsonConfig::default().validate_content_type(false)) }); - let mut form = multipart::Form::default(); - form.add_text("json", "{\"key1\": \"value1\", \"key2\": \"value2\"}"); - let response = send_form(&srv, form, "/").await; - assert_eq!(response.status(), StatusCode::OK); + let (body, headers) = crate::test::create_form_data_payload_and_headers( + "json", + None, + None, + Bytes::from_static(TEST_JSON.as_bytes()), + ); + let mut req = srv.post("/"); + *req.headers_mut() = headers; + let res = req.send_body(body).await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); } #[actix_rt::test] @@ -178,17 +185,27 @@ mod tests { }); // Deny because wrong content type - let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}"); - let mut form = multipart::Form::default(); - form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_OCTET_STREAM); - let response = send_form(&srv, form, "/").await; - assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let (body, headers) = crate::test::create_form_data_payload_and_headers( + "json", + None, + Some(mime::APPLICATION_OCTET_STREAM), + Bytes::from_static(TEST_JSON.as_bytes()), + ); + let mut req = srv.post("/"); + *req.headers_mut() = headers; + let res = req.send_body(body).await.unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); // Allow because correct content type - let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}"); - let mut form = multipart::Form::default(); - form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_JSON); - let response = send_form(&srv, form, "/").await; - assert_eq!(response.status(), StatusCode::OK); + let (body, headers) = crate::test::create_form_data_payload_and_headers( + "json", + None, + Some(mime::APPLICATION_JSON), + Bytes::from_static(TEST_JSON.as_bytes()), + ); + let mut req = srv.post("/"); + *req.headers_mut() = headers; + let res = req.send_body(body).await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); } } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c08031eba..ae32cf0cd 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -863,13 +863,15 @@ mod tests { test::TestRequest, FromRequest, }; - use bytes::Bytes; + use bytes::{BufMut as _, Bytes}; use futures_util::{future::lazy, StreamExt as _}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use super::*; + const BOUNDARY: &str = "abbc761f78ff4d7cb7573b5a23f96ef0"; + #[actix_rt::test] async fn test_boundary() { let headers = HeaderMap::new(); @@ -965,6 +967,26 @@ mod tests { } fn create_simple_request_with_header() -> (Bytes, HeaderMap) { + let (body, headers) = crate::test::create_form_data_payload_and_headers_with_boundary( + BOUNDARY, + "file", + Some("fn.txt".to_owned()), + Some(mime::TEXT_PLAIN_UTF_8), + Bytes::from_static(b"data"), + ); + + let mut buf = BytesMut::with_capacity(body.len() + 14); + + // add junk before form to test pre-boundary data rejection + buf.put("testasdadsad\r\n".as_bytes()); + + buf.put(body); + + (buf.freeze(), headers) + } + + // TODO: use test utility when multi-file support is introduced + fn create_double_request_with_header() -> (Bytes, HeaderMap) { let bytes = Bytes::from( "testasdadsad\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ @@ -990,7 +1012,7 @@ mod tests { #[actix_rt::test] async fn test_multipart_no_end_crlf() { let (sender, payload) = create_stream(); - let (mut bytes, headers) = create_simple_request_with_header(); + let (mut bytes, headers) = create_double_request_with_header(); let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf sender.send(Ok(bytes_stripped)).unwrap(); @@ -1017,7 +1039,7 @@ mod tests { #[actix_rt::test] async fn test_multipart() { let (sender, payload) = create_stream(); - let (bytes, headers) = create_simple_request_with_header(); + let (bytes, headers) = create_double_request_with_header(); sender.send(Ok(bytes)).unwrap(); @@ -1080,7 +1102,7 @@ mod tests { #[actix_rt::test] async fn test_stream() { - let (bytes, headers) = create_simple_request_with_header(); + let (bytes, headers) = create_double_request_with_header(); let payload = SlowStream::new(bytes); let mut multipart = Multipart::new(&headers, payload); @@ -1319,7 +1341,7 @@ mod tests { #[actix_rt::test] async fn test_drop_field_awaken_multipart() { let (sender, payload) = create_stream(); - let (bytes, headers) = create_simple_request_with_header(); + let (bytes, headers) = create_double_request_with_header(); sender.send(Ok(bytes)).unwrap(); drop(sender); // eof diff --git a/actix-multipart/src/test.rs b/actix-multipart/src/test.rs index 5e0250364..77d918283 100644 --- a/actix-multipart/src/test.rs +++ b/actix-multipart/src/test.rs @@ -15,6 +15,10 @@ const BOUNDARY_PREFIX: &str = "------------------------"; /// /// Returned header map can be extended or merged with existing headers. /// +/// Multipart boundary used is a random alphanumeric string. +/// +/// # Examples +/// /// ``` /// use actix_multipart::test::create_form_data_payload_and_headers; /// use actix_web::test::TestRequest; @@ -29,6 +33,7 @@ const BOUNDARY_PREFIX: &str = "------------------------"; /// ); /// /// assert!(find(&body, b"foo").is_some()); +/// assert!(find(&body, b"lorem.txt").is_some()); /// assert!(find(&body, b"text/plain; charset=utf-8").is_some()); /// assert!(find(&body, b"Lorem ipsum.").is_some()); /// @@ -41,7 +46,14 @@ const BOUNDARY_PREFIX: &str = "------------------------"; /// .set_payload(body) /// .to_http_request(); /// -/// assert!(req.headers().contains_key("content-type")); +/// assert!( +/// req.headers() +/// .get("content-type") +/// .unwrap() +/// .to_str() +/// .unwrap() +/// .starts_with("multipart/form-data; boundary=\"") +/// ); /// ``` pub fn create_form_data_payload_and_headers( name: &str, @@ -70,7 +82,7 @@ pub fn create_form_data_payload_and_headers_with_boundary( content_type: Option, file: Bytes, ) -> (Bytes, HeaderMap) { - let mut buf = BytesMut::new(); + let mut buf = BytesMut::with_capacity(file.len() + 128); let boundary_str = [BOUNDARY_PREFIX, boundary].concat(); let boundary = boundary_str.as_bytes(); @@ -128,7 +140,7 @@ mod tests { .unwrap() .parse::() .unwrap() - .get_param("boundary") + .get_param(mime::BOUNDARY) .unwrap() .as_str() .to_owned() @@ -181,6 +193,7 @@ mod tests { ); } + /// Test using an external library to prevent the two-wrongs-make-a-right class of errors. #[actix_web::test] async fn ecosystem_compat() { let (pl, headers) = create_form_data_payload_and_headers(