mod error {
use std::error::Error as StdError;
use std::fmt::{self, Display};
use std::io;
use http::header::ToStrError;
pub enum Error {
NoRequestContentType,
NotMultipart,
BoundaryNotSpecified,
PartialHeaders,
EofBeforeFirstBoundary,
NoCrLfAfterBoundary,
EofInPartHeaders,
EofInFile,
EofInPart,
InvalidHeaderNameOrValue,
HeaderValueNotMime,
ToStr(ToStrError),
Httparse(httparse::Error),
Io(io::Error),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io(err)
}
}
impl From<httparse::Error> for Error {
fn from(err: httparse::Error) -> Error {
Error::Httparse(err)
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Httparse(ref e) => format!("Httparse: {:?}", e).fmt(f),
Error::Io(ref e) => format!("Io: {}", e).fmt(f),
Error::ToStr(ref e) => format!("ToStr: {}", e).fmt(f),
Error::NoRequestContentType => "NoRequestContentType".to_string().fmt(f),
Error::NotMultipart => "NotMultipart".to_string().fmt(f),
Error::BoundaryNotSpecified => "BoundaryNotSpecified".to_string().fmt(f),
Error::PartialHeaders => "PartialHeaders".to_string().fmt(f),
Error::EofBeforeFirstBoundary => "EofBeforeFirstBoundary".to_string().fmt(f),
Error::NoCrLfAfterBoundary => "NoCrLfAfterBoundary".to_string().fmt(f),
Error::EofInPartHeaders => "EofInPartHeaders".to_string().fmt(f),
Error::EofInFile => "EofInFile".to_string().fmt(f),
Error::EofInPart => "EofInPart".to_string().fmt(f),
Error::InvalidHeaderNameOrValue => "InvalidHeaderNameOrValue".to_string().fmt(f),
Error::HeaderValueNotMime => "HeaderValueNotMime".to_string().fmt(f),
}
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)?;
if self.source().is_some() {
write!(f, ": {:?}", self.source().unwrap())?; }
Ok(())
}
}
impl StdError for Error {
fn description(&self) -> &str {
match *self {
Error::NoRequestContentType => {
"The Hyper request did not have a Content-Type header."
},
Error::NotMultipart => {
"The Hyper request Content-Type top-level Mime was not multipart."
},
Error::BoundaryNotSpecified => {
"The Content-Type header failed to specify a boundary token."
},
Error::PartialHeaders => "A multipart section contained only partial headers.",
Error::EofBeforeFirstBoundary => {
"The request body ended prior to reaching the expected starting boundary."
},
Error::NoCrLfAfterBoundary => "Missing CRLF after boundary.",
Error::EofInPartHeaders => {
"The request body ended prematurely while parsing headers of a multipart part."
},
Error::EofInFile => {
"The request body ended prematurely while streaming a file part."
},
Error::EofInPart => {
"The request body ended prematurely while reading a multipart part."
},
Error::Httparse(_) => {
"A parse error occurred while parsing the headers of a multipart section."
},
Error::Io(_) => "An I/O error occurred.",
Error::InvalidHeaderNameOrValue => "Parsing to HeaderName or HeaderValue failed",
Error::HeaderValueNotMime => "HeaderValue could not be parsed to Mime",
Error::ToStr(_) => "A ToStr error occurred.",
}
}
}
}
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::ops::Drop;
use std::path::PathBuf;
use std::str::FromStr;
use buf_read_ext::BufReadExt;
pub use error::Error;
use http::header::{HeaderMap, HeaderName, HeaderValue};
use mime::Mime;
#[derive(Clone, Debug, PartialEq)]
pub struct Part {
pub headers: HeaderMap,
pub body: Vec<u8>,
}
#[derive(Debug, PartialEq)]
pub struct FilePart {
pub headers: HeaderMap,
pub path: PathBuf,
pub size: Option<usize>,
tempdir: Option<PathBuf>,
}
impl FilePart {
pub fn create(headers: HeaderMap) -> Result<FilePart, Error> {
let mut path = tempfile::Builder::new()
.prefix("mime_multipart")
.tempdir()?
.keep();
let tempdir = Some(path.clone());
path.push("part");
Ok(FilePart {
headers,
path,
size: None,
tempdir,
})
}
}
impl Drop for FilePart {
fn drop(&mut self) {
if let Some(tempdir) = &self.tempdir {
let _ = std::fs::remove_file(&self.path);
let _ = std::fs::remove_dir(tempdir);
}
}
}
#[derive(Debug)]
pub enum Node {
Part(Part),
File(FilePart),
Multipart((HeaderMap, Vec<Node>)),
}
pub fn read_multipart_body<S: Read>(
stream: &mut S,
headers: &HeaderMap,
always_use_files: bool,
) -> Result<Vec<Node>, Error> {
let mut reader = BufReader::with_capacity(4096, stream);
inner(&mut reader, headers, always_use_files)
}
fn inner<R: BufRead>(
reader: &mut R,
headers: &HeaderMap,
always_use_files: bool,
) -> Result<Vec<Node>, Error> {
let mut nodes: Vec<Node> = Vec::new();
let mut buf: Vec<u8> = Vec::new();
let boundary = get_multipart_boundary(headers)?;
let (_, found) = reader.stream_until_token(&boundary, &mut buf)?;
if !found {
return Err(Error::EofBeforeFirstBoundary);
}
let (lt, ltlt, lt_boundary) = {
let peeker = 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.is_empty() && 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 = reader.fill_buf()?;
if peeker.len() >= 2 && &peeker[..2] == b"--" {
return Ok(nodes);
}
}
let (_, found) = reader.stream_until_token(<, &mut buf)?;
if !found {
return Err(Error::NoCrLfAfterBoundary);
}
buf.clear(); let (_, found) = 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];
match httparse::parse_headers(&buf, &mut header_memory) {
Ok(httparse::Status::Complete((_, raw_headers))) => {
let mut headers = HeaderMap::new();
for header in raw_headers {
if header.value.is_empty() {
break;
}
let trim = header
.value
.iter()
.rev()
.take_while(|&&x| x == b' ')
.count();
let value = &header.value[..header.value.len() - trim];
let header_value = match HeaderValue::from_bytes(value) {
Ok(value) => value,
Err(_) => return Err(Error::InvalidHeaderNameOrValue),
};
let header_name = header.name.to_owned();
let header_name = match HeaderName::from_str(&header_name) {
Ok(value) => value,
Err(_) => return Err(Error::InvalidHeaderNameOrValue),
};
headers.append(header_name, header_value);
}
Ok(headers)
},
Ok(httparse::Status::Partial) => Err(Error::PartialHeaders),
Err(err) => Err(From::from(err)),
}?
};
let nested = {
match part_headers.get("content-type") {
Some(ct) => match ct.to_str() {
Ok(value) => match Mime::from_str(value) {
Ok(mime) => mime.type_() == mime::MULTIPART,
Err(_) => return Err(Error::HeaderValueNotMime),
},
Err(err) => return Err(Error::ToStr(err)),
},
None => false,
}
};
if nested {
let inner_nodes = inner(reader, &part_headers, always_use_files)?;
nodes.push(Node::Multipart((part_headers, inner_nodes)));
continue;
}
let is_file = always_use_files || {
match part_headers.get("content-disposition") {
Some(content) => match content.to_str() {
Ok(value) => value.contains("attachment") || value.contains("filename"),
Err(err) => return Err(Error::ToStr(err)),
},
None => false,
}
};
if is_file {
let mut filepart = FilePart::create(part_headers)?;
let mut file = File::create(filepart.path.clone())?;
let (read, found) = reader.stream_until_token(<_boundary, &mut file)?;
if !found {
return Err(Error::EofInFile);
}
filepart.size = Some(read);
nodes.push(Node::File(filepart));
} else {
buf.clear(); let (_, found) = 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: &HeaderMap) -> Result<Vec<u8>, Error> {
let mime = match headers.get("content-type") {
Some(ct) => match ct.to_str() {
Ok(value) => match Mime::from_str(value) {
Ok(value) => value,
Err(_) => return Err(Error::HeaderValueNotMime),
},
Err(err) => return Err(Error::ToStr(err)),
},
None => return Err(Error::NoRequestContentType),
};
let top_level = mime.type_();
if top_level != mime::MULTIPART {
return Err(Error::NotMultipart);
}
match mime.get_param(mime::BOUNDARY) {
None => Err(Error::BoundaryNotSpecified),
Some(content) => {
let mut boundary = vec![];
boundary.extend(b"--".iter().cloned());
boundary.extend(content.to_string().as_bytes());
Ok(boundary)
},
}
}