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}