use bytes::Bytes;
pub use tempfile::NamedTempFile;
use std::collections::HashMap;
use std::io::{Cursor, Write};
use std::path::{Path, PathBuf};
#[cfg(feature = "v1")]
pub(crate) use actix_web_v1 as actix_web;
#[cfg(feature = "v1")]
pub(crate) use actix_multipart_v01 as actix_multipart;
#[cfg(feature = "v1")]
pub mod v1;
#[cfg(feature = "v2")]
pub(crate) use actix_web_v2 as actix_web;
#[cfg(feature = "v2")]
pub(crate) use actix_multipart_v02 as actix_multipart;
#[cfg(feature = "v2")]
#[path = "v2_3.rs"]
pub mod v2;
#[cfg(feature = "v3")]
pub(crate) use actix_web_v3 as actix_web;
#[cfg(feature = "v3")]
pub(crate) use actix_multipart_v03 as actix_multipart;
#[cfg(feature = "v3")]
#[path = "v2_3.rs"]
pub mod v3;
#[cfg(feature = "v4")]
pub(crate) use actix_web_v4 as actix_web;
#[cfg(feature = "v4")]
pub(crate) use actix_multipart_v04 as actix_multipart;
#[cfg(feature = "v4")]
#[path = "v4.rs"]
pub mod v4;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
TempFilePersistError(tempfile::PersistError),
FileTooLarge { limit: usize, file_name: Option<String> },
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Io(ref x) => x.fmt(f),
Error::TempFilePersistError(ref x) => x.fmt(f),
Error::FileTooLarge { limit, ref file_name } => {
if let Some(ref file_name) = file_name {
write!(f, "File is too large (limit: {} bytes): {}", limit, file_name)
} else {
write!(f, "File is too large (limit: {} bytes)", limit)
}
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(ref x) => Some(x),
Error::TempFilePersistError(ref x) => Some(x),
_ => None,
}
}
}
#[derive(Debug)]
pub struct Parts {
pub texts: TextParts,
pub files: FileParts,
}
#[derive(Debug)]
pub struct TextParts(Vec<(String, Bytes)>);
#[derive(Debug)]
pub struct FileParts(Vec<(String, Result<File, Error>)>);
#[derive(Debug)]
pub struct File {
inner: NamedTempFile,
original_file_name: Option<String>,
sanitized_file_name: String,
}
impl AsRef<NamedTempFile> for File {
fn as_ref(&self) -> &NamedTempFile {
&self.inner
}
}
impl AsMut<NamedTempFile> for File {
fn as_mut(&mut self) -> &mut NamedTempFile {
&mut self.inner
}
}
impl TextParts {
pub fn into_inner(self) -> Vec<(String, Bytes)> {
self.0
}
pub fn as_pairs(&self) -> Vec<(&str, &str)> {
self.0
.iter()
.flat_map(|(key, val)| std::str::from_utf8(val).map(|val| (key.as_str(), val)))
.collect()
}
pub fn to_query_string(&self) -> String {
let mut qs = url::form_urlencoded::Serializer::new(String::new());
for (key, val) in self
.0
.iter()
.flat_map(|(key, val)| std::str::from_utf8(val).map(|val| (key.as_str(), val)))
{
qs.append_pair(key, val);
}
qs.finish()
}
pub fn as_hash_map(&self) -> HashMap<&str, &str> {
self.as_pairs().into_iter().collect()
}
}
impl FileParts {
pub fn into_inner(self) -> Vec<(String, Result<File, Error>)> {
self.0
}
pub fn first(&self, key: &str) -> Option<&File> {
self.0.iter().filter(|(k, _)| k.as_str() == key).flat_map(|(_, v)| v.as_ref()).next()
}
#[deprecated(note = "Please use `take` instead")]
pub fn remove(&mut self, key: &str) -> Vec<File> {
self.take(key)
}
pub fn take(&mut self, key: &str) -> Vec<File> {
let mut taken = Vec::with_capacity(self.0.len());
let mut untaken = Vec::with_capacity(self.0.len());
for (k, v) in self.0.drain(..) {
if k == key && v.is_ok() {
taken.push(v.unwrap());
} else {
untaken.push((k, v));
}
}
self.0 = untaken;
taken
}
}
impl File {
pub fn into_inner(self) -> NamedTempFile {
self.inner
}
pub fn original_file_name(&self) -> Option<&str> {
self.original_file_name.as_deref()
}
pub fn sanitized_file_name(&self) -> &str {
&self.sanitized_file_name
}
#[deprecated(since = "0.5.4", note = "Please use the 'persist_in' function instead")]
pub fn persist<P: AsRef<Path>>(self, dir: P) -> Result<PathBuf, Error> {
let new_path = dir.as_ref().join(&self.sanitized_file_name);
self.inner.persist(&new_path).map(|_| new_path).map_err(Error::TempFilePersistError)
}
pub fn persist_in<P: AsRef<Path>>(self, dir: P) -> Result<PathBuf, Error> {
let new_path = dir.as_ref().join(&self.sanitized_file_name);
self.inner.persist(&new_path).map(|_| new_path).map_err(Error::TempFilePersistError)
}
pub fn persist_at<P: AsRef<Path>>(self, path: P) -> Result<std::fs::File, Error> {
self.inner.persist(path).map_err(Error::TempFilePersistError)
}
pub fn new(
file: NamedTempFile,
original_file_name: Option<String>,
mime_type: Option<&mime::Mime>,
) -> Self {
let sanitized_file_name = match original_file_name {
Some(ref s) => sanitize_filename::sanitize(s),
None => {
let uuid = uuid::Uuid::new_v4().to_simple();
match mime_type
.and_then(|mt| mime_guess::get_mime_extensions(mt))
.and_then(|x| x.first())
{
Some(ext) => format!("{}.{}", uuid, ext),
None => uuid.to_string(),
}
}
};
File { inner: file, sanitized_file_name, original_file_name }
}
pub fn new_with_file_name(file: NamedTempFile, original_file_name: String) -> Self {
Self::new(file, Some(original_file_name), None)
}
}
#[cfg(unix)]
impl File {
pub fn persist_with_permissions<P: AsRef<Path>>(
self,
dir: P,
mode: u32,
) -> Result<PathBuf, Error> {
use std::os::unix::fs::PermissionsExt;
let permissions = std::fs::Permissions::from_mode(mode);
std::fs::set_permissions(self.inner.path(), permissions).map_err(Error::Io)?;
let new_path = dir.as_ref().join(&self.sanitized_file_name);
self.persist_in(&new_path)
}
pub fn persist_with_open_permissions<P: AsRef<Path>>(self, dir: P) -> Result<PathBuf, Error> {
self.persist_with_permissions(dir, 0o644)
}
}
#[cfg(not(feature = "v4"))]
#[derive(Default, Debug, Clone)]
pub struct PartsConfig {
text_limit: Option<usize>,
file_limit: Option<usize>,
file_fields: Option<Vec<String>>,
text_fields: Option<Vec<String>>,
temp_dir: Option<PathBuf>,
}
#[cfg(not(feature = "v4"))]
impl PartsConfig {
pub fn with_text_limit(mut self, text_limit: usize) -> Self {
self.text_limit = Some(text_limit);
self
}
pub fn with_file_limit(mut self, file_limit: usize) -> Self {
self.file_limit = Some(file_limit);
self
}
pub fn with_file_fields(mut self, file_fields: Vec<String>) -> Self {
self.file_fields = Some(file_fields);
self
}
pub fn with_text_fields(mut self, text_fields: Vec<String>) -> Self {
self.text_fields = Some(text_fields);
self
}
pub fn with_temp_dir<I: Into<PathBuf>>(mut self, temp_dir: I) -> Self {
self.temp_dir = Some(temp_dir.into());
self
}
}
#[cfg(feature = "v4")]
pub use v4::PartsConfig;
#[derive(Debug)]
enum Part {
Text(Bytes),
File(Result<File, Error>),
}
#[derive(Debug)]
enum Buffer {
Cursor(Cursor<Vec<u8>>),
File(NamedTempFile),
}
struct FileTooLarge {
limit: usize,
}
#[cfg(test)]
mod test {
use tempfile::NamedTempFile;
use crate::File;
use std::{io::Write, iter};
#[test]
pub fn create_file_with_size() {
let file_name = "my name jeff.txt".to_string();
let sanitized_file_name = sanitize_filename::sanitize(file_name.clone());
let size = 20usize;
let expected_content = iter::repeat(1u8).take(size).collect::<Vec<_>>();
let mut name_tempfile = NamedTempFile::new().expect("Failed creating temp file.");
name_tempfile.write_all(&expected_content).expect("Failed writing to file.");
let file = File::new_with_file_name(name_tempfile, file_name.clone());
let tempfile_path = file.inner.path().to_path_buf();
let content = std::fs::read(tempfile_path).expect("Can not read temporary file");
assert_eq!(expected_content, content);
assert_eq!(file.original_file_name, Some(file_name));
assert_eq!(file.sanitized_file_name, sanitized_file_name);
}
#[test]
pub fn create_file_then_persist() {
let file_name = "create_file_then_persist.txt".to_string();
let file_path = std::env::temp_dir();
let size = 20usize;
let expected_content = iter::repeat(1u8).take(size).collect::<Vec<_>>();
let mut tempfile = NamedTempFile::new().expect("Failed creating temp file.");
tempfile.write_all(&expected_content).expect("Failed writing to file.");
let file = File::new_with_file_name(tempfile, file_name.clone());
file.persist_in(&file_path).expect("Failed persisting file");
let content = std::fs::read(format!("{}/{}", file_path.to_string_lossy(), file_name))
.expect("Can not read temporary file");
assert_eq!(expected_content, content);
}
}