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 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#[derive(Debug, Default)]
128pub struct Multipart<'n, 'd> {
129 fields: Vec<Field<'n, 'd>>,
130}
131
132impl<'n, 'd> Multipart<'n, 'd> {
133 pub fn new() -> Self {
135 Default::default()
136 }
137
138 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 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 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 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 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
251pub 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 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 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 pub fn content_len(&self) -> Option<u64> {
328 self.content_len
329 }
330
331 pub fn boundary(&self) -> &str {
333 let boundary = self.end_boundary.get_ref();
334
335 &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
444pub trait IntoCowPath<'a> {
447 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 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 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 #[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}