extern crate httparse;
extern crate hyper;
#[macro_use]
extern crate mime;
extern crate tempdir;
extern crate textnonce;
extern crate log;
extern crate encoding;
extern crate buf_read_ext;
pub mod error;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub use error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
use std::borrow::Cow;
use std::ops::Drop;
use encoding::{all, Encoding, DecoderTrap};
use hyper::header::{ContentType, Headers, ContentDisposition, DispositionParam,
DispositionType, Charset};
use tempdir::TempDir;
use textnonce::TextNonce;
use mime::{Attr, Mime, TopLevel, Value};
use buf_read_ext::BufReadExt;
#[derive(Clone, Debug, PartialEq)]
pub struct Part {
pub headers: Headers,
pub body: Vec<u8>,
}
impl Part {
pub fn content_type(&self) -> Option<Mime> {
let ct: Option<&ContentType> = self.headers.get();
ct.map(|ref ct| ct.0.clone())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FilePart {
pub headers: Headers,
pub path: PathBuf,
pub size: Option<usize>,
tempdir: Option<PathBuf>,
}
impl FilePart {
pub fn new(headers: Headers, path: &Path) -> FilePart
{
FilePart {
headers: headers,
path: path.to_owned(),
size: None,
tempdir: None,
}
}
pub fn do_not_delete_on_drop(&mut self) {
self.tempdir = None;
}
pub fn create(headers: Headers) -> Result<FilePart, Error> {
let mut path = try!(TempDir::new("mime_multipart")).into_path();
let tempdir = Some(path.clone());
path.push(TextNonce::sized_urlsafe(32).unwrap().into_string());
Ok(FilePart {
headers: headers,
path: path,
size: None,
tempdir: tempdir,
})
}
pub fn filename(&self) -> Result<Option<String>, Error> {
let cd: Option<&ContentDisposition> = self.headers.get();
match cd {
Some(cd) => get_content_disposition_filename(cd),
None => Ok(None),
}
}
pub fn content_type(&self) -> Option<Mime> {
let ct: Option<&ContentType> = self.headers.get();
ct.map(|ref ct| ct.0.clone())
}
}
impl Drop for FilePart {
fn drop(&mut self) {
if self.tempdir.is_some() {
let _ = ::std::fs::remove_file(&self.path);
let _ = ::std::fs::remove_dir(&self.tempdir.as_ref().unwrap());
}
}
}
#[derive(Clone, Debug)]
pub enum Node {
Part(Part),
File(FilePart),
Multipart((Headers, Vec<Node>)),
}
pub fn read_multipart<S: Read>(
stream: &mut S,
always_use_files: bool)
-> Result<Vec<Node>, Error>
{
let mut reader = BufReader::with_capacity(4096, stream);
let mut nodes: Vec<Node> = Vec::new();
let mut buf: Vec<u8> = Vec::new();
let (_, found) = try!(reader.stream_until_token(b"\r\n\r\n", &mut buf));
if ! found { return Err(Error::EofInMainHeaders); }
buf.extend(b"\r\n\r\n".iter().cloned());
let mut header_memory = [httparse::EMPTY_HEADER; 64];
let headers = try!(match httparse::parse_headers(&buf, &mut header_memory) {
Ok(httparse::Status::Complete((_, raw_headers))) => {
Headers::from_raw(raw_headers).map_err(|e| From::from(e))
},
Ok(httparse::Status::Partial) => Err(Error::PartialHeaders),
Err(err) => Err(From::from(err)),
});
try!(inner(&mut reader, &headers, &mut nodes, always_use_files));
Ok(nodes)
}
pub fn read_multipart_body<S: Read>(
stream: &mut S,
headers: &Headers,
always_use_files: bool)
-> Result<Vec<Node>, Error>
{
let mut reader = BufReader::with_capacity(4096, stream);
let mut nodes: Vec<Node> = Vec::new();
try!(inner(&mut reader, headers, &mut nodes, always_use_files));
Ok(nodes)
}
fn inner<R: BufRead>(
reader: &mut R,
headers: &Headers,
nodes: &mut Vec<Node>,
always_use_files: bool)
-> Result<(), Error>
{
let mut buf: Vec<u8> = Vec::new();
let boundary = try!(get_multipart_boundary(headers));
let (_, found) = try!(reader.stream_until_token(&boundary, &mut buf));
if ! found { return Err(Error::EofBeforeFirstBoundary); }
let (lt, ltlt, lt_boundary) = {
let peeker = try!(reader.fill_buf());
if peeker.len() > 1 && &peeker[..2]==b"\r\n" {
let mut output = Vec::with_capacity(2 + boundary.len());
output.push(b'\r');
output.push(b'\n');
output.extend(boundary.clone());
(vec![b'\r', b'\n'], vec![b'\r', b'\n', b'\r', b'\n'], output)
}
else if peeker.len() > 0 && peeker[0]==b'\n' {
let mut output = Vec::with_capacity(1 + boundary.len());
output.push(b'\n');
output.extend(boundary.clone());
(vec![b'\n'], vec![b'\n', b'\n'], output)
}
else {
return Err(Error::NoCrLfAfterBoundary);
}
};
loop {
{
let peeker = try!(reader.fill_buf());
if peeker.len() >= 2 && &peeker[..2] == b"--" {
return Ok(());
}
}
let (_, found) = try!(reader.stream_until_token(<, &mut buf));
if ! found { return Err(Error::NoCrLfAfterBoundary); }
buf.truncate(0);
let (_, found) = try!(reader.stream_until_token(<lt, &mut buf));
if ! found { return Err(Error::EofInPartHeaders); }
buf.extend(ltlt.iter().cloned());
let part_headers = {
let mut header_memory = [httparse::EMPTY_HEADER; 4];
try!(match httparse::parse_headers(&buf, &mut header_memory) {
Ok(httparse::Status::Complete((_, raw_headers))) => {
Headers::from_raw(raw_headers).map_err(|e| From::from(e))
},
Ok(httparse::Status::Partial) => Err(Error::PartialHeaders),
Err(err) => Err(From::from(err)),
})
};
let nested = {
let ct: Option<&ContentType> = part_headers.get();
if let Some(ct) = ct {
let &ContentType(Mime(ref top_level, _, _)) = ct;
*top_level == TopLevel::Multipart
} else {
false
}
};
if nested {
let mut inner_nodes: Vec<Node> = Vec::new();
try!(inner(reader, &part_headers, &mut inner_nodes, always_use_files));
nodes.push(Node::Multipart((part_headers, inner_nodes)));
continue;
}
let is_file = always_use_files || {
let cd: Option<&ContentDisposition> = part_headers.get();
if cd.is_some() {
if cd.unwrap().disposition == DispositionType::Attachment {
true
} else {
cd.unwrap().parameters.iter().any(|x| match x {
&DispositionParam::Filename(_,_,_) => true,
_ => false
})
}
} else {
false
}
};
if is_file {
let mut filepart = try!(FilePart::create(part_headers));
let mut file = try!(File::create(filepart.path.clone()));
let (read, found) = try!(reader.stream_until_token(<_boundary, &mut file));
if ! found { return Err(Error::EofInFile); }
filepart.size = Some(read);
nodes.push(Node::File(filepart));
} else {
buf.truncate(0);
let (_, found) = try!(reader.stream_until_token(<_boundary, &mut buf));
if ! found { return Err(Error::EofInPart); }
nodes.push(Node::Part(Part {
headers: part_headers,
body: buf.clone(),
}));
}
}
}
pub fn get_multipart_boundary(headers: &Headers) -> Result<Vec<u8>, Error> {
let ct: &ContentType = match headers.get() {
Some(ct) => ct,
None => return Err(Error::NoRequestContentType),
};
let ContentType(ref mime) = *ct;
let Mime(ref top_level, _, ref params) = *mime;
if *top_level != TopLevel::Multipart {
return Err(Error::NotMultipart);
}
for &(ref attr, ref val) in params.iter() {
if let (&Attr::Boundary, &Value::Ext(ref val)) = (attr, val) {
let mut boundary = Vec::with_capacity(2 + val.len());
boundary.extend(b"--".iter().cloned());
boundary.extend(val.as_bytes());
return Ok(boundary);
}
}
Err(Error::BoundaryNotSpecified)
}
#[inline]
fn get_content_disposition_filename(cd: &ContentDisposition) -> Result<Option<String>, Error> {
if let Some(&DispositionParam::Filename(ref charset, _, ref bytes)) =
cd.parameters.iter().find(|&x| match *x {
DispositionParam::Filename(_,_,_) => true,
_ => false,
})
{
match charset_decode(charset, bytes) {
Ok(filename) => Ok(Some(filename)),
Err(e) => Err(Error::Decoding(e)),
}
} else {
Ok(None)
}
}
fn charset_decode(charset: &Charset, bytes: &[u8]) -> Result<String, Cow<'static, str>> {
Ok(match *charset {
Charset::Us_Ascii => try!(all::ASCII.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_1 => try!(all::ISO_8859_1.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_2 => try!(all::ISO_8859_2.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_3 => try!(all::ISO_8859_3.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_4 => try!(all::ISO_8859_4.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_5 => try!(all::ISO_8859_5.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_6 => try!(all::ISO_8859_6.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_7 => try!(all::ISO_8859_7.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_8 => try!(all::ISO_8859_8.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_8859_9 => return Err("ISO_8859_9 is not supported".into()),
Charset::Iso_8859_10 => try!(all::ISO_8859_10.decode(bytes, DecoderTrap::Strict)),
Charset::Shift_Jis => return Err("Shift_Jis is not supported".into()),
Charset::Euc_Jp => try!(all::EUC_JP.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_2022_Kr => return Err("Iso_2022_Kr is not supported".into()),
Charset::Euc_Kr => return Err("Euc_Kr is not supported".into()),
Charset::Iso_2022_Jp => try!(all::ISO_2022_JP.decode(bytes, DecoderTrap::Strict)),
Charset::Iso_2022_Jp_2 => return Err("Iso_2022_Jp_2 is not supported".into()),
Charset::Iso_8859_6_E => return Err("Iso_8859_6_E is not supported".into()),
Charset::Iso_8859_6_I => return Err("Iso_8859_6_I is not supported".into()),
Charset::Iso_8859_8_E => return Err("Iso_8859_8_E is not supported".into()),
Charset::Iso_8859_8_I => return Err("Iso_8859_8_I is not supported".into()),
Charset::Gb2312 => return Err("Gb2312 is not supported".into()),
Charset::Big5 => try!(all::BIG5_2003.decode(bytes, DecoderTrap::Strict)),
Charset::Koi8_R => try!(all::KOI8_R.decode(bytes, DecoderTrap::Strict)),
Charset::Ext(ref s) => match &**s {
"UTF-8" => try!(all::UTF_8.decode(bytes, DecoderTrap::Strict)),
_ => return Err("Encoding is not supported".into()),
},
})
}
pub fn generate_boundary() -> Vec<u8> {
TextNonce::sized(68).unwrap().into_string().into_bytes()
}
trait WriteAllCount {
fn write_all_count(&mut self, buf: &[u8]) -> ::std::io::Result<usize>;
}
impl<T: Write> WriteAllCount for T {
fn write_all_count(&mut self, buf: &[u8]) -> ::std::io::Result<usize>
{
try!(self.write_all(buf));
Ok(buf.len())
}
}
pub fn write_multipart<S: Write>(
stream: &mut S,
boundary: &Vec<u8>,
nodes: &Vec<Node>)
-> Result<usize, Error>
{
let mut count: usize = 0;
for node in nodes {
count += try!(stream.write_all_count(b"--"));
count += try!(stream.write_all_count(&boundary));
count += try!(stream.write_all_count(b"\r\n"));
match node {
&Node::Part(ref part) => {
for header in part.headers.iter() {
count += try!(stream.write_all_count(header.name().as_bytes()));
count += try!(stream.write_all_count(b": "));
count += try!(stream.write_all_count(header.value_string().as_bytes()));
count += try!(stream.write_all_count(b"\r\n"));
}
count += try!(stream.write_all_count(b"\r\n"));
count += try!(stream.write_all_count(&part.body));
},
&Node::File(ref filepart) => {
for header in filepart.headers.iter() {
count += try!(stream.write_all_count(header.name().as_bytes()));
count += try!(stream.write_all_count(b": "));
count += try!(stream.write_all_count(header.value_string().as_bytes()));
count += try!(stream.write_all_count(b"\r\n"));
}
count += try!(stream.write_all_count(b"\r\n"));
let mut file = try!(File::open(&filepart.path));
count += try!(::std::io::copy(&mut file, stream)) as usize;
},
&Node::Multipart((ref headers, ref subnodes)) => {
let boundary = try!(get_multipart_boundary(headers));
for header in headers.iter() {
count += try!(stream.write_all_count(header.name().as_bytes()));
count += try!(stream.write_all_count(b": "));
count += try!(stream.write_all_count(header.value_string().as_bytes()));
count += try!(stream.write_all_count(b"\r\n"));
}
count += try!(stream.write_all_count(b"\r\n"));
count += try!(write_multipart(stream, &boundary, &subnodes));
},
}
count += try!(stream.write_all_count(b"\r\n"));
}
count += try!(stream.write_all_count(b"--"));
count += try!(stream.write_all_count(&boundary));
count += try!(stream.write_all_count(b"--"));
Ok(count)
}
pub fn write_chunk<S: Write>(
stream: &mut S,
chunk: &[u8]) -> Result<(), ::std::io::Error>
{
try!(write!(stream, "{:x}\r\n", chunk.len()));
try!(stream.write_all(chunk));
try!(stream.write_all(b"\r\n"));
Ok(())
}
pub fn write_multipart_chunked<S: Write>(
stream: &mut S,
boundary: &Vec<u8>,
nodes: &Vec<Node>)
-> Result<(), Error>
{
for node in nodes {
try!(write_chunk(stream, b"--"));
try!(write_chunk(stream, &boundary));
try!(write_chunk(stream, b"\r\n"));
match node {
&Node::Part(ref part) => {
for header in part.headers.iter() {
try!(write_chunk(stream, header.name().as_bytes()));
try!(write_chunk(stream, b": "));
try!(write_chunk(stream, header.value_string().as_bytes()));
try!(write_chunk(stream, b"\r\n"));
}
try!(write_chunk(stream, b"\r\n"));
try!(write_chunk(stream, &part.body));
},
&Node::File(ref filepart) => {
for header in filepart.headers.iter() {
try!(write_chunk(stream, header.name().as_bytes()));
try!(write_chunk(stream, b": "));
try!(write_chunk(stream, header.value_string().as_bytes()));
try!(write_chunk(stream, b"\r\n"));
}
try!(write_chunk(stream, b"\r\n"));
let metadata = try!(::std::fs::metadata(&filepart.path));
try!(write!(stream, "{:x}\r\n", metadata.len()));
let mut file = try!(File::open(&filepart.path));
try!(::std::io::copy(&mut file, stream)) as usize;
try!(stream.write(b"\r\n"));
},
&Node::Multipart((ref headers, ref subnodes)) => {
let boundary = try!(get_multipart_boundary(headers));
for header in headers.iter() {
try!(write_chunk(stream, header.name().as_bytes()));
try!(write_chunk(stream, b": "));
try!(write_chunk(stream, header.value_string().as_bytes()));
try!(write_chunk(stream, b"\r\n"));
}
try!(write_chunk(stream, b"\r\n"));
try!(write_multipart_chunked(stream, &boundary, &subnodes));
},
}
try!(write_chunk(stream, b"\r\n"));
}
try!(write_chunk(stream, b"--"));
try!(write_chunk(stream, &boundary));
try!(write_chunk(stream, b"--"));
try!(write_chunk(stream, b""));
Ok(())
}