#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
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 http::header::{CONTENT_TYPE, CONTENT_DISPOSITION};
use crate::http::headers::{ContentType, ContentDisposition, HeaderMap, HeaderName, HeaderValue, Charset};
use tempdir::TempDir;
use textnonce::TextNonce;
use mime::{self, Mime};
use buf_read_ext::BufReadExt;
use super::error::Error;
#[derive(Clone, Debug, PartialEq)]
pub struct Part {
pub headers: HeaderMap,
pub body: Vec<u8>,
}
impl Part {
pub fn content_type(&self) -> Option<Mime> {
self.headers.get(CONTENT_TYPE).and_then(|ct|ct.to_str().ok()).and_then(|ct|ct.parse().ok())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FilePart {
pub headers: HeaderMap,
pub path: PathBuf,
pub size: Option<usize>,
tempdir: Option<PathBuf>,
}
impl FilePart {
pub fn new(headers: HeaderMap, 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: HeaderMap) -> Result<FilePart, Error> {
let mut path = TempDir::new("novel_http_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> {
match self.headers.get(&CONTENT_DISPOSITION) {
Some(cd) => get_content_disposition_filename(cd),
None => Ok(None),
}
}
pub fn content_type(&self) -> Option<Mime> {
self.headers.get(CONTENT_TYPE).and_then(|hv|hv.to_str().ok()).and_then(|hv|hv.parse().ok())
}
}
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((HeaderMap, 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) = 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 mut headers = HeaderMap::new();
match httparse::parse_headers(&buf, &mut header_memory) {
Ok(httparse::Status::Complete((_, raw_headers))) => {
for header in raw_headers {
let hn: Result<HeaderName, _> = header.name.parse();
if let Ok(hn) = hn{
if let Ok(hv) = HeaderValue::from_bytes(header.value){
headers.append(hn, hv);
}
}
}
},
Ok(httparse::Status::Partial) => return Err(Error::PartialHeaders),
Err(err) => return Err(From::from(err)),
};
inner(&mut reader, &headers, &mut nodes, always_use_files)?;
Ok(nodes)
}
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);
let mut nodes: Vec<Node> = Vec::new();
inner(&mut reader, headers, &mut nodes, always_use_files)?;
Ok(nodes)
}
fn inner<R: BufRead>(reader: &mut R, headers: &HeaderMap, nodes: &mut Vec<Node>, always_use_files: bool)
-> Result<(), Error>
{
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.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 = reader.fill_buf()?;
if peeker.len() >= 2 && &peeker[..2] == b"--" {
return Ok(());
}
let (_, found) = reader.stream_until_token(<, &mut buf)?;
if ! found { return Err(Error::NoCrLfAfterBoundary); }
buf.truncate(0); let (_, found) = reader.stream_until_token(<lt, &mut buf)?;
if ! found { return Err(Error::EofInPartHeaders); }
buf.extend(ltlt.iter().cloned());
let mut part_headers = HeaderMap::new();
let mut header_memory = [httparse::EMPTY_HEADER; 4];
match httparse::parse_headers(&buf, &mut header_memory) {
Ok(httparse::Status::Complete((_, raw_headers))) => {
for header in raw_headers {
let hn: Result<HeaderName, _> = header.name.parse();
if let Ok(hn) = hn {
if let Ok(hv) = HeaderValue::from_bytes(header.value){
part_headers.append(hn, hv);
}
}
}
},
Ok(httparse::Status::Partial) => return Err(Error::PartialHeaders),
Err(err) => return Err(From::from(err)),
}
let mm: Option<Mime> = part_headers.get(CONTENT_TYPE).and_then(|ct|ct.to_str().ok()).and_then(|ct|ct.parse().ok());
let nested = if let Some(mm) = mm {
mm.type_() == mime::MULTIPART
} else {
false
};
if nested {
let mut inner_nodes: Vec<Node> = Vec::new();
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 || part_headers.get(CONTENT_DISPOSITION).map(|cd|{
cd.to_str()
.unwrap_or("")
.split(';')
.next()
.expect("split always has at least 1 item")=="attchment"
}).unwrap_or(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.truncate(0); 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 ct = headers.get(CONTENT_TYPE).ok_or(Error::NoRequestContentType)?;
let mm: Option<Mime> = ct.to_str().ok().and_then(|str|str.parse().ok());
if let Some(mm) = mm {
if mm.type_() != mime::MULTIPART {
return Err(Error::NotMultipart);
}
}
Err(Error::BoundaryNotSpecified)
}
#[inline]
fn get_content_disposition_filename(cd: &HeaderValue) -> Result<Option<String>, Error> {
for part in cd.to_str().unwrap_or("").split(';'){
if part.trim().starts_with("filename=") {
return Ok(Some(part.trim().trim_start_matches("filename=").to_owned()));
}
}
Ok(None)
}
fn charset_decode(charset: &Charset, bytes: &[u8]) -> Result<String, Cow<'static, str>> {
Ok(match *charset {
Charset::Us_Ascii => all::ASCII.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_1 => all::ISO_8859_1.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_2 => all::ISO_8859_2.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_3 => all::ISO_8859_3.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_4 => all::ISO_8859_4.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_5 => all::ISO_8859_5.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_6 => all::ISO_8859_6.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_7 => all::ISO_8859_7.decode(bytes, DecoderTrap::Strict)?,
Charset::Iso_8859_8 => 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 => all::ISO_8859_10.decode(bytes, DecoderTrap::Strict)?,
Charset::Shift_Jis => return Err("Shift_Jis is not supported".into()),
Charset::Euc_Jp => 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 => 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 => all::BIG5_2003.decode(bytes, DecoderTrap::Strict)?,
Charset::Koi8_R => all::KOI8_R.decode(bytes, DecoderTrap::Strict)?,
Charset::Ext(ref s) => match &**s {
"UTF-8" => 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>
{
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 += stream.write_all_count(b"--")?;
count += stream.write_all_count(&boundary)?;
count += stream.write_all_count(b"\r\n")?;
match node {
&Node::Part(ref part) => {
for (name, value) in part.headers.iter() {
count += stream.write_all_count(name.as_str().as_bytes())?;
count += stream.write_all_count(b": ")?;
count += stream.write_all_count(value.as_bytes())?;
count += stream.write_all_count(b"\r\n")?;
}
count += stream.write_all_count(b"\r\n")?;
count += stream.write_all_count(&part.body)?;
},
&Node::File(ref filepart) => {
for (name, value) in filepart.headers.iter() {
count += stream.write_all_count(name.as_str().as_bytes())?;
count += stream.write_all_count(b": ")?;
count += stream.write_all_count(value.as_bytes())?;
count += stream.write_all_count(b"\r\n")?;
}
count += stream.write_all_count(b"\r\n")?;
let mut file = File::open(&filepart.path)?;
count += ::std::io::copy(&mut file, stream)? as usize;
},
&Node::Multipart((ref headers, ref subnodes)) => {
let boundary = get_multipart_boundary(headers)?;
for (name, value) in headers.iter() {
count += stream.write_all_count(name.as_str().as_bytes())?;
count += stream.write_all_count(b": ")?;
count += stream.write_all_count(value.as_bytes())?;
count += stream.write_all_count(b"\r\n")?;
}
count += stream.write_all_count(b"\r\n")?;
count += write_multipart(stream, &boundary, &subnodes)?;
},
}
count += stream.write_all_count(b"\r\n")?;
}
count += stream.write_all_count(b"--")?;
count += stream.write_all_count(&boundary)?;
count += stream.write_all_count(b"--")?;
Ok(count)
}
pub fn write_chunk<S: Write>(
stream: &mut S,
chunk: &[u8]) -> Result<(), ::std::io::Error>
{
write!(stream, "{:x}\r\n", chunk.len())?;
stream.write_all(chunk)?;
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 {
write_chunk(stream, b"--")?;
write_chunk(stream, &boundary)?;
write_chunk(stream, b"\r\n")?;
match node {
&Node::Part(ref part) => {
for (name, value) in part.headers.iter() {
write_chunk(stream, name.as_str().as_bytes())?;
write_chunk(stream, b": ")?;
write_chunk(stream, value.as_bytes())?;
write_chunk(stream, b"\r\n")?;
}
write_chunk(stream, b"\r\n")?;
write_chunk(stream, &part.body)?;
},
&Node::File(ref filepart) => {
for (name, value) in filepart.headers.iter() {
write_chunk(stream, name.as_str().as_bytes())?;
write_chunk(stream, b": ")?;
write_chunk(stream, value.as_bytes())?;
write_chunk(stream, b"\r\n")?;
}
write_chunk(stream, b"\r\n")?;
let metadata = ::std::fs::metadata(&filepart.path)?;
write!(stream, "{:x}\r\n", metadata.len())?;
let mut file = File::open(&filepart.path)?;
::std::io::copy(&mut file, stream)? as usize;
stream.write(b"\r\n")?;
},
&Node::Multipart((ref headers, ref subnodes)) => {
let boundary = get_multipart_boundary(headers)?;
for (name, value) in headers.iter() {
write_chunk(stream, name.as_str().as_bytes())?;
write_chunk(stream, b": ")?;
write_chunk(stream, value.as_bytes())?;
write_chunk(stream, b"\r\n")?;
}
write_chunk(stream, b"\r\n")?;
write_multipart_chunked(stream, &boundary, &subnodes)?;
},
}
write_chunk(stream, b"\r\n")?;
}
write_chunk(stream, b"--")?;
write_chunk(stream, &boundary)?;
write_chunk(stream, b"--")?;
write_chunk(stream, b"")?;
Ok(())
}