use std::borrow::Cow;
use std::fmt;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::path::Path;
use mime_guess::{self, Mime};
use super::Body;
use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
use crate::header::HeaderMap;
pub struct Form {
inner: FormParts<Part>,
}
pub struct Part {
meta: PartMetadata,
value: Body,
}
impl Default for Form {
fn default() -> Self {
Self::new()
}
}
impl Form {
pub fn new() -> Form {
Form {
inner: FormParts::new(),
}
}
#[inline]
pub fn boundary(&self) -> &str {
self.inner.boundary()
}
pub fn text<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
where
T: Into<Cow<'static, str>>,
U: AsRef<Path>,
{
Ok(self.part(name, Part::file(path)?))
}
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
pub fn percent_encode_path_segment(self) -> Form {
self.with_inner(|inner| inner.percent_encode_path_segment())
}
pub fn percent_encode_attr_chars(self) -> Form {
self.with_inner(|inner| inner.percent_encode_attr_chars())
}
pub fn percent_encode_noop(self) -> Form {
self.with_inner(|inner| inner.percent_encode_noop())
}
pub(crate) fn reader(self) -> Reader {
Reader::new(self)
}
pub(crate) fn compute_length(&mut self) -> Option<u64> {
self.inner.compute_length()
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
}
impl fmt::Debug for Form {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt_fields("Form", f)
}
}
impl Part {
pub fn text<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(string) => Body::from(string),
};
Part::new(body)
}
pub fn bytes<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(vec) => Body::from(vec),
};
Part::new(body)
}
pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
Part::new(Body::new(value))
}
pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
Part::new(Body::sized(value, length))
}
pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
let path = path.as_ref();
let file_name = path
.file_name()
.map(|filename| filename.to_string_lossy().into_owned());
let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
let mime = mime_guess::from_ext(ext).first_or_octet_stream();
let file = File::open(path)?;
let field = Part::new(Body::from(file)).mime(mime);
Ok(if let Some(file_name) = file_name {
field.file_name(file_name)
} else {
field
})
}
fn new(value: Body) -> Part {
Part {
meta: PartMetadata::new(),
value,
}
}
pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
}
fn mime(self, mime: Mime) -> Part {
self.with_inner(move |inner| inner.mime(mime))
}
pub fn file_name<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.file_name(filename))
}
pub fn headers(self, headers: HeaderMap) -> Part {
self.with_inner(move |inner| inner.headers(headers))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(PartMetadata) -> PartMetadata,
{
Part {
meta: func(self.meta),
value: self.value,
}
}
}
impl fmt::Debug for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Part");
dbg.field("value", &self.value);
self.meta.fmt_fields(&mut dbg);
dbg.finish()
}
}
impl PartProps for Part {
fn value_len(&self) -> Option<u64> {
self.value.len()
}
fn metadata(&self) -> &PartMetadata {
&self.meta
}
}
pub(crate) struct Reader {
form: Form,
active_reader: Option<Box<dyn Read + Send>>,
}
impl fmt::Debug for Reader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Reader").field("form", &self.form).finish()
}
}
impl Reader {
fn new(form: Form) -> Reader {
let mut reader = Reader {
form,
active_reader: None,
};
reader.next_reader();
reader
}
fn next_reader(&mut self) {
self.active_reader = if !self.form.inner.fields.is_empty() {
let (name, field) = self.form.inner.fields.remove(0);
let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
let header = Cursor::new({
let mut h = if !self.form.inner.computed_headers.is_empty() {
self.form.inner.computed_headers.remove(0)
} else {
self.form
.inner
.percent_encoding
.encode_headers(&name, field.metadata())
};
h.extend_from_slice(b"\r\n\r\n");
h
});
let reader = boundary
.chain(header)
.chain(field.value.into_reader())
.chain(Cursor::new("\r\n"));
if !self.form.inner.fields.is_empty() {
Some(Box::new(reader))
} else {
Some(Box::new(reader.chain(Cursor::new(format!(
"--{}--\r\n",
self.form.boundary()
)))))
}
} else {
None
}
}
}
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut total_bytes_read = 0usize;
let mut last_read_bytes;
loop {
match self.active_reader {
Some(ref mut reader) => {
last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
total_bytes_read += last_read_bytes;
if total_bytes_read == buf.len() {
return Ok(total_bytes_read);
}
}
None => return Ok(total_bytes_read),
};
if last_read_bytes == 0 && !buf.is_empty() {
self.next_reader();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn form_empty() {
let mut output = Vec::new();
let mut form = Form::new();
let length = form.compute_length();
form.reader().read_to_end(&mut output).unwrap();
assert_eq!(output, b"");
assert_eq!(length.unwrap(), 0);
}
#[test]
fn read_to_end() {
let mut output = Vec::new();
let mut form = Form::new()
.part("reader1", Part::reader(std::io::empty()))
.part("key1", Part::text("value1"))
.part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
.part("reader2", Part::reader(std::io::empty()))
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let length = form.compute_length();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{}\nEND EXPECTED", expected);
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
assert!(length.is_none());
}
#[test]
fn read_to_end_with_length() {
let mut output = Vec::new();
let mut form = Form::new()
.text("key1", "value1")
.part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let length = form.compute_length();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{}\nEND EXPECTED", expected);
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
assert_eq!(length.unwrap(), expected.len() as u64);
}
#[test]
fn read_to_end_with_header() {
let mut output = Vec::new();
let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
let mut headers = HeaderMap::new();
headers.insert("Hdr3", "/a/b/c".parse().unwrap());
part = part.headers(headers);
let mut form = Form::new().part("key2", part);
form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\
hdr3: /a/b/c\r\n\
\r\n\
value2\r\n\
--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{}\nEND EXPECTED", expected);
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
}
}