extern crate rocket;
extern crate multipart;
extern crate chrono;
use std::collections::HashMap;
use std::io::{self, Read, Write};
use std::path::{PathBuf, Path};
use std::sync::Arc;
use std::env;
use std::string;
use std::fs::{self, File};
use chrono::prelude::*;
use rocket::Data;
use rocket::http::ContentType;
pub use rocket::http::hyper::mime::{Mime, TopLevel};
use multipart::server::Multipart;
const BUFFER_SIZE: usize = 4096;
#[derive(Debug)]
pub struct MultipartFormDataOptions<'a> {
pub text_size_limit: u64,
pub file_size_limit: u64,
pub temporary_dir: PathBuf,
pub allowed_text_fields: Vec<&'a str>,
pub allowed_file_fields: Vec<&'a str>,
}
#[derive(Debug)]
pub struct MultipartFormData {
pub files: HashMap<Arc<String>, FileField>,
pub texts: HashMap<Arc<String>, TextField>,
}
#[derive(Debug)]
pub struct SingleFileField {
pub content_type: Option<Mime>,
pub file_name: Option<String>,
pub path: PathBuf,
}
#[derive(Debug)]
pub enum FileField {
Single(SingleFileField),
Multiple(Vec<SingleFileField>),
}
#[derive(Debug)]
pub struct SingleTextField {
pub content_type: Option<Mime>,
pub file_name: Option<String>,
pub text: String,
}
#[derive(Debug)]
pub enum TextField {
Single(SingleTextField),
Multiple(Vec<SingleTextField>),
}
#[derive(Debug)]
pub enum MultipartFormDataError {
NotFormDataError,
BoundaryNotFoundError,
BodySizeTooLargeError,
IOError(io::Error),
FieldTypeAmbiguousError,
FromUtf8Error(string::FromUtf8Error),
DataTooLargeError(Arc<String>),
}
impl<'a> MultipartFormDataOptions<'a> {
pub fn new() -> MultipartFormDataOptions<'a> {
MultipartFormDataOptions {
text_size_limit: 1 * 1024 * 1024,
file_size_limit: 8 * 1024 * 1024,
temporary_dir: env::temp_dir(),
allowed_text_fields: Vec::new(),
allowed_file_fields: Vec::new(),
}
}
}
impl MultipartFormData {
pub fn parse(content_type: &ContentType, data: Data, mut options: MultipartFormDataOptions) -> Result<MultipartFormData, MultipartFormDataError> {
if !content_type.is_form_data() {
return Err(MultipartFormDataError::NotFormDataError);
}
let (_, boundary) = match content_type.params().find(|&(k, _)| k == "boundary") {
Some(s) => s,
None => return Err(MultipartFormDataError::BoundaryNotFoundError)
};
options.allowed_file_fields.sort();
options.allowed_text_fields.sort();
let mut multipart = Multipart::with_body(data.open(), boundary);
let mut files = HashMap::new();
let mut texts = HashMap::new();
if !files.is_empty() {
let path = options.temporary_dir.as_path();
if path.exists() {
if !path.is_dir() {
return Err(MultipartFormDataError::IOError(io::Error::new(io::ErrorKind::AlreadyExists, "the temporary path exists and it is not a directory")));
}
} else {
fs::create_dir_all(path).map_err(|err| MultipartFormDataError::IOError(err))?;
}
}
loop {
match multipart.read_entry().map_err(|err| MultipartFormDataError::IOError(err))? {
Some(entry) => {
let field_name = entry.headers.name;
if let Ok(vi) = options.allowed_text_fields.binary_search(&field_name.as_str()) {
let mut data = entry.data;
let mut buffer = [0u8; BUFFER_SIZE];
let mut text_buffer = Vec::new();
loop {
let c = data.read(&mut buffer).map_err(|err| MultipartFormDataError::IOError(err))?;
if c == 0 {
break;
}
if text_buffer.len() as u64 + c as u64 > options.text_size_limit {
return Err(MultipartFormDataError::DataTooLargeError(field_name));
}
text_buffer.extend_from_slice(&buffer[..c]);
}
let text = String::from_utf8(text_buffer).map_err(|err| MultipartFormDataError::FromUtf8Error(err))?;
let content_type = entry.headers.content_type;
let file_name = entry.headers.filename;
let f = SingleTextField {
content_type,
file_name,
text,
};
options.allowed_text_fields.remove(vi);
match texts.remove(&field_name) {
Some(field) => {
match field {
TextField::Single(t) => {
let v = vec![t, f];
texts.insert(field_name, TextField::Multiple(v));
}
TextField::Multiple(mut v) => {
v.push(f);
texts.insert(field_name, TextField::Multiple(v));
}
}
}
None => {
texts.insert(field_name, TextField::Single(f));
}
}
} else if let Ok(vi) = options.allowed_file_fields.binary_search(&field_name.as_str()) {
let mut data = entry.data;
let mut buffer = [0u8; BUFFER_SIZE];
let now = Utc::now();
let target_file_name = format!("rs-{}", now.timestamp_nanos());
let target_path = {
let mut i = 0usize;
let mut p;
loop {
p = if i == 0 {
Path::join(&options.temporary_dir, &target_file_name)
} else {
Path::join(&options.temporary_dir, format!("{}-{}", &target_file_name, i))
};
if !p.exists() {
break;
}
i += 1;
}
p
};
let mut file = File::create(&target_path).map_err(|err| MultipartFormDataError::IOError(err))?;
let mut sum_c = 0u64;
loop {
let c = data.read(&mut buffer).map_err(|err| {
try_delete(&target_path);
MultipartFormDataError::IOError(err)
})?;
if c == 0 {
break;
}
sum_c += c as u64;
if sum_c > options.file_size_limit {
try_delete(&target_path);
return Err(MultipartFormDataError::DataTooLargeError(field_name));
}
file.write(&buffer[..c]).map_err(|err| {
try_delete(&target_path);
MultipartFormDataError::IOError(err)
})?;
}
let content_type = entry.headers.content_type;
let file_name = entry.headers.filename;
let f = SingleFileField {
content_type,
file_name,
path: target_path,
};
options.allowed_text_fields.remove(vi);
match files.remove(&field_name) {
Some(field) => {
match field {
FileField::Single(t) => {
let v = vec![t, f];
files.insert(field_name, FileField::Multiple(v));
}
FileField::Multiple(mut v) => {
v.push(f);
files.insert(field_name, FileField::Multiple(v));
}
}
}
None => {
files.insert(field_name, FileField::Single(f));
}
}
}
}
None => {
break;
}
}
}
Ok(MultipartFormData {
files,
texts,
})
}
}
impl Drop for MultipartFormData {
fn drop(&mut self) {
let files = &self.files;
for (_, field) in files {
match field {
FileField::Single(f) => {
try_delete(&f.path);
}
FileField::Multiple(vf) => {
for f in vf {
try_delete(&f.path);
}
}
}
}
}
}
fn try_delete<P: AsRef<Path>>(path: P) {
if let Err(_) = fs::remove_file(path.as_ref()) {}
}