1use 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
30pub type LazyIoError<'a> = LazyError<'a, io::Error>;
32
33pub type LazyIoResult<'a, T> = Result<T, LazyIoError<'a>>;
35
36pub struct LazyError<'a, E> {
39 pub field_name: Option<Cow<'a, str>>,
42 pub error: E,
44 _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
74impl<'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#[derive(Debug, Default)]
124pub struct Multipart<'n, 'd> {
125 fields: Vec<Field<'n, 'd>>,
126}
127
128impl<'n, 'd> Multipart<'n, 'd> {
129 pub fn new() -> Self {
131 Default::default()
132 }
133
134 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 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 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 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 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
247pub 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 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 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 pub fn content_len(&self) -> Option<u64> {
324 self.content_len
325 }
326
327 pub fn boundary(&self) -> &str {
329 let boundary = self.end_boundary.get_ref();
330
331 &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
440pub trait IntoCowPath<'a> {
443 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 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 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 #[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}