use mime::Mime;
use std::borrow::Cow;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::Path;
#[cfg(feature = "hyper")]
pub mod hyper;
pub mod lazy;
mod sized;
pub use self::sized::SizedRequest;
const BOUNDARY_LEN: usize = 16;
macro_rules! map_self {
($selff:expr, $try:expr) => (
match $try {
Ok(_) => Ok($selff),
Err(err) => Err(err.into()),
}
)
}
pub struct Multipart<S> {
writer: MultipartWriter<'static, S>,
}
impl Multipart<()> {
pub fn from_request<R: HttpRequest>(req: R) -> Result<Multipart<R::Stream>, R::Error> {
let (boundary, stream) = try!(open_stream(req, None));
Ok(Multipart {
writer: MultipartWriter::new(stream, boundary),
})
}
}
impl<S: HttpStream> Multipart<S> {
pub fn write_text<N: AsRef<str>, V: AsRef<str>>(&mut self, name: N, val: V) -> Result<&mut Self, S::Error> {
map_self!(self, self.writer.write_text(name.as_ref(), val.as_ref()))
}
pub fn write_file<N: AsRef<str>, P: AsRef<Path>>(&mut self, name: N, path: P) -> Result<&mut Self, S::Error> {
let name = name.as_ref();
let path = path.as_ref();
map_self!(self, self.writer.write_file(name, path))
}
pub fn write_stream<N: AsRef<str>, St: Read>(
&mut self, name: N, stream: &mut St, filename: Option<&str>, content_type: Option<Mime>
) -> Result<&mut Self, S::Error> {
let name = name.as_ref();
map_self!(self, self.writer.write_stream(stream, name, filename, content_type))
}
pub fn send(self) -> Result<S::Response, S::Error> {
self.writer.finish().map_err(io::Error::into).and_then(|body| body.finish())
}
}
impl<R: HttpRequest> Multipart<SizedRequest<R>>
where <R::Stream as HttpStream>::Error: From<R::Error> {
pub fn from_request_sized(req: R) -> Result<Self, R::Error> {
Multipart::from_request(SizedRequest::from_request(req))
}
}
pub trait HttpRequest {
type Stream: HttpStream;
type Error: From<io::Error> + Into<<Self::Stream as HttpStream>::Error>;
fn apply_headers(&mut self, boundary: &str, content_len: Option<u64>) -> bool;
fn open_stream(self) -> Result<Self::Stream, Self::Error>;
}
pub trait HttpStream: Write {
type Request: HttpRequest;
type Response;
type Error: From<io::Error> + From<<Self::Request as HttpRequest>::Error>;
fn finish(self) -> Result<Self::Response, Self::Error>;
}
impl HttpRequest for () {
type Stream = io::Sink;
type Error = io::Error;
fn apply_headers(&mut self, _: &str, _: Option<u64>) -> bool { true }
fn open_stream(self) -> Result<Self::Stream, Self::Error> { Ok(io::sink()) }
}
impl HttpStream for io::Sink {
type Request = ();
type Response = ();
type Error = io::Error;
fn finish(self) -> Result<Self::Response, Self::Error> { Ok(()) }
}
fn gen_boundary() -> String {
::random_alphanumeric(BOUNDARY_LEN)
}
fn open_stream<R: HttpRequest>(mut req: R, content_len: Option<u64>) -> Result<(String, R::Stream), R::Error> {
let boundary = gen_boundary();
req.apply_headers(&boundary, content_len);
req.open_stream().map(|stream| (boundary, stream))
}
struct MultipartWriter<'a, W> {
inner: W,
boundary: Cow<'a, str>,
data_written: bool,
}
impl<'a, W: Write> MultipartWriter<'a, W> {
fn new<B: Into<Cow<'a, str>>>(inner: W, boundary: B) -> Self {
MultipartWriter {
inner: inner,
boundary: boundary.into(),
data_written: false,
}
}
fn write_boundary(&mut self) -> io::Result<()> {
write!(self.inner, "\r\n--{}\r\n", self.boundary)
}
fn write_text(&mut self, name: &str, text: &str) -> io::Result<()> {
chain_result! {
self.write_field_headers(name, None, None),
self.inner.write_all(text.as_bytes())
}
}
fn write_file(&mut self, name: &str, path: &Path) -> io::Result<()> {
let (content_type, filename) = mime_filename(path);
let mut file = try!(File::open(path));
self.write_stream(&mut file, name, filename, Some(content_type))
}
fn write_stream<S: Read>(&mut self, stream: &mut S, name: &str, filename: Option<&str>, content_type: Option<Mime>) -> io::Result<()> {
let content_type = Some(content_type.unwrap_or_else(::mime_guess::octet_stream));
chain_result! {
self.write_field_headers(name, filename, content_type),
io::copy(stream, &mut self.inner),
Ok(())
}
}
fn write_field_headers(&mut self, name: &str, filename: Option<&str>, content_type: Option<Mime>)
-> io::Result<()> {
chain_result! {
self.write_boundary(),
{ self.data_written = true; Ok(()) },
write!(self.inner, "Content-Disposition: form-data; name=\"{}\"", name),
filename.map(|filename| write!(self.inner, "; filename=\"{}\"", filename))
.unwrap_or(Ok(())),
content_type.map(|content_type| write!(self.inner, "\r\nContent-Type: {}", content_type))
.unwrap_or(Ok(())),
self.inner.write_all(b"\r\n\r\n")
}
}
fn finish(mut self) -> io::Result<W> {
if self.data_written {
try!(write!(self.inner, "\r\n--{}--", self.boundary));
}
Ok(self.inner)
}
}
fn mime_filename(path: &Path) -> (Mime, Option<&str>) {
let content_type = ::mime_guess::guess_mime_type(path);
let filename = opt_filename(path);
(content_type, filename)
}
fn opt_filename(path: &Path) -> Option<&str> {
path.file_name().and_then(|filename| filename.to_str())
}