datatrash/src/multipart.rs

210 lines
6.9 KiB
Rust
Raw Normal View History

use crate::{config, mime_relations};
2020-07-09 17:27:24 +00:00
use actix_multipart::{Field, Multipart};
2021-09-11 00:08:47 +00:00
use actix_web::{error, http::header::DispositionParam, Error};
2022-02-26 23:34:57 +00:00
use futures_util::{StreamExt, TryStreamExt};
use mime::{Mime, APPLICATION_OCTET_STREAM, TEXT_PLAIN};
use std::{
cmp::{max, min},
path::Path,
};
2022-07-02 20:28:48 +00:00
use time::{Duration, OffsetDateTime};
2022-02-26 23:34:57 +00:00
use tokio::{fs::File, io::AsyncWriteExt};
2020-07-08 19:26:46 +00:00
2022-09-30 12:38:33 +00:00
const MAX_UPLOAD_DURATION: Duration = Duration::days(31);
const DEFAULT_UPLOAD_DURATION: Duration = Duration::minutes(30);
2021-08-18 21:22:50 +00:00
pub(crate) struct UploadConfig {
pub original_name: Option<String>,
pub content_type: Mime,
2022-02-27 00:50:29 +00:00
pub valid_till: OffsetDateTime,
pub delete_on_download: bool,
}
2020-07-09 17:27:24 +00:00
pub(crate) async fn parse_multipart(
mut payload: Multipart,
file_path: &Path,
config: &config::Config,
) -> Result<UploadConfig, error::Error> {
2020-07-09 17:27:24 +00:00
let mut original_name: Option<String> = None;
let mut content_type: Option<Mime> = None;
2022-09-30 12:38:33 +00:00
let mut keep_for_seconds: Option<String> = None;
let mut delete_on_download = false;
let mut password = None;
let mut size = 0;
2020-07-09 17:27:24 +00:00
2022-11-04 10:37:10 +00:00
while let Ok(Some(mut field)) = payload.try_next().await {
let name = get_field_name(&field)?.to_owned();
match name.as_str() {
"keep_for" => {
2022-11-04 10:37:10 +00:00
keep_for_seconds = Some(parse_string(&name, &mut field).await?);
2020-07-09 17:27:24 +00:00
}
"file" => {
let (mime, uploaded_name) = get_file_metadata(&field);
2022-11-04 10:37:10 +00:00
if uploaded_name.is_none() || uploaded_name.as_deref() == Some("") {
2020-07-09 17:27:24 +00:00
continue;
}
original_name = uploaded_name;
let first_bytes;
(size, first_bytes) = create_file(file_path, field, config.max_file_size).await?;
content_type = Some(if mime == APPLICATION_OCTET_STREAM {
get_content_type(&first_bytes)
} else {
2022-10-07 13:52:12 +00:00
mime_relations::get_alias(mime)
});
2020-07-09 17:27:24 +00:00
}
"text" => {
2020-07-09 17:27:24 +00:00
if original_name.is_some() {
continue;
}
let first_bytes;
(size, first_bytes) = create_file(file_path, field, config.max_file_size).await?;
content_type = Some(get_content_type(&first_bytes));
2020-07-09 17:27:24 +00:00
}
"delete_on_download" => {
2022-11-04 10:37:10 +00:00
delete_on_download = parse_string(&name, &mut field).await? != "false";
}
"password" => {
2022-11-04 10:37:10 +00:00
password = Some(parse_string(&name, &mut field).await?);
}
2020-07-09 17:27:24 +00:00
_ => {}
};
}
let content_type =
content_type.ok_or_else(|| error::ErrorBadRequest("no content type found"))?;
2022-09-30 12:38:33 +00:00
let keep_for = keep_for_seconds
2022-02-27 00:50:29 +00:00
.map(|k| k.parse())
.transpose()
2022-10-07 13:52:12 +00:00
.map_err(|e| error::ErrorBadRequest(format!("field keep_for is not a number: {e}")))?
2022-11-04 10:37:10 +00:00
.map_or(DEFAULT_UPLOAD_DURATION, Duration::seconds);
2022-09-30 12:38:33 +00:00
let valid_till = OffsetDateTime::now_utc() + keep_for;
let upload_config = UploadConfig {
original_name,
content_type,
valid_till,
delete_on_download,
};
2022-11-04 10:37:10 +00:00
check_requirements(&upload_config, size, &password, &keep_for, config)?;
Ok(upload_config)
2020-07-09 17:27:24 +00:00
}
fn check_requirements(
upload_config: &UploadConfig,
size: u64,
2022-11-04 10:37:10 +00:00
password: &Option<String>,
2022-09-30 12:38:33 +00:00
keep_for: &Duration,
config: &config::Config,
) -> Result<(), error::Error> {
if let Some(original_name) = upload_config.original_name.as_ref() {
if original_name.len() > 255 {
return Err(error::ErrorBadRequest("filename is too long"));
}
}
2022-09-30 12:38:33 +00:00
if *keep_for > MAX_UPLOAD_DURATION {
return Err(error::ErrorBadRequest(format!(
2022-10-07 13:52:12 +00:00
"maximum allowed validity is {MAX_UPLOAD_DURATION}, but you specified {keep_for}"
)));
}
if let Some(no_auth_limits) = &config.no_auth_limits {
2022-09-30 12:38:33 +00:00
let requires_auth = *keep_for > no_auth_limits.max_time
|| *keep_for > no_auth_limits.large_file_max_time
&& size > no_auth_limits.large_file_size;
// hIGh sECUriTy paSsWoRD CHEck
if requires_auth && password.as_ref() != Some(&no_auth_limits.auth_password) {
return Err(error::ErrorBadRequest(
"upload requires authentication, but authentication was incorrect",
));
}
}
Ok(())
}
2022-11-04 10:37:10 +00:00
fn get_field_name(field: &Field) -> Result<&str, error::Error> {
2020-07-09 17:27:24 +00:00
Ok(field
2020-07-08 19:26:46 +00:00
.content_disposition()
.get_name()
2020-12-03 22:30:37 +00:00
.ok_or(error::ParseError::Incomplete)?)
2020-07-08 19:26:46 +00:00
}
2022-11-04 10:37:10 +00:00
async fn parse_string(
name: &str,
field: &mut actix_multipart::Field,
) -> Result<String, error::Error> {
2020-07-09 17:27:24 +00:00
let data = read_content(field).await?;
String::from_utf8(data)
2022-10-07 13:52:12 +00:00
.map_err(|_| error::ErrorBadRequest(format!("could not parse field {name} as utf-8")))
2020-07-09 17:27:24 +00:00
}
2022-11-04 10:37:10 +00:00
async fn read_content(field: &mut actix_multipart::Field) -> Result<Vec<u8>, error::Error> {
2020-07-08 19:26:46 +00:00
let mut data = Vec::new();
2022-02-26 23:34:57 +00:00
while let Some(chunk) = field.try_next().await.map_err(error::ErrorBadRequest)? {
data.extend(chunk);
2020-07-08 19:26:46 +00:00
}
2020-07-09 17:27:24 +00:00
Ok(data)
}
2021-09-11 00:08:47 +00:00
async fn create_file(
filename: &Path,
field: Field,
max_file_size: Option<u64>,
) -> Result<(u64, Vec<u8>), Error> {
2021-09-11 00:08:47 +00:00
let mut file = File::create(&filename).await.map_err(|file_err| {
log::error!("could not create file {:?}", file_err);
error::ErrorInternalServerError("could not create file")
})?;
write_to_file(&mut file, field, max_file_size).await
2021-09-11 00:08:47 +00:00
}
2020-07-09 17:27:24 +00:00
async fn write_to_file(
file: &mut File,
2021-09-11 00:08:47 +00:00
mut field: Field,
2021-03-09 22:36:24 +00:00
max_size: Option<u64>,
) -> Result<(u64, Vec<u8>), error::Error> {
let mut first_bytes = Vec::with_capacity(2048);
2021-03-09 22:36:24 +00:00
let mut written_bytes: u64 = 0;
2020-07-09 17:27:24 +00:00
while let Some(chunk) = field.next().await {
2021-03-09 22:36:24 +00:00
let chunk = chunk.map_err(error::ErrorBadRequest)?;
let remaining_first_bytes = min(max(0, 2048 - written_bytes) as usize, chunk.len());
first_bytes.extend_from_slice(&chunk[0..remaining_first_bytes]);
written_bytes += chunk.len() as u64;
2021-03-09 22:36:24 +00:00
if let Some(max_size) = max_size {
if written_bytes > max_size {
return Err(error::ErrorBadRequest(format!(
2022-10-07 13:52:12 +00:00
"exceeded maximum file size of {max_size} bytes"
2021-03-09 22:36:24 +00:00
)));
}
}
file.write_all(&chunk).await.map_err(|write_err| {
log::error!("could not write file {:?}", write_err);
error::ErrorInternalServerError("could not write file")
})?;
2020-07-09 17:27:24 +00:00
}
Ok((written_bytes, first_bytes))
2020-07-09 17:27:24 +00:00
}
fn get_file_metadata(field: &actix_multipart::Field) -> (Mime, Option<String>) {
let mime = field.content_type().clone();
let filename = field
2022-02-26 23:34:57 +00:00
.content_disposition()
.parameters
2022-02-26 23:34:57 +00:00
.iter()
.find_map(|param| match param {
DispositionParam::Filename(filename) => Some(filename.clone()),
_ => None,
});
(mime, filename)
}
fn get_content_type(bytes: &[u8]) -> Mime {
tree_magic_mini::from_u8(bytes)
.parse()
.ok()
.unwrap_or(TEXT_PLAIN)
2020-07-08 19:26:46 +00:00
}