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