multipart_rfc7578/form.rs
1// Copyright 2017 rust-hyper-multipart-rfc7578 Developers
2// Copyright 2018 rust-multipart-rfc7578 Developers
3//
4// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
5// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
6// http://opensource.org/licenses/MIT>, at your option. This file may not be
7// copied, modified, or distributed except according to those terms.
8//
9
10use crate::boundary_generator::{BoundaryGenerator, RandomAsciiGenerator};
11use crate::form_reader::FormReader;
12use crate::part::{Inner, Part};
13use crate::CRLF;
14use mime::Mime;
15use std::borrow::Borrow;
16use std::{
17 fmt::Display,
18 fs::File,
19 io::{self, Cursor, Read},
20 path::Path,
21 str::FromStr,
22};
23
24#[cfg(any(feature = "hyper", feature = "awc"))]
25use crate::body::Body;
26#[cfg(any(feature = "hyper", feature = "awc"))]
27use http::header::{CONTENT_LENGTH, CONTENT_TYPE};
28
29// use error::Error;
30
31/// Implements the multipart/form-data media type as described by
32/// RFC 7578.
33///
34/// [See](https://tools.ietf.org/html/rfc7578#section-1).
35///
36pub struct Form<'a> {
37 parts: Vec<Part<'a>>,
38
39 /// The auto-generated boundary as described by 4.1.
40 ///
41 /// [See](https://tools.ietf.org/html/rfc7578#section-4.1).
42 ///
43 boundary: String,
44}
45
46impl<'a> Default for Form<'a> {
47 /// Creates a new form with the default boundary generator.
48 ///
49 #[inline]
50 fn default() -> Self {
51 Form::new::<RandomAsciiGenerator>()
52 }
53}
54
55impl<'a> Form<'a> {
56 /// Creates a new form with the specified boundary generator function.
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// # use multipart_rfc7578::Form;
62 /// # use multipart_rfc7578::BoundaryGenerator;
63 /// #
64 /// struct TestGenerator;
65 ///
66 /// impl BoundaryGenerator for TestGenerator {
67 /// fn generate_boundary() -> String {
68 /// "test".to_string()
69 /// }
70 /// }
71 ///
72 /// let form = Form::new::<TestGenerator>();
73 /// ```
74 ///
75 #[inline]
76 pub fn new<G>() -> Self
77 where
78 G: BoundaryGenerator,
79 {
80 Self {
81 parts: vec![],
82 boundary: G::generate_boundary(),
83 }
84 }
85
86 /// Adds a text part to the Form.
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// use multipart_rfc7578::Form;
92 ///
93 /// let mut form = Form::default();
94 ///
95 /// form.add_text("text", "Hello World!");
96 /// form.add_text("more", String::from("Hello Universe!"));
97 /// ```
98 ///
99 pub fn add_text<N, T>(&mut self, name: N, text: T)
100 where
101 N: Display,
102 T: Into<String>,
103 {
104 self.parts.push(Part::new::<_, String>(
105 Inner::Text(text.into()),
106 name,
107 None,
108 None,
109 ))
110 }
111
112 /// Adds a readable part to the Form.
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// # extern crate mime;
118 /// # extern crate multipart_rfc7578;
119 /// #
120 /// use multipart_rfc7578::Form;
121 /// use std::io::Cursor;
122 ///
123 /// let string = "Hello World!";
124 /// let bytes = Cursor::new(string);
125 /// let mut form = Form::default();
126 ///
127 /// form.add_reader2("input", bytes, Some("filename.png"), Some(mime::TEXT_PLAIN), Some(string.len() as u64));
128 /// ```
129 pub fn add_reader2<F, G, R>(
130 &mut self,
131 name: F,
132 read: R,
133 filename: Option<G>,
134 mime: Option<Mime>,
135 length: Option<u64>,
136 ) where
137 F: Display,
138 G: Into<String>,
139 R: 'a + Read + Send,
140 {
141 let read = Box::new(read);
142
143 self.parts.push(Part::new::<_, String>(
144 Inner::Read(read, length),
145 name,
146 mime,
147 filename.map(Into::into),
148 ));
149 }
150
151 /// Adds a readable part to the Form.
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use multipart_rfc7578::Form;
157 /// use std::io::Cursor;
158 ///
159 /// let bytes = Cursor::new("Hello World!");
160 /// let mut form = Form::default();
161 ///
162 /// form.add_reader("input", bytes);
163 /// ```
164 #[inline]
165 pub fn add_reader<F, R>(&mut self, name: F, read: R)
166 where
167 F: Display,
168 R: 'a + Read + Send,
169 {
170 self.add_reader2(name, read, None::<&str>, None, None);
171 }
172
173 /// Adds a readable part to the Form as a file.
174 ///
175 /// # Examples
176 ///
177 /// ```
178 /// use multipart_rfc7578::Form;
179 /// use std::io::Cursor;
180 ///
181 /// let bytes = Cursor::new("Hello World!");
182 /// let mut form = Form::default();
183 ///
184 /// form.add_reader_file("input", bytes, "filename.txt");
185 /// ```
186 #[inline]
187 pub fn add_reader_file<F, G, R>(&mut self, name: F, read: R, filename: G)
188 where
189 F: Display,
190 G: Into<String>,
191 R: 'a + Read + Send,
192 {
193 self.add_reader2(name, read, Some(filename), None, None);
194 }
195
196 /// Adds a readable part to the Form as a file with a specified mime.
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// # extern crate mime;
202 /// # extern crate multipart_rfc7578;
203 /// #
204 /// use multipart_rfc7578::Form;
205 /// use std::io::Cursor;
206 ///
207 /// # fn main() {
208 /// let bytes = Cursor::new("Hello World!");
209 /// let mut form = Form::default();
210 ///
211 /// form.add_reader_file_with_mime("input", bytes, "filename.txt", mime::TEXT_PLAIN);
212 /// # }
213 /// ```
214 ///
215 #[inline]
216 pub fn add_reader_file_with_mime<F, G, R>(&mut self, name: F, read: R, filename: G, mime: Mime)
217 where
218 F: Display,
219 G: Into<String>,
220 R: 'a + Read + Send,
221 {
222 self.add_reader2(name, read, Some(filename), Some(mime), None);
223 }
224
225 /// Adds a file, and attempts to derive the mime type.
226 ///
227 /// # Examples
228 ///
229 /// ```
230 /// use multipart_rfc7578::Form;
231 ///
232 /// let mut form = Form::default();
233 ///
234 /// form.add_file("file", file!()).expect("file to exist");
235 /// ```
236 ///
237 #[inline]
238 pub fn add_file<P, F>(&mut self, name: F, path: P) -> io::Result<()>
239 where
240 P: AsRef<Path>,
241 F: Display,
242 {
243 self._add_file(name, path, None)
244 }
245
246 /// Adds a file with the specified mime type to the form.
247 /// If the mime type isn't specified, a mime type will try to
248 /// be derived.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// # extern crate mime;
254 /// # extern crate multipart_rfc7578;
255 /// #
256 /// use multipart_rfc7578::Form;
257 ///
258 /// # fn main() {
259 /// let mut form = Form::default();
260 ///
261 /// form.add_file_with_mime("data", "test.csv", mime::TEXT_CSV);
262 /// # }
263 /// ```
264 ///
265 #[inline]
266 pub fn add_file_with_mime<P, F>(&mut self, name: F, path: P, mime: Mime) -> io::Result<()>
267 where
268 P: AsRef<Path>,
269 F: Display,
270 {
271 self._add_file(name, path, Some(mime))
272 }
273
274 /// Internal method for adding a file part to the form.
275 ///
276 fn _add_file<P, F>(&mut self, name: F, path: P, mime: Option<Mime>) -> io::Result<()>
277 where
278 P: AsRef<Path>,
279 F: Display,
280 {
281 let f = File::open(&path)?;
282 let mime = match mime {
283 Some(mime) => Some(mime),
284 None => match path.as_ref().extension() {
285 Some(ext) => Mime::from_str(ext.to_string_lossy().borrow()).ok(),
286 None => None,
287 },
288 };
289 let len = match f.metadata() {
290 // If the path is not a file, it can't be uploaded because there
291 // is no content.
292 //
293 Ok(ref meta) if !meta.is_file() => Err(io::Error::new(
294 io::ErrorKind::InvalidInput,
295 "expected a file not directory",
296 )),
297
298 // If there is some metadata on the file, try to derive some
299 // header values.
300 //
301 Ok(ref meta) => Ok(Some(meta.len())),
302
303 // The file metadata could not be accessed. This MIGHT not be an
304 // error, if the file could be opened.
305 //
306 Err(e) => Err(e),
307 }?;
308
309 let read = Box::new(f);
310
311 self.parts.push(Part::new(
312 Inner::Read(read, len),
313 name,
314 mime,
315 Some(path.as_ref().as_os_str().to_string_lossy()),
316 ));
317
318 Ok(())
319 }
320
321 /// get boundary as content type string
322 #[inline]
323 pub fn content_type(&self) -> String {
324 format!("multipart/form-data; boundary=\"{}\"", &self.boundary)
325 }
326
327 #[inline]
328 fn boundary_string(&self) -> String {
329 format!("--{}{}", self.boundary, CRLF)
330 }
331
332 #[inline]
333 fn final_boundary_string(&self) -> String {
334 format!("--{}--{}", self.boundary, CRLF)
335 }
336
337 #[doc(hidden)]
338 pub fn into_reader(self) -> impl Read + 'a {
339 let boundary = Cursor::new(self.boundary_string());
340 let final_boundary = Cursor::new(self.final_boundary_string());
341 let readers = self
342 .parts
343 .into_iter()
344 .map(|part| part.into_reader())
345 .peekable();
346 FormReader::new(boundary, readers, final_boundary)
347 }
348
349 #[inline]
350 fn boundary_len(&self) -> u64 {
351 (self.boundary.len() + 4) as u64
352 }
353
354 /// get content length
355 pub fn content_length(&self) -> Option<u64> {
356 let boundary_len = self.boundary_len() + 2;
357 self.parts.iter().try_fold(boundary_len, |sum, part| {
358 part.content_length().map(|len| sum + len + boundary_len)
359 })
360 }
361}
362
363impl Form<'static> {
364 /// Just for documentation.
365 /// Updates a request instance with the multipart Content-Type header
366 /// and the payload data.
367 ///
368 /// # awc example
369 ///
370 /// ```
371 /// # #[cfg(feature = "awc")]
372 /// use awc::Client;
373 /// use multipart_rfc7578::{Form, SetBody};
374 ///
375 /// # #[cfg(feature = "awc")]
376 /// # fn main() {
377 /// let url = "http://localhost:80/upload";
378 /// let req = Client::default().post(url);
379 /// let mut form = Form::default();
380 ///
381 /// form.add_text("text", "Hello World!");
382 /// let req = form.set_body(req).unwrap();
383 /// # }
384 /// # #[cfg(not(feature = "awc"))]
385 /// # fn main() {
386 /// # }
387 /// ```
388 ///
389 /// # Hyper example
390 ///
391 /// ```
392 /// # #[cfg(feature = "hyper")]
393 /// use hyper::{Method, Request, Uri};
394 /// use multipart_rfc7578::{Form, SetBody};
395 ///
396 /// # #[cfg(feature = "hyper")]
397 /// # fn main() {
398 /// let url: Uri = "http://localhost:80/upload".parse().unwrap();
399 /// let mut req_builder = Request::post(url);
400 /// let mut form = Form::default();
401 ///
402 /// form.add_text("text", "Hello World!");
403 /// let req = form.set_body(&mut req_builder).unwrap();
404 /// # }
405 /// # #[cfg(not(feature = "hyper"))]
406 /// # fn main() {
407 /// # }
408 /// ```
409 #[cfg(all(not(feature = "awc"), not(feature = "hyper")))]
410 pub fn set_body(self) -> ! {
411 unimplemented!()
412 }
413 #[cfg(feature = "awc")]
414 pub fn set_body(
415 self,
416 req: awc::ClientRequest,
417 ) -> impl futures::Future<
418 Item = awc::ClientResponse<
419 impl futures::Stream<Item = bytes::Bytes, Error = awc::error::PayloadError>,
420 >,
421 Error = awc::error::SendRequestError,
422 > {
423 let req = req.set_header(CONTENT_TYPE, self.content_type());
424 let req = match self.content_length() {
425 Some(len) => req.set_header(CONTENT_LENGTH, len.to_string()),
426 _ => req,
427 };
428 req.send_stream(Body::from(self))
429 }
430 #[cfg(feature = "hyper")]
431 pub fn set_body(
432 self,
433 mut req: http::request::Builder,
434 ) -> Result<http::request::Request<hyper::Body>, http::Error> {
435 req.header(CONTENT_TYPE, self.content_type());
436 if let Some(len) = self.content_length() {
437 req.header(CONTENT_LENGTH, len.to_string());
438 }
439 req.body(hyper::Body::wrap_stream(Body::from(self)))
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::Form;
446 use std::io::{Cursor, Read};
447 #[test]
448 fn test_text_form() {
449 let mut form = Form::default();
450 form.add_text("hello", "world");
451 form.add_text("foo", "bar");
452 #[cfg(feature = "part-content-length")]
453 let content_length = "content-length: 5\r\n";
454 #[cfg(not(feature = "part-content-length"))]
455 let content_length = "";
456 #[cfg(feature = "part-content-length")]
457 let content_length1 = "content-length: 3\r\n";
458 #[cfg(not(feature = "part-content-length"))]
459 let content_length1 = "";
460 let test_string = format!(
461 "--{}\r
462content-disposition: form-data; name=\"hello\"\r
463content-type: text/plain\r
464{}\r
465world\r
466--{}\r
467content-disposition: form-data; name=\"foo\"\r
468content-type: text/plain\r
469{}\r
470bar\r
471--{}--\r
472",
473 form.boundary, content_length, form.boundary, content_length1, form.boundary
474 );
475 let mut form_string = String::with_capacity(test_string.len() + 1);
476 form.into_reader().read_to_string(&mut form_string).unwrap();
477 assert_eq!(test_string, form_string);
478 }
479
480 #[test]
481 fn test_form_reader() {
482 let mut form = Form::default();
483 form.add_reader("hello", Cursor::new("world"));
484 form.add_text("foo", "bar");
485 #[cfg(feature = "part-content-length")]
486 let content_length = "content-length: 3\r\n";
487 #[cfg(not(feature = "part-content-length"))]
488 let content_length = "";
489 let test_string = format!(
490 "--{}\r
491content-disposition: form-data; name=\"hello\"\r
492content-type: application/octet-stream\r
493\r
494world\r
495--{}\r
496content-disposition: form-data; name=\"foo\"\r
497content-type: text/plain\r
498{}\r
499bar\r
500--{}--\r
501",
502 form.boundary, form.boundary, content_length, form.boundary
503 );
504 let test_string = test_string.to_string();
505 let mut form_string = String::with_capacity(test_string.len() + 1);
506 form.into_reader().read_to_string(&mut form_string).unwrap();
507 assert_eq!(test_string, form_string);
508 }
509}