use mime::Mime;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::Path;
#[cfg(feature = "hyper")]
pub mod hyper;
mod sized;
pub use self::sized::SizedRequest;
const BOUNDARY_LEN: usize = 16;
pub struct Multipart<S: HttpStream> {
stream: S,
boundary: String,
last_err: Option<S::Error>,
data_written: bool,
}
impl Multipart<io::Sink> {
pub fn from_request<R: HttpRequest>(mut req: R) -> Result<Multipart<R::Stream>, R::Error> {
let boundary = ::random_alphanumeric(BOUNDARY_LEN);
req.apply_headers(&boundary, None);
let stream = try!(req.open_stream());
Ok(Multipart {
stream: stream,
boundary: boundary,
last_err: None,
data_written: false,
})
}
}
impl<S: HttpStream> Multipart<S> {
pub fn last_err(&self) -> Option<&S::Error> {
self.last_err.as_ref()
}
pub fn take_err(&mut self) -> Option<S::Error> {
self.last_err.take()
}
pub fn write_text<N: AsRef<str>, V: AsRef<str>>(&mut self, name: N, val: V) -> &mut Self {
if self.last_err.is_none() {
self.last_err = chain_result! {
self.write_field_headers(name.as_ref(), None, None),
self.stream.write_all(val.as_ref().as_bytes())
}.err().map(|err| err.into())
}
self
}
pub fn write_file<N: AsRef<str>, P: AsRef<Path>>(&mut self, name: N, path: P) -> &mut Self {
if self.last_err.is_none() {
let path = path.as_ref();
self.last_err = chain_result! {
{ let content_type = ::mime_guess::guess_mime_type(path);
let filename = path.file_name().and_then(|filename| filename.to_str());
self.write_field_headers(name.as_ref(), filename, Some(content_type))
},
File::open(path).and_then(|ref mut file| io::copy(file, &mut self.stream))
}.err().map(|err| err.into());
}
self
}
pub fn write_stream<N: AsRef<str>, St: Read>(
&mut self, name: N, read: &mut St, filename: Option<&str>, content_type: Option<Mime>
) -> &mut Self {
if self.last_err.is_none() {
let content_type = content_type.unwrap_or_else(::mime_guess::octet_stream);
self.last_err = chain_result! {
self.write_field_headers(name.as_ref(), filename, Some(content_type)),
io::copy(read, &mut self.stream)
}.err().map(|err| err.into());
}
self
}
fn write_field_headers(&mut self, name: &str, filename: Option<&str>, content_type: Option<Mime>)
-> io::Result<()> {
self.data_written = true;
chain_result! {
write!(self.stream, "\r\n--{}\r\n", self.boundary),
write!(self.stream, "Content-Disposition: form-data; name=\"{}\"", name),
filename.map(|filename| write!(self.stream, "; filename=\"{}\"", filename))
.unwrap_or(Ok(())),
content_type.map(|content_type| write!(self.stream, "\r\nContent-Type: {}", content_type))
.unwrap_or(Ok(())),
self.stream.write_all(b"\r\n\r\n")
}
}
pub fn send(mut self) -> Result<S::Response, S::Error> {
match self.last_err {
None => {
if self.data_written {
try!(write!(self.stream, "\r\n--{}--", self.boundary));
}
self.stream.finish()
},
Some(err) => Err(err),
}
}
}
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(()) }
}