#![allow(clippy::borrow_interior_mutable_const)]
use crate::CRLF;
use http::header;
use mime::{self, Mime};
use std::{
fmt::Display,
io::{Cursor, Read},
};
pub(crate) struct Part<'a> {
inner: Inner<'a>,
content_type: String,
content_disposition: String,
}
impl<'a> Part<'a> {
pub(crate) fn new<N, F>(
inner: Inner<'a>,
name: N,
mime: Option<Mime>,
filename: Option<F>,
) -> Part
where
N: Display,
F: Display,
{
let mut disposition_params = vec![format!("name=\"{}\"", name)];
if let Some(filename) = filename {
disposition_params.push(format!("filename=\"{}\"", filename));
}
let content_type = format!("{}", mime.unwrap_or_else(|| inner.default_content_type()));
Part {
inner,
content_type,
content_disposition: format!("form-data; {}", disposition_params.join("; ")),
}
}
#[inline]
fn headers_string(&self) -> String {
#[cfg(feature = "part-content-length")]
let content_length = match self.inner.len() {
Some(len) => format!("{}{}: {}", CRLF, header::CONTENT_LENGTH.as_str(), len),
None => String::new(),
};
#[cfg(not(feature = "part-content-length"))]
let content_length = "";
format!(
"{}: {}{}{}: {}{}{}{}",
header::CONTENT_DISPOSITION.as_str(),
self.content_disposition,
CRLF,
header::CONTENT_TYPE.as_str(),
self.content_type,
content_length,
CRLF,
CRLF
)
}
pub(crate) fn into_reader(self) -> impl Read + 'a {
let cursor = Cursor::new(self.headers_string());
let inner = match self.inner {
Inner::Text(string) => Box::new(Cursor::new(string.into_bytes())),
Inner::Read(read, _) => read,
};
cursor.chain(inner).chain(Cursor::new(CRLF))
}
#[inline]
fn content_disposition_len(&self) -> u64 {
(header::CONTENT_DISPOSITION.as_str().len() + 2 + self.content_disposition.len() + 2) as u64
}
#[inline]
fn content_type_len(&self) -> u64 {
(header::CONTENT_TYPE.as_str().len() + 2 + self.content_type.len() + 2) as u64
}
#[inline]
fn content_length_len(&self) -> u64 {
#[cfg(feature = "part-content-length")]
return (header::CONTENT_LENGTH.as_str().len()
+ 2
+ self.inner.len().unwrap().to_string().len()
+ 2) as u64;
#[cfg(not(feature = "part-content-length"))]
0
}
#[inline]
pub(crate) fn content_length(&self) -> Option<u64> {
self.inner.len().map(|len| {
len + self.content_disposition_len()
+ self.content_length_len()
+ self.content_type_len()
+ 2
})
}
}
pub(crate) enum Inner<'a> {
Read(Box<'a + Read + Send>, Option<u64>),
Text(String),
}
impl<'a> Inner<'a> {
#[inline]
fn default_content_type(&self) -> Mime {
match *self {
Inner::Read(_, _) => mime::APPLICATION_OCTET_STREAM,
Inner::Text(_) => mime::TEXT_PLAIN,
}
}
#[inline]
fn len(&self) -> Option<u64> {
match *self {
Inner::Read(_, len) => len,
Inner::Text(ref s) => Some(s.len() as u64),
}
}
}
#[cfg(test)]
mod tests {
use super::{Inner, Part};
use std::io::{Cursor, Read};
#[test]
fn test_inner_text() {
let name = "hello";
let inner_content = "world";
let inner = Inner::Text(inner_content.to_string());
#[cfg(feature = "part-content-length")]
let test_string = "content-disposition: form-data; name=\"hello\"\r
content-type: text/plain\r
content-length: 5\r
\r
world\r
";
#[cfg(not(feature = "part-content-length"))]
let test_string = "content-disposition: form-data; name=\"hello\"\r
content-type: text/plain\r
\r
world\r
";
let test_string = test_string.to_string();
let part = Part::new::<_, &str>(inner, name, None, None);
let mut part_string = String::new();
part.into_reader().read_to_string(&mut part_string).unwrap();
assert_eq!(test_string, part_string);
}
#[test]
fn test_inner_read() {
let name = "hello";
let inner_content = "world";
let inner = Inner::Read(
Box::new(Cursor::new(inner_content.as_bytes())),
Some(inner_content.len() as u64),
);
#[cfg(feature = "part-content-length")]
let test_string = "content-disposition: form-data; name=\"hello\"\r
content-type: application/octet-stream\r
content-length: 5\r
\r
world\r
";
#[cfg(not(feature = "part-content-length"))]
let test_string = "content-disposition: form-data; name=\"hello\"\r
content-type: application/octet-stream\r
\r
world\r
";
let test_string = test_string.to_string();
let part = Part::new::<_, &str>(inner, name, None, None);
let mut part_string = String::new();
part.into_reader().read_to_string(&mut part_string).unwrap();
assert_eq!(test_string, part_string);
}
}