use std::fmt;
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
use mime_guess;
use crate::error::Result;
use crate::http::plus::random_alphanumeric;
const BOUNDARY_LEN: usize = 32;
fn gen_boundary() -> String {
random_alphanumeric(BOUNDARY_LEN)
}
use crate::http::mime::{self, Mime};
#[derive(Debug, Default)]
pub struct Multipart<'a> {
fields: Vec<Field<'a>>,
}
impl<'a> Multipart<'a> {
#[inline]
pub fn new() -> Multipart<'a> {
Multipart { fields: Vec::new() }
}
#[inline]
pub fn add_text<V>(&mut self, name: V, value: V) -> &mut Self
where
V: Into<String>,
{
let filed = Field {
name: name.into(),
data: Data::Text(value.into()),
};
self.fields.push(filed);
self
}
#[inline]
pub fn add_file<V, P>(&mut self, name: V, path: P) -> &mut Self
where
V: Into<String>,
P: Into<PathBuf>,
{
let filed = Field {
name: name.into(),
data: Data::File(path.into()),
};
self.fields.push(filed);
self
}
#[inline]
pub fn add_stream<V, R>(
&mut self,
name: V,
stream: R,
filename: Option<V>,
mime: Option<Mime>,
) -> &mut Self
where
R: Read + 'a,
V: Into<String>,
{
let filed = Field {
name: name.into(),
data: Data::Stream(Stream {
content_type: mime.unwrap_or(mime::APPLICATION_OCTET_STREAM),
filename: filename.map(|f| f.into()),
stream: Box::new(stream),
}),
};
self.fields.push(filed);
self
}
pub fn convert(&mut self) -> Result<(String, Vec<u8>)> {
let mut boundary = format!("\r\n--{}", gen_boundary());
let mut buf: Vec<u8> = Vec::new();
for field in self.fields.drain(..) {
match field.data {
Data::Text(value) => {
write!(
buf,
"{}\r\nContent-Disposition: form-data; name=\"{}\"\r\n\r\n{}",
boundary, field.name, value
)?;
}
Data::File(path) => {
let (content_type, filename) = mime_filename(&path);
let mut file = File::open(&path)?;
write!(
buf,
"{}\r\nContent-Disposition: form-data; name=\"{}\"",
boundary, field.name
)?;
if let Some(filename) = filename {
write!(buf, "; filename=\"{}\"", filename)?;
}
write!(buf, "\r\nContent-Type: {}\r\n\r\n", content_type)?;
let mut temp: Vec<u8> = Vec::new();
file.read_to_end(&mut temp)?;
buf.extend(temp);
}
Data::Stream(mut stream) => {
write!(
buf,
"{}\r\nContent-Disposition: form-data; name=\"{}\"",
boundary, field.name
)?;
if let Some(filename) = stream.filename {
write!(buf, "; filename=\"{}\"", filename)?;
}
write!(buf, "\r\nContent-Type: {}\r\n\r\n", stream.content_type)?;
let mut temp: Vec<u8> = Vec::new();
stream.stream.read_to_end(&mut temp)?;
buf.extend(temp);
}
}
}
boundary.push_str("--");
buf.extend(boundary.as_bytes());
Ok((boundary[4..boundary.len() - 2].to_string(), buf))
}
}
#[derive(Debug)]
struct Field<'a> {
name: String,
data: Data<'a>,
}
enum Data<'a> {
Text(String),
File(PathBuf),
Stream(Stream<'a>),
}
impl<'a> fmt::Debug for Data<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Data::Text(ref value) => write!(f, "Data::Text({:?})", value),
Data::File(ref path) => write!(f, "Data::File({:?})", path),
Data::Stream(_) => f.write_str("Data::Stream(Box<Read>)"),
}
}
}
struct Stream<'a> {
filename: Option<String>,
content_type: Mime,
stream: Box<Read + 'a>,
}
fn mime_filename(path: &PathBuf) -> (Mime, Option<&str>) {
let content_type = mime_guess::guess_mime_type(path);
let filename = opt_filename(path);
(content_type, filename)
}
fn opt_filename(path: &PathBuf) -> Option<&str> {
path.file_name().and_then(|filename| filename.to_str())
}