|
| 1 | +//! Multipart form derive macro for Actix Web. |
| 2 | +//! |
| 3 | +//! See [`macro@MultipartForm`] for usage examples. |
| 4 | +
|
| 5 | +#![deny(rust_2018_idioms, nonstandard_style)] |
| 6 | +#![warn(future_incompatible)] |
| 7 | +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] |
| 8 | +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] |
| 9 | +#![cfg_attr(docsrs, feature(doc_cfg))] |
| 10 | + |
| 11 | +use std::{collections::HashSet, convert::TryFrom as _}; |
| 12 | + |
| 13 | +use darling::{FromDeriveInput, FromField, FromMeta}; |
| 14 | +use parse_size::parse_size; |
| 15 | +use proc_macro::TokenStream; |
| 16 | +use proc_macro2::Ident; |
| 17 | +use quote::quote; |
| 18 | +use syn::{parse_macro_input, Type}; |
| 19 | + |
| 20 | +#[derive(FromMeta)] |
| 21 | +enum DuplicateField { |
| 22 | + Ignore, |
| 23 | + Deny, |
| 24 | + Replace, |
| 25 | +} |
| 26 | + |
| 27 | +impl Default for DuplicateField { |
| 28 | + fn default() -> Self { |
| 29 | + Self::Ignore |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +#[derive(FromDeriveInput, Default)] |
| 34 | +#[darling(attributes(multipart), default)] |
| 35 | +struct MultipartFormAttrs { |
| 36 | + deny_unknown_fields: bool, |
| 37 | + duplicate_field: DuplicateField, |
| 38 | +} |
| 39 | + |
| 40 | +#[derive(FromField, Default)] |
| 41 | +#[darling(attributes(multipart), default)] |
| 42 | +struct FieldAttrs { |
| 43 | + rename: Option<String>, |
| 44 | + limit: Option<String>, |
| 45 | +} |
| 46 | + |
| 47 | +struct ParsedField<'t> { |
| 48 | + serialization_name: String, |
| 49 | + rust_name: &'t Ident, |
| 50 | + limit: Option<usize>, |
| 51 | + ty: &'t Type, |
| 52 | +} |
| 53 | + |
| 54 | +/// Implements `MultipartCollect` for a struct so that it can be used with the `MultipartForm` |
| 55 | +/// extractor. |
| 56 | +/// |
| 57 | +/// # Basic Use |
| 58 | +/// |
| 59 | +/// Each field type should implement the `FieldReader` trait: |
| 60 | +/// |
| 61 | +/// ``` |
| 62 | +/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; |
| 63 | +/// |
| 64 | +/// #[derive(MultipartForm)] |
| 65 | +/// struct ImageUpload { |
| 66 | +/// description: Text<String>, |
| 67 | +/// timestamp: Text<i64>, |
| 68 | +/// image: TempFile, |
| 69 | +/// } |
| 70 | +/// ``` |
| 71 | +/// |
| 72 | +/// # Optional and List Fields |
| 73 | +/// |
| 74 | +/// You can also use `Vec<T>` and `Option<T>` provided that `T: FieldReader`. |
| 75 | +/// |
| 76 | +/// A [`Vec`] field corresponds to an upload with multiple parts under the [same field |
| 77 | +/// name](https://www.rfc-editor.org/rfc/rfc7578#section-4.3). |
| 78 | +/// |
| 79 | +/// ``` |
| 80 | +/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; |
| 81 | +/// |
| 82 | +/// #[derive(MultipartForm)] |
| 83 | +/// struct Form { |
| 84 | +/// category: Option<Text<String>>, |
| 85 | +/// files: Vec<TempFile>, |
| 86 | +/// } |
| 87 | +/// ``` |
| 88 | +/// |
| 89 | +/// # Field Renaming |
| 90 | +/// |
| 91 | +/// You can use the `#[multipart(rename = "foo")]` attribute to receive a field by a different name. |
| 92 | +/// |
| 93 | +/// ``` |
| 94 | +/// use actix_multipart::form::{tempfile::TempFile, MultipartForm}; |
| 95 | +/// |
| 96 | +/// #[derive(MultipartForm)] |
| 97 | +/// struct Form { |
| 98 | +/// #[multipart(rename = "files[]")] |
| 99 | +/// files: Vec<TempFile>, |
| 100 | +/// } |
| 101 | +/// ``` |
| 102 | +/// |
| 103 | +/// # Field Limits |
| 104 | +/// |
| 105 | +/// You can use the `#[multipart(limit = "<size>")]` attribute to set field level limits. The limit |
| 106 | +/// string is parsed using [parse_size]. |
| 107 | +/// |
| 108 | +/// Note: the form is also subject to the global limits configured using `MultipartFormConfig`. |
| 109 | +/// |
| 110 | +/// ``` |
| 111 | +/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; |
| 112 | +/// |
| 113 | +/// #[derive(MultipartForm)] |
| 114 | +/// struct Form { |
| 115 | +/// #[multipart(limit = "2 KiB")] |
| 116 | +/// description: Text<String>, |
| 117 | +/// |
| 118 | +/// #[multipart(limit = "512 MiB")] |
| 119 | +/// files: Vec<TempFile>, |
| 120 | +/// } |
| 121 | +/// ``` |
| 122 | +/// |
| 123 | +/// # Unknown Fields |
| 124 | +/// |
| 125 | +/// By default fields with an unknown name are ignored. They can be rejected using the |
| 126 | +/// `#[multipart(deny_unknown_fields)]` attribute: |
| 127 | +/// |
| 128 | +/// ``` |
| 129 | +/// # use actix_multipart::form::MultipartForm; |
| 130 | +/// #[derive(MultipartForm)] |
| 131 | +/// #[multipart(deny_unknown_fields)] |
| 132 | +/// struct Form { } |
| 133 | +/// ``` |
| 134 | +/// |
| 135 | +/// # Duplicate Fields |
| 136 | +/// |
| 137 | +/// The behaviour for when multiple fields with the same name are received can be changed using the |
| 138 | +/// `#[multipart(duplicate_field = "<behavior>")]` attribute: |
| 139 | +/// |
| 140 | +/// - "ignore": (default) Extra fields are ignored. I.e., the first one is persisted. |
| 141 | +/// - "deny": A `MultipartError::UnsupportedField` error response is returned. |
| 142 | +/// - "replace": Each field is processed, but only the last one is persisted. |
| 143 | +/// |
| 144 | +/// Note that `Vec` fields will ignore this option. |
| 145 | +/// |
| 146 | +/// ``` |
| 147 | +/// # use actix_multipart::form::MultipartForm; |
| 148 | +/// #[derive(MultipartForm)] |
| 149 | +/// #[multipart(duplicate_field = "deny")] |
| 150 | +/// struct Form { } |
| 151 | +/// ``` |
| 152 | +/// |
| 153 | +/// [parse_size]: https://docs.rs/parse-size/1/parse_size |
| 154 | +#[proc_macro_derive(MultipartForm, attributes(multipart))] |
| 155 | +pub fn impl_multipart_form(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| 156 | + let input: syn::DeriveInput = parse_macro_input!(input); |
| 157 | + |
| 158 | + let name = &input.ident; |
| 159 | + |
| 160 | + let data_struct = match &input.data { |
| 161 | + syn::Data::Struct(data_struct) => data_struct, |
| 162 | + _ => { |
| 163 | + return compile_err(syn::Error::new( |
| 164 | + input.ident.span(), |
| 165 | + "`MultipartForm` can only be derived for structs", |
| 166 | + )) |
| 167 | + } |
| 168 | + }; |
| 169 | + |
| 170 | + let fields = match &data_struct.fields { |
| 171 | + syn::Fields::Named(fields_named) => fields_named, |
| 172 | + _ => { |
| 173 | + return compile_err(syn::Error::new( |
| 174 | + input.ident.span(), |
| 175 | + "`MultipartForm` can only be derived for a struct with named fields", |
| 176 | + )) |
| 177 | + } |
| 178 | + }; |
| 179 | + |
| 180 | + let attrs = match MultipartFormAttrs::from_derive_input(&input) { |
| 181 | + Ok(attrs) => attrs, |
| 182 | + Err(err) => return err.write_errors().into(), |
| 183 | + }; |
| 184 | + |
| 185 | + // Parse the field attributes |
| 186 | + let parsed = match fields |
| 187 | + .named |
| 188 | + .iter() |
| 189 | + .map(|field| { |
| 190 | + let rust_name = field.ident.as_ref().unwrap(); |
| 191 | + let attrs = FieldAttrs::from_field(field).map_err(|err| err.write_errors())?; |
| 192 | + let serialization_name = attrs.rename.unwrap_or_else(|| rust_name.to_string()); |
| 193 | + |
| 194 | + let limit = match attrs.limit.map(|limit| match parse_size(&limit) { |
| 195 | + Ok(size) => Ok(usize::try_from(size).unwrap()), |
| 196 | + Err(err) => Err(syn::Error::new( |
| 197 | + field.ident.as_ref().unwrap().span(), |
| 198 | + format!("Could not parse size limit `{}`: {}", limit, err), |
| 199 | + )), |
| 200 | + }) { |
| 201 | + Some(Err(err)) => return Err(compile_err(err)), |
| 202 | + limit => limit.map(Result::unwrap), |
| 203 | + }; |
| 204 | + |
| 205 | + Ok(ParsedField { |
| 206 | + serialization_name, |
| 207 | + rust_name, |
| 208 | + limit, |
| 209 | + ty: &field.ty, |
| 210 | + }) |
| 211 | + }) |
| 212 | + .collect::<Result<Vec<_>, TokenStream>>() |
| 213 | + { |
| 214 | + Ok(attrs) => attrs, |
| 215 | + Err(err) => return err, |
| 216 | + }; |
| 217 | + |
| 218 | + // Check that field names are unique |
| 219 | + let mut set = HashSet::new(); |
| 220 | + for field in &parsed { |
| 221 | + if !set.insert(field.serialization_name.clone()) { |
| 222 | + return compile_err(syn::Error::new( |
| 223 | + field.rust_name.span(), |
| 224 | + format!("Multiple fields named: `{}`", field.serialization_name), |
| 225 | + )); |
| 226 | + } |
| 227 | + } |
| 228 | + |
| 229 | + // Return value when a field name is not supported by the form |
| 230 | + let unknown_field_result = if attrs.deny_unknown_fields { |
| 231 | + quote!(::std::result::Result::Err( |
| 232 | + ::actix_multipart::MultipartError::UnsupportedField(field.name().to_string()) |
| 233 | + )) |
| 234 | + } else { |
| 235 | + quote!(::std::result::Result::Ok(())) |
| 236 | + }; |
| 237 | + |
| 238 | + // Value for duplicate action |
| 239 | + let duplicate_field = match attrs.duplicate_field { |
| 240 | + DuplicateField::Ignore => quote!(::actix_multipart::form::DuplicateField::Ignore), |
| 241 | + DuplicateField::Deny => quote!(::actix_multipart::form::DuplicateField::Deny), |
| 242 | + DuplicateField::Replace => quote!(::actix_multipart::form::DuplicateField::Replace), |
| 243 | + }; |
| 244 | + |
| 245 | + // limit() implementation |
| 246 | + let mut limit_impl = quote!(); |
| 247 | + for field in &parsed { |
| 248 | + let name = &field.serialization_name; |
| 249 | + if let Some(value) = field.limit { |
| 250 | + limit_impl.extend(quote!( |
| 251 | + #name => ::std::option::Option::Some(#value), |
| 252 | + )); |
| 253 | + } |
| 254 | + } |
| 255 | + |
| 256 | + // handle_field() implementation |
| 257 | + let mut handle_field_impl = quote!(); |
| 258 | + for field in &parsed { |
| 259 | + let name = &field.serialization_name; |
| 260 | + let ty = &field.ty; |
| 261 | + |
| 262 | + handle_field_impl.extend(quote!( |
| 263 | + #name => ::std::boxed::Box::pin( |
| 264 | + <#ty as ::actix_multipart::form::FieldGroupReader>::handle_field(req, field, limits, state, #duplicate_field) |
| 265 | + ), |
| 266 | + )); |
| 267 | + } |
| 268 | + |
| 269 | + // from_state() implementation |
| 270 | + let mut from_state_impl = quote!(); |
| 271 | + for field in &parsed { |
| 272 | + let name = &field.serialization_name; |
| 273 | + let rust_name = &field.rust_name; |
| 274 | + let ty = &field.ty; |
| 275 | + from_state_impl.extend(quote!( |
| 276 | + #rust_name: <#ty as ::actix_multipart::form::FieldGroupReader>::from_state(#name, &mut state)?, |
| 277 | + )); |
| 278 | + } |
| 279 | + |
| 280 | + let gen = quote! { |
| 281 | + impl ::actix_multipart::form::MultipartCollect for #name { |
| 282 | + fn limit(field_name: &str) -> ::std::option::Option<usize> { |
| 283 | + match field_name { |
| 284 | + #limit_impl |
| 285 | + _ => None, |
| 286 | + } |
| 287 | + } |
| 288 | + |
| 289 | + fn handle_field<'t>( |
| 290 | + req: &'t ::actix_web::HttpRequest, |
| 291 | + field: ::actix_multipart::Field, |
| 292 | + limits: &'t mut ::actix_multipart::form::Limits, |
| 293 | + state: &'t mut ::actix_multipart::form::State, |
| 294 | + ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<(), ::actix_multipart::MultipartError>> + 't>> { |
| 295 | + match field.name() { |
| 296 | + #handle_field_impl |
| 297 | + _ => return ::std::boxed::Box::pin(::std::future::ready(#unknown_field_result)), |
| 298 | + } |
| 299 | + } |
| 300 | + |
| 301 | + fn from_state(mut state: ::actix_multipart::form::State) -> ::std::result::Result<Self, ::actix_multipart::MultipartError> { |
| 302 | + Ok(Self { |
| 303 | + #from_state_impl |
| 304 | + }) |
| 305 | + } |
| 306 | + |
| 307 | + } |
| 308 | + }; |
| 309 | + gen.into() |
| 310 | +} |
| 311 | + |
| 312 | +/// Transform a syn error into a token stream for returning. |
| 313 | +fn compile_err(err: syn::Error) -> TokenStream { |
| 314 | + TokenStream::from(err.to_compile_error()) |
| 315 | +} |
0 commit comments