1use 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
39pub struct Multipart<S> {
45 writer: MultipartWriter<'static, S>,
46}
47
48impl Multipart<()> {
49 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 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 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 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 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 pub fn from_request_sized(req: R) -> Result<Self, R::Error> {
149 Multipart::from_request(SizedRequest::from_request(req))
150 }
151}
152
153pub trait HttpRequest {
155 type Stream: HttpStream;
158 type Error: From<io::Error> + Into<<Self::Stream as HttpStream>::Error>;
161
162 fn apply_headers(&mut self, boundary: &str, content_len: Option<u64>) -> bool;
168
169 fn open_stream(self) -> Result<Self::Stream, Self::Error>;
171}
172
173pub trait HttpStream: Write {
175 type Request: HttpRequest;
177 type Response;
179 type Error: From<io::Error> + From<<Self::Request as HttpRequest>::Error>;
182
183 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 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 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 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}