qiniu_multipart/client/
mod.rs

1// Copyright 2016 `multipart` Crate Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7//! The client-side abstraction for multipart requests. Enabled with the `client` feature.
8//!
9//! Use this when sending POST requests with files to a server.
10use mime::Mime;
11
12use std::borrow::Cow;
13use std::fs::File;
14use std::io;
15use std::io::prelude::*;
16
17use std::path::Path;
18
19#[cfg(feature = "hyper")]
20pub mod hyper;
21
22pub mod lazy;
23
24mod sized;
25
26pub use self::sized::SizedRequest;
27
28const BOUNDARY_LEN: usize = 16;
29
30macro_rules! map_self {
31    ($selff:expr, $try:expr) => {
32        match $try {
33            Ok(_) => Ok($selff),
34            Err(err) => Err(err.into()),
35        }
36    };
37}
38
39/// The entry point of the client-side multipart API.
40///
41/// Though they perform I/O, the `.write_*()` methods do not return `io::Result<_>` in order to
42/// facilitate method chaining. Upon the first error, all subsequent API calls will be no-ops until
43/// `.send()` is called, at which point the error will be reported.
44pub struct Multipart<S> {
45    writer: MultipartWriter<'static, S>,
46}
47
48impl Multipart<()> {
49    /// Create a new `Multipart` to wrap a request.
50    ///
51    /// ## Returns Error
52    /// If `req.open_stream()` returns an error.
53    pub fn from_request<R: HttpRequest>(req: R) -> Result<Multipart<R::Stream>, R::Error> {
54        let (boundary, stream) = open_stream(req, None)?;
55
56        Ok(Multipart {
57            writer: MultipartWriter::new(stream, boundary),
58        })
59    }
60}
61
62impl<S: HttpStream> Multipart<S> {
63    /// Write a text field to this multipart request.
64    /// `name` and `val` can be either owned `String` or `&str`.
65    ///
66    /// ## Errors
67    /// If something went wrong with the HTTP stream.
68    pub fn write_text<N: AsRef<str>, V: AsRef<str>>(
69        &mut self,
70        name: N,
71        val: V,
72    ) -> Result<&mut Self, S::Error> {
73        map_self!(self, self.writer.write_text(name.as_ref(), val.as_ref()))
74    }
75
76    /// Open a file pointed to by `path` and write its contents to the multipart request,
77    /// supplying its filename and guessing its `Content-Type` from its extension.
78    ///
79    /// If you want to set these values manually, or use another type that implements `Read`,
80    /// use `.write_stream()`.
81    ///
82    /// `name` can be either `String` or `&str`, and `path` can be `PathBuf` or `&Path`.
83    ///
84    /// ## Errors
85    /// If there was a problem opening the file (was a directory or didn't exist),
86    /// or if something went wrong with the HTTP stream.
87    pub fn write_file<N: AsRef<str>, P: AsRef<Path>>(
88        &mut self,
89        name: N,
90        path: P,
91    ) -> Result<&mut Self, S::Error> {
92        let name = name.as_ref();
93        let path = path.as_ref();
94
95        map_self!(self, self.writer.write_file(name, path))
96    }
97
98    /// Write a byte stream to the multipart request as a file field, supplying `filename` if given,
99    /// and `content_type` if given or `"application/octet-stream"` if not.
100    ///
101    /// `name` can be either `String` or `&str`, and `read` can take the `Read` by-value or
102    /// with an `&mut` borrow.
103    ///
104    /// ## Warning
105    /// The given `Read` **must** be able to read to EOF (end of file/no more data), meaning
106    /// `Read::read()` returns `Ok(0)`. If it never returns EOF it will be read to infinity
107    /// and the request will never be completed.
108    ///
109    /// When using `SizedRequest` this also can cause out-of-control memory usage as the
110    /// multipart data has to be written to an in-memory buffer so its size can be calculated.
111    ///
112    /// Use `Read::take()` if you wish to send data from a `Read`
113    /// that will never return EOF otherwise.
114    ///
115    /// ## Errors
116    /// If the reader returned an error, or if something went wrong with the HTTP stream.
117    // RFC: How to format this declaration?
118    pub fn write_stream<N: AsRef<str>, St: Read>(
119        &mut self,
120        name: N,
121        stream: &mut St,
122        filename: Option<&str>,
123        content_type: Option<Mime>,
124    ) -> Result<&mut Self, S::Error> {
125        let name = name.as_ref();
126
127        map_self!(
128            self,
129            self.writer
130                .write_stream(stream, name, filename, content_type)
131        )
132    }
133
134    /// Finalize the request and return the response from the server, or the last error if set.
135    pub fn send(self) -> Result<S::Response, S::Error> {
136        self.writer
137            .finish()
138            .map_err(io::Error::into)
139            .and_then(|body| body.finish())
140    }
141}
142
143impl<R: HttpRequest> Multipart<SizedRequest<R>>
144where
145    <R::Stream as HttpStream>::Error: From<R::Error>,
146{
147    /// Create a new `Multipart` using the `SizedRequest` wrapper around `req`.
148    pub fn from_request_sized(req: R) -> Result<Self, R::Error> {
149        Multipart::from_request(SizedRequest::from_request(req))
150    }
151}
152
153/// A trait describing an HTTP request that can be used to send multipart data.
154pub trait HttpRequest {
155    /// The HTTP stream type that can be opend by this request, to which the multipart data will be
156    /// written.
157    type Stream: HttpStream;
158    /// The error type for this request.
159    /// Must be compatible with `io::Error` as well as `Self::HttpStream::Error`
160    type Error: From<io::Error> + Into<<Self::Stream as HttpStream>::Error>;
161
162    /// Set the `Content-Type` header to `multipart/form-data` and supply the `boundary` value.
163    /// If `content_len` is given, set the `Content-Length` header to its value.
164    ///
165    /// Return `true` if any and all sanity checks passed and the stream is ready to be opened,
166    /// or `false` otherwise.
167    fn apply_headers(&mut self, boundary: &str, content_len: Option<u64>) -> bool;
168
169    /// Open the request stream and return it or any error otherwise.
170    fn open_stream(self) -> Result<Self::Stream, Self::Error>;
171}
172
173/// A trait describing an open HTTP stream that can be written to.
174pub trait HttpStream: Write {
175    /// The request type that opened this stream.
176    type Request: HttpRequest;
177    /// The response type that will be returned after the request is completed.
178    type Response;
179    /// The error type for this stream.
180    /// Must be compatible with `io::Error` as well as `Self::Request::Error`.
181    type Error: From<io::Error> + From<<Self::Request as HttpRequest>::Error>;
182
183    /// Finalize and close the stream and return the response object, or any error otherwise.
184    fn finish(self) -> Result<Self::Response, Self::Error>;
185}
186
187impl HttpRequest for () {
188    type Stream = io::Sink;
189    type Error = io::Error;
190
191    fn apply_headers(&mut self, _: &str, _: Option<u64>) -> bool {
192        true
193    }
194    fn open_stream(self) -> Result<Self::Stream, Self::Error> {
195        Ok(io::sink())
196    }
197}
198
199impl HttpStream for io::Sink {
200    type Request = ();
201    type Response = ();
202    type Error = io::Error;
203
204    fn finish(self) -> Result<Self::Response, Self::Error> {
205        Ok(())
206    }
207}
208
209fn gen_boundary() -> String {
210    ::random_alphanumeric(BOUNDARY_LEN)
211}
212
213fn open_stream<R: HttpRequest>(
214    mut req: R,
215    content_len: Option<u64>,
216) -> Result<(String, R::Stream), R::Error> {
217    let boundary = gen_boundary();
218    req.apply_headers(&boundary, content_len);
219    req.open_stream().map(|stream| (boundary, stream))
220}
221
222struct MultipartWriter<'a, W> {
223    inner: W,
224    boundary: Cow<'a, str>,
225    data_written: bool,
226}
227
228impl<'a, W: Write> MultipartWriter<'a, W> {
229    fn new<B: Into<Cow<'a, str>>>(inner: W, boundary: B) -> Self {
230        MultipartWriter {
231            inner,
232            boundary: boundary.into(),
233            data_written: false,
234        }
235    }
236
237    fn write_boundary(&mut self) -> io::Result<()> {
238        if self.data_written {
239            self.inner.write_all(b"\r\n")?;
240        }
241
242        write!(self.inner, "--{}\r\n", self.boundary)
243    }
244
245    fn write_text(&mut self, name: &str, text: &str) -> io::Result<()> {
246        chain_result! {
247            self.write_field_headers(name, None, None),
248            self.inner.write_all(text.as_bytes())
249        }
250    }
251
252    fn write_file(&mut self, name: &str, path: &Path) -> io::Result<()> {
253        let (content_type, filename) = mime_filename(path);
254        let mut file = File::open(path)?;
255        self.write_stream(&mut file, name, filename, Some(content_type))
256    }
257
258    fn write_stream<S: Read>(
259        &mut self,
260        stream: &mut S,
261        name: &str,
262        filename: Option<&str>,
263        content_type: Option<Mime>,
264    ) -> io::Result<()> {
265        // This is necessary to make sure it is interpreted as a file on the server end.
266        let content_type = Some(content_type.unwrap_or(mime::APPLICATION_OCTET_STREAM));
267
268        chain_result! {
269            self.write_field_headers(name, filename, content_type),
270            io::copy(stream, &mut self.inner),
271            Ok(())
272        }
273    }
274
275    fn write_field_headers(
276        &mut self,
277        name: &str,
278        filename: Option<&str>,
279        content_type: Option<Mime>,
280    ) -> io::Result<()> {
281        chain_result! {
282            // Write the first boundary, or the boundary for the previous field.
283            self.write_boundary(),
284            { self.data_written = true; Ok(()) },
285            write!(self.inner, "Content-Disposition: form-data; name=\"{}\"", name),
286            filename.map(|filename| write!(self.inner, "; filename=\"{}\"", filename))
287                .unwrap_or(Ok(())),
288            content_type.map(|content_type| write!(self.inner, "\r\nContent-Type: {}", content_type))
289                .unwrap_or(Ok(())),
290            self.inner.write_all(b"\r\n\r\n")
291        }
292    }
293
294    fn finish(mut self) -> io::Result<W> {
295        if self.data_written {
296            self.inner.write_all(b"\r\n")?;
297        }
298
299        // always write the closing boundary, even for empty bodies
300        // trailing CRLF is optional but Actix requires it due to a naive implementation:
301        // https://github.com/actix/actix-web/issues/598
302        write!(self.inner, "--{}--\r\n", self.boundary)?;
303        Ok(self.inner)
304    }
305}
306
307fn mime_filename(path: &Path) -> (Mime, Option<&str>) {
308    let content_type = ::mime_guess::from_path(path);
309    let filename = opt_filename(path);
310    (content_type.first_or_octet_stream(), filename)
311}
312
313fn opt_filename(path: &Path) -> Option<&str> {
314    path.file_name().and_then(|filename| filename.to_str())
315}