actix_easy_multipart/
text.rs1use crate::bytes::Bytes;
3use crate::{field_mime, FieldReader, Limits};
4use actix_multipart::Field;
5use actix_web::http::StatusCode;
6use actix_web::{web, Error, HttpRequest, ResponseError};
7use derive_more::{Deref, DerefMut, Display, Error};
8use futures_core::future::LocalBoxFuture;
9use futures_util::FutureExt;
10use serde::de::DeserializeOwned;
11use std::sync::Arc;
12
13#[derive(Debug, Deref, DerefMut)]
18pub struct Text<T: DeserializeOwned>(pub T);
19
20impl<T: DeserializeOwned> Text<T> {
21 pub fn into_inner(self) -> T {
22 self.0
23 }
24}
25
26impl<'t, T: DeserializeOwned + 'static> FieldReader<'t> for Text<T> {
27 type Future = LocalBoxFuture<'t, Result<Self, crate::Error>>;
28
29 fn read_field(req: &'t HttpRequest, field: Field, limits: &'t mut Limits) -> Self::Future {
30 async move {
31 let config = TextConfig::from_req(req);
32 let field_name = field.name().to_owned();
33
34 if config.validate_content_type {
35 let valid = if let Some(mime) = field_mime(&field) {
36 mime.subtype() == mime::PLAIN || mime.suffix() == Some(mime::PLAIN)
37 } else {
38 true
41 };
42 if !valid && config.validate_content_type {
43 return Err(crate::Error::Field {
44 field_name,
45 source: config.map_error(req, TextError::ContentType),
46 });
47 }
48 }
49
50 let bytes = Bytes::read_field(req, field, limits).await?;
51
52 let text =
53 std::str::from_utf8(bytes.data.as_ref()).map_err(|e| crate::Error::Field {
54 field_name: field_name.clone(),
55 source: config.map_error(req, TextError::Utf8Error(e)),
56 })?;
57
58 Ok(Text(serde_plain::from_str(text).map_err(|e| {
59 crate::Error::Field {
60 field_name,
61 source: config.map_error(req, TextError::Deserialize(e)),
62 }
63 })?))
64 }
65 .boxed_local()
66 }
67}
68
69#[derive(Debug, Display, Error)]
70#[non_exhaustive]
71pub enum TextError {
72 #[display(fmt = "Utf8 decoding error: {}", _0)]
74 Utf8Error(std::str::Utf8Error),
75
76 #[display(fmt = "Plain text deserialize error: {}", _0)]
78 Deserialize(serde_plain::Error),
79
80 #[display(fmt = "Content type error")]
82 ContentType,
83}
84
85impl ResponseError for TextError {
86 fn status_code(&self) -> StatusCode {
87 StatusCode::BAD_REQUEST
88 }
89}
90
91#[derive(Clone)]
93pub struct TextConfig {
94 err_handler: Option<Arc<dyn Fn(TextError, &HttpRequest) -> Error + Send + Sync>>,
95 validate_content_type: bool,
96}
97
98const DEFAULT_CONFIG: TextConfig = TextConfig {
99 err_handler: None,
100 validate_content_type: true,
101};
102
103impl TextConfig {
104 pub fn error_handler<F>(mut self, f: F) -> Self
105 where
106 F: Fn(TextError, &HttpRequest) -> Error + Send + Sync + 'static,
107 {
108 self.err_handler = Some(Arc::new(f));
109 self
110 }
111
112 fn from_req(req: &HttpRequest) -> &Self {
115 req.app_data::<Self>()
116 .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
117 .unwrap_or(&DEFAULT_CONFIG)
118 }
119
120 fn map_error(&self, req: &HttpRequest, err: TextError) -> Error {
121 if let Some(err_handler) = self.err_handler.as_ref() {
122 (*err_handler)(err, req)
123 } else {
124 err.into()
125 }
126 }
127
128 pub fn validate_content_type(mut self, validate_content_type: bool) -> Self {
132 self.validate_content_type = validate_content_type;
133 self
134 }
135}
136
137impl Default for TextConfig {
138 fn default() -> Self {
139 DEFAULT_CONFIG
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use crate::tests::send_form;
146 use crate::text::{Text, TextConfig};
147 use crate::MultipartForm;
148 use actix_multipart_rfc7578::client::multipart;
149 use actix_web::http::StatusCode;
150 use actix_web::{web, App, HttpResponse, Responder};
151 use std::io::Cursor;
152
153 #[derive(MultipartForm)]
154 struct TextForm {
155 number: Text<i32>,
156 }
157
158 async fn test_text_route(form: MultipartForm<TextForm>) -> impl Responder {
159 assert_eq!(*form.number, 1025);
160 HttpResponse::Ok().finish()
161 }
162
163 #[actix_rt::test]
164 async fn test_content_type_validation() {
165 let srv = actix_test::start(|| {
166 App::new()
167 .route("/", web::post().to(test_text_route))
168 .app_data(TextConfig::default().validate_content_type(true))
169 });
170
171 let bytes = Cursor::new("1025");
173 let mut form = multipart::Form::default();
174 form.add_reader_file_with_mime("number", bytes, "", mime::APPLICATION_OCTET_STREAM);
175 let response = send_form(&srv, form, "/").await;
176 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
177
178 let bytes = Cursor::new("1025");
180 let mut form = multipart::Form::default();
181 form.add_reader_file_with_mime("number", bytes, "", mime::TEXT_PLAIN);
182 let response = send_form(&srv, form, "/").await;
183 assert_eq!(response.status(), StatusCode::OK);
184 }
185}