extern crate buf_redux;
extern crate httparse;
extern crate memchr;
use tempdir::TempDir;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::fs;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::{io, mem, ptr};
use self::boundary::BoundaryReader;
use self::field::FieldHeaders;
pub use self::field::{MultipartField, MultipartFile, MultipartData, SavedFile};
macro_rules! try_opt (
($expr:expr) => (
match $expr {
Some(val) => val,
None => return None,
}
);
($expr:expr, $before_ret:expr) => (
match $expr {
Some(val) => val,
None => {
$before_ret;
return None;
}
}
)
);
mod boundary;
mod field;
#[cfg(feature = "hyper")]
pub mod hyper;
#[cfg(feature = "iron")]
pub mod iron;
#[cfg(feature = "nickel")]
pub mod nickel;
#[cfg(feature = "tiny_http")]
pub mod tiny_http;
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);
}
self::field::read_field(self)
}
fn read_field_headers(&mut self) -> io::Result<Option<FieldHeaders>> {
FieldHeaders::parse(&mut self.source)
}
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),
}
}
}
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, None) {
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, None) {
Ok(()) => SaveResult::Full(entries),
Err(err) => SaveResult::Partial(entries, err),
}
}
pub fn save_all_limited(&mut self, limit: u64) -> SaveResult {
let mut entries = match Entries::new_tempdir() {
Ok(entries) => entries,
Err(err) => return SaveResult::Error(err),
};
match self.read_to_entries(&mut entries, Some(limit)) {
Ok(()) => SaveResult::Full(entries),
Err(err) => SaveResult::Partial(entries, err),
}
}
pub fn save_all_under_limited<P: AsRef<Path>>(&mut self, dir: P, limit: u64) -> 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, Some(limit)) {
Ok(()) => SaveResult::Full(entries),
Err(err) => SaveResult::Partial(entries, err),
}
}
fn read_to_entries(&mut self, entries: &mut Entries, limit: Option<u64>) -> io::Result<()> {
while let Some(field) = try!(self.read_entry()) {
match field.data {
MultipartData::File(mut file) => {
let file = if let Some(limit) = limit {
try!(file.save_in_limited(&entries.dir, limit))
} else {
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_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> {
debug!("Consume boundary!");
try!(self.source.consume_boundary());
let mut out = [0; 2];
let _ = try!(self.source.read(&mut out));
if *b"\r\n" == out {
Ok(true)
} else {
if *b"--" != out {
warn!("Unexpected 2-bytes after boundary: {:?}", out);
}
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),
}
}
}
pub trait HttpRequest {
type Body: Read;
fn multipart_boundary(&self) -> Option<&str>;
fn body(self) -> Self::Body;
}
#[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),
}
}
}
#[derive(Debug)]
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()
}
}
#[cfg(feature = "nightly")]
fn prepend_str(prefix: &str, mut string: String) -> String {
string.insert_str(0, prefix);
string
}
#[cfg(not(feature = "nightly"))]
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
}