use mime::Mime;
use std::borrow::Cow;
use std::error::Error;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::io::prelude::*;
use std::io::Cursor;
use std::{fmt, io};
use super::{HttpRequest, HttpStream};
macro_rules! try_lazy (
($field:expr, $try:expr) => (
match $try {
Ok(ok) => ok,
Err(e) => return Err(LazyError::with_field($field.into(), e)),
}
);
($try:expr) => (
match $try {
Ok(ok) => ok,
Err(e) => return Err(LazyError::without_field(e)),
}
)
);
pub type LazyIoError<'a> = LazyError<'a, io::Error>;
pub type LazyIoResult<'a, T> = Result<T, LazyIoError<'a>>;
pub struct LazyError<'a, E> {
pub field_name: Option<Cow<'a, str>>,
pub error: E,
_priv: (),
}
impl<'a, E> LazyError<'a, E> {
fn without_field<E_: Into<E>>(error: E_) -> Self {
LazyError {
field_name: None,
error: error.into(),
_priv: (),
}
}
fn with_field<E_: Into<E>>(field_name: Cow<'a, str>, error: E_) -> Self {
LazyError {
field_name: Some(field_name),
error: error.into(),
_priv: (),
}
}
fn transform_err<E_: From<E>>(self) -> LazyError<'a, E_> {
LazyError {
field_name: self.field_name,
error: self.error.into(),
_priv: (),
}
}
}
impl<'a> Into<io::Error> for LazyError<'a, io::Error> {
fn into(self) -> io::Error {
self.error
}
}
impl<'a, E: Error> Error for LazyError<'a, E> {
fn description(&self) -> &str {
self.error.description()
}
fn cause(&self) -> Option<&dyn Error> {
Some(&self.error)
}
}
impl<'a, E: fmt::Debug> fmt::Debug for LazyError<'a, E> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref field_name) = self.field_name {
fmt.write_fmt(format_args!(
"LazyError (on field {:?}): {:?}",
field_name, self.error
))
} else {
fmt.write_fmt(format_args!("LazyError (misc): {:?}", self.error))
}
}
}
impl<'a, E: fmt::Display> fmt::Display for LazyError<'a, E> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref field_name) = self.field_name {
fmt.write_fmt(format_args!(
"Error writing field {:?}: {}",
field_name, self.error
))
} else {
fmt.write_fmt(format_args!(
"Error opening or flushing stream: {}",
self.error
))
}
}
}
#[derive(Debug, Default)]
pub struct Multipart<'n, 'd> {
fields: Vec<Field<'n, 'd>>,
}
impl<'n, 'd> Multipart<'n, 'd> {
pub fn new() -> Self {
Default::default()
}
pub fn add_text<N, T>(&mut self, name: N, text: T) -> &mut Self
where
N: Into<Cow<'n, str>>,
T: Into<Cow<'d, str>>,
{
self.fields.push(Field {
name: name.into(),
data: Data::Text(text.into()),
});
self
}
pub fn add_file<N, P>(&mut self, name: N, path: P) -> &mut Self
where
N: Into<Cow<'n, str>>,
P: IntoCowPath<'d>,
{
self.fields.push(Field {
name: name.into(),
data: Data::File(path.into_cow_path()),
});
self
}
pub fn add_stream<N, R, F>(
&mut self,
name: N,
stream: R,
filename: Option<F>,
mime: Option<Mime>,
) -> &mut Self
where
N: Into<Cow<'n, str>>,
R: Read + 'd,
F: Into<Cow<'n, str>>,
{
self.fields.push(Field {
name: name.into(),
data: Data::Stream(Stream {
content_type: mime.unwrap_or(mime::APPLICATION_OCTET_STREAM),
filename: filename.map(|f| f.into()),
stream: Box::new(stream),
}),
});
self
}
pub fn send<R: HttpRequest>(
&mut self,
mut req: R,
) -> Result<<R::Stream as HttpStream>::Response, LazyError<'n, <R::Stream as HttpStream>::Error>>
{
let mut prepared = self.prepare().map_err(LazyError::transform_err)?;
req.apply_headers(prepared.boundary(), prepared.content_len());
let mut stream = try_lazy!(req.open_stream());
try_lazy!(io::copy(&mut prepared, &mut stream));
stream.finish().map_err(LazyError::without_field)
}
pub fn prepare(&mut self) -> LazyIoResult<'n, PreparedFields<'d>> {
PreparedFields::from_fields(&mut self.fields)
}
}
#[derive(Debug)]
struct Field<'n, 'd> {
name: Cow<'n, str>,
data: Data<'n, 'd>,
}
enum Data<'n, 'd> {
Text(Cow<'d, str>),
File(Cow<'d, Path>),
Stream(Stream<'n, 'd>),
}
impl<'n, 'd> fmt::Debug for Data<'n, 'd> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Data::Text(ref text) => write!(f, "Data::Text({:?})", text),
Data::File(ref path) => write!(f, "Data::File({:?})", path),
Data::Stream(_) => f.write_str("Data::Stream(Box<Read>)"),
}
}
}
struct Stream<'n, 'd> {
filename: Option<Cow<'n, str>>,
content_type: Mime,
stream: Box<dyn Read + 'd>,
}
pub struct PreparedFields<'d> {
text_data: Cursor<Vec<u8>>,
streams: Vec<PreparedField<'d>>,
end_boundary: Cursor<String>,
content_len: Option<u64>,
}
impl<'d> PreparedFields<'d> {
fn from_fields<'n>(fields: &mut Vec<Field<'n, 'd>>) -> Result<Self, LazyIoError<'n>> {
debug!("Field count: {}", fields.len());
let mut boundary = format!("\r\n--{}", super::gen_boundary());
let mut text_data = Vec::new();
let mut streams = Vec::new();
let mut content_len = 0u64;
let mut use_len = true;
for field in fields.drain(..) {
match field.data {
Data::Text(text) => write!(
text_data,
"{}\r\nContent-Disposition: form-data; \
name=\"{}\"\r\n\r\n{}",
boundary, field.name, text
)
.unwrap(),
Data::File(file) => {
let (stream, len) = PreparedField::from_path(field.name, &file, &boundary)?;
content_len += len;
streams.push(stream);
}
Data::Stream(stream) => {
use_len = false;
streams.push(PreparedField::from_stream(
&field.name,
&boundary,
&stream.content_type,
stream.filename.as_ref().map(|f| &**f),
stream.stream,
));
}
}
}
if text_data.is_empty() && streams.is_empty() {
boundary = String::new();
} else {
boundary.push_str("--");
}
content_len += boundary.len() as u64;
Ok(PreparedFields {
text_data: Cursor::new(text_data),
streams,
end_boundary: Cursor::new(boundary),
content_len: if use_len { Some(content_len) } else { None },
})
}
pub fn content_len(&self) -> Option<u64> {
self.content_len
}
pub fn boundary(&self) -> &str {
let boundary = self.end_boundary.get_ref();
&boundary[4..boundary.len() - 2]
}
}
impl<'d> Read for PreparedFields<'d> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
debug!("PreparedFields::read() was passed a zero-sized buffer.");
return Ok(0);
}
let mut total_read = 0;
while total_read < buf.len() && !cursor_at_end(&self.end_boundary) {
let buf = &mut buf[total_read..];
total_read += if !cursor_at_end(&self.text_data) {
self.text_data.read(buf)?
} else if let Some(mut field) = self.streams.pop() {
match field.read(buf) {
Ok(0) => continue,
res => {
self.streams.push(field);
res
}
}?
} else {
self.end_boundary.read(buf)?
};
}
Ok(total_read)
}
}
struct PreparedField<'d> {
header: Cursor<Vec<u8>>,
stream: Box<dyn Read + 'd>,
}
impl<'d> PreparedField<'d> {
fn from_path<'n>(
name: Cow<'n, str>,
path: &Path,
boundary: &str,
) -> Result<(Self, u64), LazyIoError<'n>> {
let (content_type, filename) = super::mime_filename(&path);
let file = try_lazy!(name, File::open(path));
let content_len = try_lazy!(name, file.metadata()).len();
let stream = Self::from_stream(&name, boundary, &content_type, filename, Box::new(file));
let content_len = content_len + (stream.header.get_ref().len() as u64);
Ok((stream, content_len))
}
fn from_stream(
name: &str,
boundary: &str,
content_type: &Mime,
filename: Option<&str>,
stream: Box<dyn Read + 'd>,
) -> Self {
let mut header = Vec::new();
write!(
header,
"{}\r\nContent-Disposition: form-data; name=\"{}\"",
boundary, name
)
.unwrap();
if let Some(filename) = filename {
write!(header, "; filename=\"{}\"", filename).unwrap();
}
write!(header, "\r\nContent-Type: {}\r\n\r\n", content_type).unwrap();
PreparedField {
header: Cursor::new(header),
stream,
}
}
}
impl<'d> Read for PreparedField<'d> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
debug!("PreparedField::read()");
if !cursor_at_end(&self.header) {
self.header.read(buf)
} else {
self.stream.read(buf)
}
}
}
impl<'d> fmt::Debug for PreparedField<'d> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PreparedField")
.field("header", &self.header)
.field("stream", &"Box<Read>")
.finish()
}
}
pub trait IntoCowPath<'a> {
fn into_cow_path(self) -> Cow<'a, Path>;
}
impl<'a> IntoCowPath<'a> for Cow<'a, Path> {
fn into_cow_path(self) -> Cow<'a, Path> {
self
}
}
impl IntoCowPath<'static> for PathBuf {
fn into_cow_path(self) -> Cow<'static, Path> {
self.into()
}
}
impl<'a> IntoCowPath<'a> for &'a Path {
fn into_cow_path(self) -> Cow<'a, Path> {
self.into()
}
}
impl IntoCowPath<'static> for String {
fn into_cow_path(self) -> Cow<'static, Path> {
PathBuf::from(self).into()
}
}
impl<'a> IntoCowPath<'a> for &'a str {
fn into_cow_path(self) -> Cow<'a, Path> {
Path::new(self).into()
}
}
fn cursor_at_end<T: AsRef<[u8]>>(cursor: &Cursor<T>) -> bool {
cursor.position() == (cursor.get_ref().as_ref().len() as u64)
}
#[cfg(feature = "hyper")]
mod hyper {
use hyper::client::{Body, Client, IntoUrl, RequestBuilder, Response};
use hyper::Result as HyperResult;
impl<'n, 'd> super::Multipart<'n, 'd> {
pub fn client_request<U: IntoUrl>(
&mut self,
client: &Client,
url: U,
) -> HyperResult<Response> {
self.client_request_mut(client, url, |r| r)
}
pub fn client_request_mut<U: IntoUrl, F: FnOnce(RequestBuilder) -> RequestBuilder>(
&mut self,
client: &Client,
url: U,
mut_fn: F,
) -> HyperResult<Response> {
let mut fields = match self.prepare() {
Ok(fields) => fields,
Err(err) => {
error!("Error preparing request: {}", err);
return Err(err.error.into());
}
};
mut_fn(client.post(url))
.header(::client::hyper::content_type(fields.boundary()))
.body(fields.to_body())
.send()
}
}
impl<'d> super::PreparedFields<'d> {
#[cfg_attr(feature = "clippy", warn(wrong_self_convention))]
pub fn to_body<'b>(&'b mut self) -> Body<'b>
where
'd: 'b,
{
if let Some(content_len) = self.content_len {
Body::SizedBody(self, content_len)
} else {
Body::ChunkedBody(self)
}
}
}
}