1 //! multipart/form-data
2 use std::borrow::Cow;
3 use std::fmt;
4 
5 use http::HeaderMap;
6 use mime_guess::Mime;
7 use web_sys::FormData;
8 
9 use super::Body;
10 
11 /// An async multipart/form-data request.
12 pub struct Form {
13     inner: FormParts<Part>,
14 }
15 
16 impl Form {
is_empty(&self) -> bool17     pub(crate) fn is_empty(&self) -> bool {
18         self.inner.fields.is_empty()
19     }
20 }
21 
22 /// A field in a multipart form.
23 pub struct Part {
24     meta: PartMetadata,
25     value: Body,
26 }
27 
28 pub(crate) struct FormParts<P> {
29     pub(crate) fields: Vec<(Cow<'static, str>, P)>,
30 }
31 
32 pub(crate) struct PartMetadata {
33     mime: Option<Mime>,
34     file_name: Option<Cow<'static, str>>,
35     pub(crate) headers: HeaderMap,
36 }
37 
38 pub(crate) trait PartProps {
metadata(&self) -> &PartMetadata39     fn metadata(&self) -> &PartMetadata;
40 }
41 
42 // ===== impl Form =====
43 
44 impl Default for Form {
default() -> Self45     fn default() -> Self {
46         Self::new()
47     }
48 }
49 
50 impl Form {
51     /// Creates a new async Form without any content.
new() -> Form52     pub fn new() -> Form {
53         Form {
54             inner: FormParts::new(),
55         }
56     }
57 
58     /// Add a data field with supplied name and value.
59     ///
60     /// # Examples
61     ///
62     /// ```
63     /// let form = reqwest::multipart::Form::new()
64     ///     .text("username", "seanmonstar")
65     ///     .text("password", "secret");
66     /// ```
text<T, U>(self, name: T, value: U) -> Form where T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>,67     pub fn text<T, U>(self, name: T, value: U) -> Form
68     where
69         T: Into<Cow<'static, str>>,
70         U: Into<Cow<'static, str>>,
71     {
72         self.part(name, Part::text(value))
73     }
74 
75     /// Adds a customized Part.
part<T>(self, name: T, part: Part) -> Form where T: Into<Cow<'static, str>>,76     pub fn part<T>(self, name: T, part: Part) -> Form
77     where
78         T: Into<Cow<'static, str>>,
79     {
80         self.with_inner(move |inner| inner.part(name, part))
81     }
82 
with_inner<F>(self, func: F) -> Self where F: FnOnce(FormParts<Part>) -> FormParts<Part>,83     fn with_inner<F>(self, func: F) -> Self
84     where
85         F: FnOnce(FormParts<Part>) -> FormParts<Part>,
86     {
87         Form {
88             inner: func(self.inner),
89         }
90     }
91 
to_form_data(&self) -> crate::Result<FormData>92     pub(crate) fn to_form_data(&self) -> crate::Result<FormData> {
93         let form = FormData::new()
94             .map_err(crate::error::wasm)
95             .map_err(crate::error::builder)?;
96 
97         for (name, part) in self.inner.fields.iter() {
98             let blob = part.blob()?;
99 
100             if let Some(file_name) = &part.metadata().file_name {
101                 form.append_with_blob_and_filename(name, &blob, &file_name)
102             } else {
103                 form.append_with_blob(name, &blob)
104             }
105             .map_err(crate::error::wasm)
106             .map_err(crate::error::builder)?;
107         }
108         Ok(form)
109     }
110 }
111 
112 impl fmt::Debug for Form {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result113     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114         self.inner.fmt_fields("Form", f)
115     }
116 }
117 
118 // ===== impl Part =====
119 
120 impl Part {
121     /// Makes a text parameter.
text<T>(value: T) -> Part where T: Into<Cow<'static, str>>,122     pub fn text<T>(value: T) -> Part
123     where
124         T: Into<Cow<'static, str>>,
125     {
126         let body = match value.into() {
127             Cow::Borrowed(slice) => Body::from(slice),
128             Cow::Owned(string) => Body::from(string),
129         };
130         Part::new(body)
131     }
132 
133     /// Makes a new parameter from arbitrary bytes.
bytes<T>(value: T) -> Part where T: Into<Cow<'static, [u8]>>,134     pub fn bytes<T>(value: T) -> Part
135     where
136         T: Into<Cow<'static, [u8]>>,
137     {
138         let body = match value.into() {
139             Cow::Borrowed(slice) => Body::from(slice),
140             Cow::Owned(vec) => Body::from(vec),
141         };
142         Part::new(body)
143     }
144 
145     /// Makes a new parameter from an arbitrary stream.
stream<T: Into<Body>>(value: T) -> Part146     pub fn stream<T: Into<Body>>(value: T) -> Part {
147         Part::new(value.into())
148     }
149 
new(value: Body) -> Part150     fn new(value: Body) -> Part {
151         Part {
152             meta: PartMetadata::new(),
153             value,
154         }
155     }
156 
157     /// Tries to set the mime of this part.
mime_str(self, mime: &str) -> crate::Result<Part>158     pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
159         Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
160     }
161 
162     // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
mime(self, mime: Mime) -> Part163     fn mime(self, mime: Mime) -> Part {
164         self.with_inner(move |inner| inner.mime(mime))
165     }
166 
167     /// Sets the filename, builder style.
file_name<T>(self, filename: T) -> Part where T: Into<Cow<'static, str>>,168     pub fn file_name<T>(self, filename: T) -> Part
169     where
170         T: Into<Cow<'static, str>>,
171     {
172         self.with_inner(move |inner| inner.file_name(filename))
173     }
174 
with_inner<F>(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata,175     fn with_inner<F>(self, func: F) -> Self
176     where
177         F: FnOnce(PartMetadata) -> PartMetadata,
178     {
179         Part {
180             meta: func(self.meta),
181             value: self.value,
182         }
183     }
184 
blob(&self) -> crate::Result<web_sys::Blob>185     fn blob(&self) -> crate::Result<web_sys::Blob> {
186         use web_sys::Blob;
187         use web_sys::BlobPropertyBag;
188         let mut properties = BlobPropertyBag::new();
189         if let Some(mime) = &self.meta.mime {
190             properties.type_(mime.as_ref());
191         }
192 
193         // BUG: the return value of to_js_value() is not valid if
194         // it is a Multipart variant.
195         let js_value = self.value.to_js_value()?;
196         Blob::new_with_u8_array_sequence_and_options(&js_value, &properties)
197             .map_err(crate::error::wasm)
198             .map_err(crate::error::builder)
199     }
200 }
201 
202 impl fmt::Debug for Part {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result203     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204         let mut dbg = f.debug_struct("Part");
205         dbg.field("value", &self.value);
206         self.meta.fmt_fields(&mut dbg);
207         dbg.finish()
208     }
209 }
210 
211 impl PartProps for Part {
metadata(&self) -> &PartMetadata212     fn metadata(&self) -> &PartMetadata {
213         &self.meta
214     }
215 }
216 
217 // ===== impl FormParts =====
218 
219 impl<P: PartProps> FormParts<P> {
new() -> Self220     pub(crate) fn new() -> Self {
221         FormParts { fields: Vec::new() }
222     }
223 
224     /// Adds a customized Part.
part<T>(mut self, name: T, part: P) -> Self where T: Into<Cow<'static, str>>,225     pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
226     where
227         T: Into<Cow<'static, str>>,
228     {
229         self.fields.push((name.into(), part));
230         self
231     }
232 }
233 
234 impl<P: fmt::Debug> FormParts<P> {
fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result235     pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
236         f.debug_struct(ty_name)
237             .field("parts", &self.fields)
238             .finish()
239     }
240 }
241 
242 // ===== impl PartMetadata =====
243 
244 impl PartMetadata {
new() -> Self245     pub(crate) fn new() -> Self {
246         PartMetadata {
247             mime: None,
248             file_name: None,
249             headers: HeaderMap::default(),
250         }
251     }
252 
mime(mut self, mime: Mime) -> Self253     pub(crate) fn mime(mut self, mime: Mime) -> Self {
254         self.mime = Some(mime);
255         self
256     }
257 
file_name<T>(mut self, filename: T) -> Self where T: Into<Cow<'static, str>>,258     pub(crate) fn file_name<T>(mut self, filename: T) -> Self
259     where
260         T: Into<Cow<'static, str>>,
261     {
262         self.file_name = Some(filename.into());
263         self
264     }
265 }
266 
267 impl PartMetadata {
fmt_fields<'f, 'fa, 'fb>( &self, debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, ) -> &'f mut fmt::DebugStruct<'fa, 'fb>268     pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
269         &self,
270         debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
271     ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
272         debug_struct
273             .field("mime", &self.mime)
274             .field("file_name", &self.file_name)
275             .field("headers", &self.headers)
276     }
277 }
278 
279 #[cfg(test)]
280 mod tests {}
281