multipart/client/
lazy.rs

1//! Multipart requests which write out their data in one fell swoop.
2use mime::Mime;
3
4use std::borrow::Cow;
5use std::error::Error;
6use std::fs::File;
7use std::path::{Path, PathBuf};
8
9use std::io::prelude::*;
10use std::io::Cursor;
11use std::{fmt, io};
12
13use super::{HttpRequest, HttpStream};
14
15macro_rules! try_lazy (
16    ($field:expr, $try:expr) => (
17        match $try {
18            Ok(ok) => ok,
19            Err(e) => return Err(LazyError::with_field($field.into(), e)),
20        }
21    );
22    ($try:expr) => (
23        match $try {
24            Ok(ok) => ok,
25            Err(e) => return Err(LazyError::without_field(e)),
26        }
27    )
28);
29
30/// A `LazyError` wrapping `std::io::Error`.
31pub type LazyIoError<'a> = LazyError<'a, io::Error>;
32
33/// `Result` type for `LazyIoError`.
34pub type LazyIoResult<'a, T> = Result<T, LazyIoError<'a>>;
35
36/// An error for lazily written multipart requests, including the original error as well
37/// as the field which caused the error, if applicable.
38pub struct LazyError<'a, E> {
39    /// The field that caused the error.
40    /// If `None`, there was a problem opening the stream to write or finalizing the stream.
41    pub field_name: Option<Cow<'a, str>>,
42    /// The inner error.
43    pub error: E,
44    /// Private field for back-compat.
45    _priv: (),
46}
47
48impl<'a, E> LazyError<'a, E> {
49    fn without_field<E_: Into<E>>(error: E_) -> Self {
50        LazyError {
51            field_name: None,
52            error: error.into(),
53            _priv: (),
54        }
55    }
56
57    fn with_field<E_: Into<E>>(field_name: Cow<'a, str>, error: E_) -> Self {
58        LazyError {
59            field_name: Some(field_name),
60            error: error.into(),
61            _priv: (),
62        }
63    }
64
65    fn transform_err<E_: From<E>>(self) -> LazyError<'a, E_> {
66        LazyError {
67            field_name: self.field_name,
68            error: self.error.into(),
69            _priv: (),
70        }
71    }
72}
73
74/// Take `self.error`, discarding `self.field_name`.
75impl<'a> Into<io::Error> for LazyError<'a, io::Error> {
76    fn into(self) -> io::Error {
77        self.error
78    }
79}
80
81impl<'a, E: Error> Error for LazyError<'a, E> {
82    fn description(&self) -> &str {
83        self.error.description()
84    }
85
86    fn cause(&self) -> Option<&dyn Error> {
87        Some(&self.error)
88    }
89}
90
91impl<'a, E: fmt::Debug> fmt::Debug for LazyError<'a, E> {
92    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
93        if let Some(ref field_name) = self.field_name {
94            fmt.write_fmt(format_args!(
95                "LazyError (on field {:?}): {:?}",
96                field_name, self.error
97            ))
98        } else {
99            fmt.write_fmt(format_args!("LazyError (misc): {:?}", self.error))
100        }
101    }
102}
103
104impl<'a, E: fmt::Display> fmt::Display for LazyError<'a, E> {
105    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
106        if let Some(ref field_name) = self.field_name {
107            fmt.write_fmt(format_args!(
108                "Error writing field {:?}: {}",
109                field_name, self.error
110            ))
111        } else {
112            fmt.write_fmt(format_args!(
113                "Error opening or flushing stream: {}",
114                self.error
115            ))
116        }
117    }
118}
119
120/// A multipart request which writes all fields at once upon being provided an output stream.
121///
122/// Sacrifices static dispatch for support for dynamic construction. Reusable.
123///
124/// #### Lifetimes
125/// * `'n`: Lifetime for field **n**ames; will only escape this struct in `LazyIoError<'n>`.
126/// * `'d`: Lifetime for **d**ata: will only escape this struct in `PreparedFields<'d>`.
127#[derive(Debug, Default)]
128pub struct Multipart<'n, 'd> {
129    fields: Vec<Field<'n, 'd>>,
130}
131
132impl<'n, 'd> Multipart<'n, 'd> {
133    /// Initialize a new lazy dynamic request.
134    pub fn new() -> Self {
135        Default::default()
136    }
137
138    /// Add a text field to this request.
139    pub fn add_text<N, T>(&mut self, name: N, text: T) -> &mut Self
140    where
141        N: Into<Cow<'n, str>>,
142        T: Into<Cow<'d, str>>,
143    {
144        self.fields.push(Field {
145            name: name.into(),
146            data: Data::Text(text.into()),
147        });
148
149        self
150    }
151
152    /// Add a file field to this request.
153    ///
154    /// ### Note
155    /// Does not check if `path` exists.
156    pub fn add_file<N, P>(&mut self, name: N, path: P) -> &mut Self
157    where
158        N: Into<Cow<'n, str>>,
159        P: IntoCowPath<'d>,
160    {
161        self.fields.push(Field {
162            name: name.into(),
163            data: Data::File(path.into_cow_path()),
164        });
165
166        self
167    }
168
169    /// Add a generic stream field to this request,
170    pub fn add_stream<N, R, F>(
171        &mut self,
172        name: N,
173        stream: R,
174        filename: Option<F>,
175        mime: Option<Mime>,
176    ) -> &mut Self
177    where
178        N: Into<Cow<'n, str>>,
179        R: Read + 'd,
180        F: Into<Cow<'n, str>>,
181    {
182        self.fields.push(Field {
183            name: name.into(),
184            data: Data::Stream(Stream {
185                content_type: mime.unwrap_or(mime::APPLICATION_OCTET_STREAM),
186                filename: filename.map(|f| f.into()),
187                stream: Box::new(stream),
188            }),
189        });
190
191        self
192    }
193
194    /// Convert `req` to `HttpStream`, write out the fields in this request, and finish the
195    /// request, returning the response if successful, or the first error encountered.
196    ///
197    /// If any files were added by path they will now be opened for reading.
198    pub fn send<R: HttpRequest>(
199        &mut self,
200        mut req: R,
201    ) -> Result<<R::Stream as HttpStream>::Response, LazyError<'n, <R::Stream as HttpStream>::Error>>
202    {
203        let mut prepared = self.prepare().map_err(LazyError::transform_err)?;
204
205        req.apply_headers(prepared.boundary(), prepared.content_len());
206
207        let mut stream = try_lazy!(req.open_stream());
208
209        try_lazy!(io::copy(&mut prepared, &mut stream));
210
211        stream.finish().map_err(LazyError::without_field)
212    }
213
214    /// Export the multipart data contained in this lazy request as an adaptor which implements `Read`.
215    ///
216    /// During this step, if any files were added by path then they will be opened for reading
217    /// and their length measured.
218    pub fn prepare(&mut self) -> LazyIoResult<'n, PreparedFields<'d>> {
219        PreparedFields::from_fields(&mut self.fields)
220    }
221}
222
223#[derive(Debug)]
224struct Field<'n, 'd> {
225    name: Cow<'n, str>,
226    data: Data<'n, 'd>,
227}
228
229enum Data<'n, 'd> {
230    Text(Cow<'d, str>),
231    File(Cow<'d, Path>),
232    Stream(Stream<'n, 'd>),
233}
234
235impl<'n, 'd> fmt::Debug for Data<'n, 'd> {
236    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
237        match *self {
238            Data::Text(ref text) => write!(f, "Data::Text({:?})", text),
239            Data::File(ref path) => write!(f, "Data::File({:?})", path),
240            Data::Stream(_) => f.write_str("Data::Stream(Box<Read>)"),
241        }
242    }
243}
244
245struct Stream<'n, 'd> {
246    filename: Option<Cow<'n, str>>,
247    content_type: Mime,
248    stream: Box<dyn Read + 'd>,
249}
250
251/// The result of [`Multipart::prepare()`](struct.Multipart.html#method.prepare).
252///
253/// Implements `Read`, contains the entire request body.
254///
255/// Individual files/streams are dropped as they are read to completion.
256///
257/// ### Note
258/// The fields in the request may have been reordered to simplify the preparation step.
259/// No compliant server implementation will be relying on the specific ordering of fields anyways.
260pub struct PreparedFields<'d> {
261    text_data: Cursor<Vec<u8>>,
262    streams: Vec<PreparedField<'d>>,
263    end_boundary: Cursor<String>,
264    content_len: Option<u64>,
265}
266
267impl<'d> PreparedFields<'d> {
268    fn from_fields<'n>(fields: &mut Vec<Field<'n, 'd>>) -> Result<Self, LazyIoError<'n>> {
269        debug!("Field count: {}", fields.len());
270
271        // One of the two RFCs specifies that any bytes before the first boundary are to be
272        // ignored anyway
273        let mut boundary = format!("\r\n--{}", super::gen_boundary());
274
275        let mut text_data = Vec::new();
276        let mut streams = Vec::new();
277        let mut content_len = 0u64;
278        let mut use_len = true;
279
280        for field in fields.drain(..) {
281            match field.data {
282                Data::Text(text) => write!(
283                    text_data,
284                    "{}\r\nContent-Disposition: form-data; \
285                     name=\"{}\"\r\n\r\n{}",
286                    boundary, field.name, text
287                )
288                .unwrap(),
289                Data::File(file) => {
290                    let (stream, len) = PreparedField::from_path(field.name, &file, &boundary)?;
291                    content_len += len;
292                    streams.push(stream);
293                }
294                Data::Stream(stream) => {
295                    use_len = false;
296
297                    streams.push(PreparedField::from_stream(
298                        &field.name,
299                        &boundary,
300                        &stream.content_type,
301                        stream.filename.as_ref().map(|f| &**f),
302                        stream.stream,
303                    ));
304                }
305            }
306        }
307
308        // So we don't write a spurious end boundary
309        if text_data.is_empty() && streams.is_empty() {
310            boundary = String::new();
311        } else {
312            boundary.push_str("--");
313        }
314
315        content_len += boundary.len() as u64;
316
317        Ok(PreparedFields {
318            text_data: Cursor::new(text_data),
319            streams,
320            end_boundary: Cursor::new(boundary),
321            content_len: if use_len { Some(content_len) } else { None },
322        })
323    }
324
325    /// Get the content-length value for this set of fields, if applicable (all fields are sized,
326    /// i.e. not generic streams).
327    pub fn content_len(&self) -> Option<u64> {
328        self.content_len
329    }
330
331    /// Get the boundary that was used to serialize the request.
332    pub fn boundary(&self) -> &str {
333        let boundary = self.end_boundary.get_ref();
334
335        // Get just the bare boundary string
336        &boundary[4..boundary.len() - 2]
337    }
338}
339
340impl<'d> Read for PreparedFields<'d> {
341    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
342        if buf.is_empty() {
343            debug!("PreparedFields::read() was passed a zero-sized buffer.");
344            return Ok(0);
345        }
346
347        let mut total_read = 0;
348
349        while total_read < buf.len() && !cursor_at_end(&self.end_boundary) {
350            let buf = &mut buf[total_read..];
351
352            total_read += if !cursor_at_end(&self.text_data) {
353                self.text_data.read(buf)?
354            } else if let Some(mut field) = self.streams.pop() {
355                match field.read(buf) {
356                    Ok(0) => continue,
357                    res => {
358                        self.streams.push(field);
359                        res
360                    }
361                }?
362            } else {
363                self.end_boundary.read(buf)?
364            };
365        }
366
367        Ok(total_read)
368    }
369}
370
371struct PreparedField<'d> {
372    header: Cursor<Vec<u8>>,
373    stream: Box<dyn Read + 'd>,
374}
375
376impl<'d> PreparedField<'d> {
377    fn from_path<'n>(
378        name: Cow<'n, str>,
379        path: &Path,
380        boundary: &str,
381    ) -> Result<(Self, u64), LazyIoError<'n>> {
382        let (content_type, filename) = super::mime_filename(&path);
383
384        let file = try_lazy!(name, File::open(path));
385        let content_len = try_lazy!(name, file.metadata()).len();
386
387        let stream = Self::from_stream(&name, boundary, &content_type, filename, Box::new(file));
388
389        let content_len = content_len + (stream.header.get_ref().len() as u64);
390
391        Ok((stream, content_len))
392    }
393
394    fn from_stream(
395        name: &str,
396        boundary: &str,
397        content_type: &Mime,
398        filename: Option<&str>,
399        stream: Box<dyn Read + 'd>,
400    ) -> Self {
401        let mut header = Vec::new();
402
403        write!(
404            header,
405            "{}\r\nContent-Disposition: form-data; name=\"{}\"",
406            boundary, name
407        )
408        .unwrap();
409
410        if let Some(filename) = filename {
411            write!(header, "; filename=\"{}\"", filename).unwrap();
412        }
413
414        write!(header, "\r\nContent-Type: {}\r\n\r\n", content_type).unwrap();
415
416        PreparedField {
417            header: Cursor::new(header),
418            stream,
419        }
420    }
421}
422
423impl<'d> Read for PreparedField<'d> {
424    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
425        debug!("PreparedField::read()");
426
427        if !cursor_at_end(&self.header) {
428            self.header.read(buf)
429        } else {
430            self.stream.read(buf)
431        }
432    }
433}
434
435impl<'d> fmt::Debug for PreparedField<'d> {
436    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437        f.debug_struct("PreparedField")
438            .field("header", &self.header)
439            .field("stream", &"Box<Read>")
440            .finish()
441    }
442}
443
444/// Conversion trait necessary for `Multipart::add_file()` to accept borrowed or owned strings
445/// and borrowed or owned paths
446pub trait IntoCowPath<'a> {
447    /// Self-explanatory, hopefully
448    fn into_cow_path(self) -> Cow<'a, Path>;
449}
450
451impl<'a> IntoCowPath<'a> for Cow<'a, Path> {
452    fn into_cow_path(self) -> Cow<'a, Path> {
453        self
454    }
455}
456
457impl IntoCowPath<'static> for PathBuf {
458    fn into_cow_path(self) -> Cow<'static, Path> {
459        self.into()
460    }
461}
462
463impl<'a> IntoCowPath<'a> for &'a Path {
464    fn into_cow_path(self) -> Cow<'a, Path> {
465        self.into()
466    }
467}
468
469impl IntoCowPath<'static> for String {
470    fn into_cow_path(self) -> Cow<'static, Path> {
471        PathBuf::from(self).into()
472    }
473}
474
475impl<'a> IntoCowPath<'a> for &'a str {
476    fn into_cow_path(self) -> Cow<'a, Path> {
477        Path::new(self).into()
478    }
479}
480
481fn cursor_at_end<T: AsRef<[u8]>>(cursor: &Cursor<T>) -> bool {
482    cursor.position() == (cursor.get_ref().as_ref().len() as u64)
483}
484
485#[cfg(feature = "hyper")]
486mod hyper {
487    use hyper::client::{Body, Client, IntoUrl, RequestBuilder, Response};
488    use hyper::Result as HyperResult;
489
490    impl<'n, 'd> super::Multipart<'n, 'd> {
491        /// #### Feature: `hyper`
492        /// Complete a POST request with the given `hyper::client::Client` and URL.
493        ///
494        /// Supplies the fields in the body, optionally setting the content-length header if
495        /// applicable (all added fields were text or files, i.e. no streams).
496        pub fn client_request<U: IntoUrl>(
497            &mut self,
498            client: &Client,
499            url: U,
500        ) -> HyperResult<Response> {
501            self.client_request_mut(client, url, |r| r)
502        }
503
504        /// #### Feature: `hyper`
505        /// Complete a POST request with the given `hyper::client::Client` and URL;
506        /// allows mutating the `hyper::client::RequestBuilder` via the passed closure.
507        ///
508        /// Note that the body, and the `ContentType` and `ContentLength` headers will be
509        /// overwritten, either by this method or by Hyper.
510        pub fn client_request_mut<U: IntoUrl, F: FnOnce(RequestBuilder) -> RequestBuilder>(
511            &mut self,
512            client: &Client,
513            url: U,
514            mut_fn: F,
515        ) -> HyperResult<Response> {
516            let mut fields = match self.prepare() {
517                Ok(fields) => fields,
518                Err(err) => {
519                    error!("Error preparing request: {}", err);
520                    return Err(err.error.into());
521                }
522            };
523
524            mut_fn(client.post(url))
525                .header(::client::hyper::content_type(fields.boundary()))
526                .body(fields.to_body())
527                .send()
528        }
529    }
530
531    impl<'d> super::PreparedFields<'d> {
532        /// #### Feature: `hyper`
533        /// Convert `self` to `hyper::client::Body`.
534        #[cfg_attr(feature = "clippy", warn(wrong_self_convention))]
535        pub fn to_body<'b>(&'b mut self) -> Body<'b>
536        where
537            'd: 'b,
538        {
539            if let Some(content_len) = self.content_len {
540                Body::SizedBody(self, content_len)
541            } else {
542                Body::ChunkedBody(self)
543            }
544        }
545    }
546}