use mime::Mime;
use tempdir::TempDir;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::{fmt, io, mem, ptr};
use self::boundary::BoundaryReader;
macro_rules! try_opt (
($expr:expr) => (
match $expr {
Some(val) => val,
None => return None,
}
)
);
mod boundary;
#[cfg(feature = "hyper")]
pub mod hyper;
#[cfg(feature = "iron")]
pub mod iron;
#[cfg(feature = "nickel2")]
mod nickel;
#[cfg(feature = "tiny_http")]
pub mod tiny_http;
const RANDOM_FILENAME_LEN: usize = 12;
pub struct Multipart<B> {
source: BoundaryReader<B>,
line_buf: String,
}
impl Multipart<()> {
pub fn from_request<R: HttpRequest>(req: R) -> Result<Multipart<R::Body>, R> {
let boundary = match req.multipart_boundary().map(String::from) {
Some(boundary) => boundary,
None => return Err(req),
};
Ok(Multipart::with_body(req.body(), boundary))
}
}
impl<B: Read> Multipart<B> {
pub fn with_body<Bnd: Into<String>>(body: B, boundary: Bnd) -> Self {
let boundary = prepend_str("--", boundary.into());
debug!("Boundary: {}", boundary);
Multipart {
source: BoundaryReader::from_reader(body, boundary),
line_buf: String::new(),
}
}
pub fn read_entry(&mut self) -> io::Result<Option<MultipartField<B>>> {
if !try!(self.consume_boundary()) {
return Ok(None);
}
MultipartField::read_from(self)
}
fn read_content_disposition(&mut self) -> io::Result<Option<ContentDisp>> {
let line = try!(self.read_line());
Ok(ContentDisp::read_from(line))
}
pub fn foreach_entry<F>(&mut self, mut foreach: F) -> io::Result<()> where F: FnMut(MultipartField<B>) {
loop {
match self.read_entry() {
Ok(Some(field)) => foreach(field),
Ok(None) => return Ok(()),
Err(err) => return Err(err),
}
}
}
fn read_content_type(&mut self) -> io::Result<Option<ContentType>> {
debug!("Read content type!");
let line = try!(self.read_line());
Ok(ContentType::read_from(line))
}
pub fn save_all(&mut self) -> SaveResult {
let mut entries = match Entries::new_tempdir() {
Ok(entries) => entries,
Err(err) => return SaveResult::Error(err),
};
match self.read_to_entries(&mut entries) {
Ok(()) => SaveResult::Full(entries),
Err(err) => SaveResult::Partial(entries, err),
}
}
pub fn save_all_under<P: AsRef<Path>>(&mut self, dir: P) -> SaveResult {
let mut entries = match Entries::new_tempdir_in(dir) {
Ok(entries) => entries,
Err(err) => return SaveResult::Error(err),
};
match self.read_to_entries(&mut entries) {
Ok(()) => SaveResult::Full(entries),
Err(err) => SaveResult::Partial(entries, err),
}
}
fn read_to_entries(&mut self, entries: &mut Entries) -> io::Result<()> {
while let Some(field) = try!(self.read_entry()) {
match field.data {
MultipartData::File(mut file) => {
let file = try!(file.save_in(&entries.dir));
entries.files.insert(field.name, file);
},
MultipartData::Text(text) => {
entries.fields.insert(field.name, text.into());
},
}
}
Ok(())
}
fn read_line(&mut self) -> io::Result<&str> {
self.line_buf.clear();
match self.source.read_line(&mut self.line_buf) {
Ok(read) => Ok(&self.line_buf[..read]),
Err(err) => Err(err),
}
}
fn read_to_string(&mut self) -> io::Result<&str> {
self.line_buf.clear();
match self.source.read_to_string(&mut self.line_buf) {
Ok(read) => Ok(&self.line_buf[..read]),
Err(err) => Err(err),
}
}
fn consume_boundary(&mut self) -> io::Result<bool> {
try!(self.source.consume_boundary());
let mut out = [0; 2];
let _ = try!(self.source.read(&mut out));
if *b"\r\n" == out {
return Ok(true);
} else {
if *b"--" != out {
warn!("Unexpected 2-bytes after boundary: {:?}", out);
}
return Ok(false);
}
}
}
impl<B> Borrow<B> for Multipart<B> {
fn borrow(&self) -> &B {
self.source.borrow()
}
}
#[derive(Debug)]
pub enum SaveResult {
Full(Entries),
Partial(Entries, io::Error),
Error(io::Error),
}
impl SaveResult {
pub fn to_entries(self) -> Option<Entries> {
use self::SaveResult::*;
match self {
Full(entries) | Partial(entries, _) => Some(entries),
Error(_) => None,
}
}
pub fn to_opt(self) -> (Option<Entries>, Option<io::Error>) {
use self::SaveResult::*;
match self {
Full(entries) => (Some(entries), None),
Partial(entries, error) => (Some(entries), Some(error)),
Error(error) => (None, Some(error)),
}
}
pub fn to_result(self) -> io::Result<Entries> {
use self::SaveResult::*;
match self {
Full(entries) | Partial(entries, _) => Ok(entries),
Error(error) => Err(error),
}
}
}
struct ContentType {
val: Mime,
#[allow(dead_code)]
boundary: Option<String>,
}
impl ContentType {
fn read_from(line: &str) -> Option<ContentType> {
const CONTENT_TYPE: &'static str = "Content-Type:";
const BOUNDARY: &'static str = "boundary=\"";
debug!("Reading Content-Type header from line: {:?}", line);
if let Some((cont_type, after_cont_type)) = get_str_after(CONTENT_TYPE, ';', line) {
let content_type = read_content_type(cont_type.trim());
let boundary = get_str_after(BOUNDARY, '"', after_cont_type).map(|tup| tup.0.into());
Some(ContentType {
val: content_type,
boundary: boundary,
})
} else {
get_remainder_after(CONTENT_TYPE, line).map(|cont_type| {
let content_type = read_content_type(cont_type.trim());
ContentType { val: content_type, boundary: None }
})
}
}
}
fn read_content_type(cont_type: &str) -> Mime {
cont_type.parse().ok().unwrap_or_else(::mime_guess::octet_stream)
}
struct ContentDisp {
field_name: String,
filename: Option<String>,
}
impl ContentDisp {
fn read_from(line: &str) -> Option<ContentDisp> {
debug!("Reading Content-Disposition from line: {:?}", line);
if line.is_empty() {
return None;
}
const CONT_DISP: &'static str = "Content-Disposition:";
const NAME: &'static str = "name=\"";
const FILENAME: &'static str = "filename=\"";
let after_disp_type = {
let (disp_type, after_disp_type) = try_opt!(get_str_after(CONT_DISP, ';', line));
let disp_type = disp_type.trim();
if disp_type != "form-data" {
error!("Unexpected Content-Disposition value: {:?}", disp_type);
return None;
}
after_disp_type
};
let (field_name, after_field_name) = try_opt!(get_str_after(NAME, '"', after_disp_type));
let filename = get_str_after(FILENAME, '"', after_field_name)
.map(|(filename, _)| filename.to_owned());
Some(ContentDisp { field_name: field_name.to_owned(), filename: filename })
}
}
fn get_str_after<'a>(needle: &str, end_val_delim: char, haystack: &'a str) -> Option<(&'a str, &'a str)> {
let val_start_idx = try_opt!(haystack.find(needle)) + needle.len();
let val_end_idx = try_opt!(haystack[val_start_idx..].find(end_val_delim)) + val_start_idx;
Some((&haystack[val_start_idx..val_end_idx], &haystack[val_end_idx..]))
}
fn get_remainder_after<'a>(needle: &str, haystack: &'a str) -> Option<(&'a str)> {
let val_start_idx = try_opt!(haystack.find(needle)) + needle.len();
Some(&haystack[val_start_idx..])
}
pub trait HttpRequest {
type Body: Read;
fn multipart_boundary(&self) -> Option<&str>;
fn body(self) -> Self::Body;
}
#[derive(Debug)]
pub struct MultipartField<'a, B: 'a> {
pub name: String,
pub data: MultipartData<'a, B>,
}
impl<'a, B: Read + 'a> MultipartField<'a, B> {
fn read_from(multipart: &'a mut Multipart<B>) -> io::Result<Option<MultipartField<'a, B>>> {
let cont_disp = match multipart.read_content_disposition() {
Ok(Some(cont_disp)) => cont_disp,
Ok(None) => return Ok(None),
Err(err) => return Err(err),
};
let data = match try!(multipart.read_content_type()) {
Some(content_type) => {
let _ = try!(multipart.read_line()); MultipartData::File(
MultipartFile::from_stream(
cont_disp.filename,
content_type.val,
&mut multipart.source,
)
)
},
None => {
let text = try!(multipart.read_to_string());
MultipartData::Text(&text)
},
};
Ok(Some(
MultipartField {
name: cont_disp.field_name,
data: data,
}
))
}
}
#[derive(Debug)]
pub enum MultipartData<'a, B: 'a> {
Text(&'a str),
File(MultipartFile<'a, B>),
}
impl<'a, B> MultipartData<'a, B> {
pub fn as_text(&self) -> Option<&str> {
match *self {
MultipartData::Text(ref s) => Some(s),
_ => None,
}
}
pub fn as_file(&mut self) -> Option<&mut MultipartFile<'a, B>> {
match *self {
MultipartData::File(ref mut file) => Some(file),
_ => None,
}
}
}
#[derive(Debug)]
pub struct MultipartFile<'a, B: 'a> {
filename: Option<String>,
content_type: Mime,
stream: &'a mut BoundaryReader<B>,
}
impl<'a, B: Read> MultipartFile<'a, B> {
fn from_stream(filename: Option<String>,
content_type: Mime,
stream: &'a mut BoundaryReader<B>) -> MultipartFile<'a, B> {
MultipartFile {
filename: filename,
content_type: content_type,
stream: stream,
}
}
pub fn save_to<W: Write>(&mut self, mut out: W) -> io::Result<u64> {
retry_on_interrupt(|| io::copy(self.stream, &mut out))
}
pub fn save_to_limited<W: Write>(&mut self, mut out: W, limit: u64) -> io::Result<u64> {
retry_on_interrupt(|| io::copy(&mut self.stream.take(limit), &mut out))
}
pub fn save_as<P: Into<PathBuf>>(&mut self, path: P) -> io::Result<SavedFile> {
let path = path.into();
let file = try!(create_full_path(&path));
let size = try!(self.save_to(file));
Ok(SavedFile {
path: path,
filename: self.filename.clone(),
size: size,
})
}
pub fn save_in<P: AsRef<Path>>(&mut self, dir: P) -> io::Result<SavedFile> {
let path = dir.as_ref().join(::random_alphanumeric(RANDOM_FILENAME_LEN));
self.save_as(path)
}
pub fn save_as_limited<P: Into<PathBuf>>(&mut self, path: P, limit: u64) -> io::Result<SavedFile> {
let path = path.into();
let file = try!(create_full_path(&path));
let size = try!(self.save_to_limited(file, limit));
Ok(SavedFile {
path: path,
filename: self.filename.clone(),
size: size,
})
}
pub fn save_in_limited<P: AsRef<Path>>(&mut self, dir: P, limit: u64) -> io::Result<SavedFile> {
let path = dir.as_ref().join(::random_alphanumeric(RANDOM_FILENAME_LEN));
self.save_as_limited(path, limit)
}
pub fn filename(&self) -> Option<&str> {
self.filename.as_ref().map(String::as_ref)
}
pub fn content_type(&self) -> &Mime {
&self.content_type
}
}
impl<'a, B: Read> Read for MultipartFile<'a, B> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>{
self.stream.read(buf)
}
}
impl<'a, B: Read> BufRead for MultipartFile<'a, B> {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
self.stream.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.stream.consume(amt)
}
}
#[derive(Debug)]
pub struct Entries {
pub fields: HashMap<String, String>,
pub files: HashMap<String, SavedFile>,
pub dir: SaveDir,
}
impl Entries {
fn new_tempdir_in<P: AsRef<Path>>(path: P) -> io::Result<Self> {
TempDir::new_in(path, "multipart").map(Self::with_tempdir)
}
fn new_tempdir() -> io::Result<Self> {
TempDir::new("multipart").map(Self::with_tempdir)
}
fn with_tempdir(tempdir: TempDir) -> Entries {
Entries {
fields: HashMap::new(),
files: HashMap::new(),
dir: SaveDir::Temp(tempdir),
}
}
}
pub enum SaveDir {
Temp(TempDir),
Perm(PathBuf),
}
impl SaveDir {
pub fn as_path(&self) -> &Path {
use self::SaveDir::*;
match *self {
Temp(ref tempdir) => tempdir.path(),
Perm(ref pathbuf) => &*pathbuf,
}
}
pub fn is_temporary(&self) -> bool {
use self::SaveDir::*;
match *self {
Temp(_) => true,
Perm(_) => false,
}
}
pub fn into_path(self) -> PathBuf {
use self::SaveDir::*;
match self {
Temp(tempdir) => tempdir.into_path(),
Perm(pathbuf) => pathbuf,
}
}
pub fn keep(&mut self) {
use self::SaveDir::*;
*self = match mem::replace(self, Perm(PathBuf::new())) {
Temp(tempdir) => Perm(tempdir.into_path()),
old_self => old_self,
};
}
pub fn delete(self) -> io::Result<()> {
use self::SaveDir::*;
match self {
Temp(tempdir) => tempdir.close(),
Perm(pathbuf) => fs::remove_dir_all(&pathbuf),
}
}
}
impl AsRef<Path> for SaveDir {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl fmt::Debug for SaveDir {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::SaveDir::*;
match *self {
Temp(ref tempdir) => write!(f, "SaveDir::Temp({:?})", tempdir.path()),
Perm(ref path) => write!(f, "SaveDir::Perm({:?})", path),
}
}
}
#[derive(Debug)]
pub struct SavedFile {
pub path: PathBuf,
pub filename: Option<String>,
pub size: u64,
}
fn retry_on_interrupt<F, T>(mut do_fn: F) -> io::Result<T> where F: FnMut() -> io::Result<T> {
loop {
match do_fn() {
Ok(val) => return Ok(val),
Err(err) => if err.kind() != io::ErrorKind::Interrupted {
return Err(err);
},
}
}
}
fn prepend_str(prefix: &str, mut string: String) -> String {
string.reserve(prefix.len());
unsafe {
let bytes = string.as_mut_vec();
let old_len = bytes.len();
let new_len = bytes.len() + prefix.len();
bytes.set_len(new_len);
ptr::copy(bytes.as_ptr(), bytes[prefix.len()..].as_mut_ptr(), old_len);
ptr::copy(prefix.as_ptr(), bytes.as_mut_ptr(), prefix.len());
}
string
}
fn create_full_path(path: &Path) -> io::Result<File> {
if let Some(parent) = path.parent() {
try!(fs::create_dir_all(parent));
} else {
warn!("Attempting to save file in what looks like a root directory. File path: {:?}", path);
}
File::create(&path)
}