datatrash/src/multipart.rs

189 lines
6.4 KiB
Rust
Raw Normal View History

use crate::{config, file_kind::FileKind};
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};
use async_std::{fs::File, path::Path, prelude::*};
2020-07-09 17:27:24 +00:00
use chrono::{prelude::*, Duration};
use futures::{StreamExt, TryStreamExt};
2020-07-08 19:26:46 +00:00
2021-08-18 21:22:50 +00:00
const MAX_UPLOAD_SECONDS: u64 = 31 * 24 * 60 * 60;
const DEFAULT_UPLOAD_SECONDS: u64 = 30 * 60;
pub(crate) struct UploadConfig {
pub original_name: String,
pub valid_till: DateTime<Local>,
pub kind: FileKind,
pub delete_on_download: bool,
}
2020-07-09 17:27:24 +00:00
pub(crate) async fn parse_multipart(
mut payload: Multipart,
file_id: &str,
2021-09-11 00:08:47 +00:00
file_name: &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 keep_for: Option<String> = None;
2020-07-09 17:27:24 +00:00
let mut kind: Option<FileKind> = None;
let mut delete_on_download = false;
let mut password = None;
let mut size = 0;
2020-07-09 17:27:24 +00:00
while let Ok(Some(field)) = payload.try_next().await {
let name = get_field_name(&field)?;
let name = name.as_str();
match name {
"keep_for" => {
keep_for = Some(parse_string(name, field).await?);
2020-07-09 17:27:24 +00:00
}
"file" => {
2020-07-09 17:27:24 +00:00
let file_original_name = get_original_filename(&field);
if file_original_name == None || file_original_name.as_deref() == Some("") {
continue;
}
original_name = file_original_name;
kind = Some(FileKind::Binary);
2021-09-11 00:08:47 +00:00
size = create_file(file_name, field, config.max_file_size).await?;
2020-07-09 17:27:24 +00:00
}
"text" => {
2020-07-09 17:27:24 +00:00
if original_name.is_some() {
continue;
}
original_name = Some(format!("{}.txt", file_id));
kind = Some(FileKind::Text);
2021-09-11 00:08:47 +00:00
size = create_file(file_name, field, config.max_file_size).await?;
2020-07-09 17:27:24 +00:00
}
"delete_on_download" => {
delete_on_download = dbg!(parse_string(name, field).await?) != "false";
}
"password" => {
password = Some(parse_string(name, field).await?);
}
2020-07-09 17:27:24 +00:00
_ => {}
};
}
let original_name = original_name.ok_or_else(|| error::ErrorBadRequest("no content found"))?;
let kind = kind.ok_or_else(|| error::ErrorBadRequest("no content found"))?;
2020-07-09 17:27:24 +00:00
if original_name.len() > 255 {
return Err(error::ErrorBadRequest("filename is too long"));
}
let validated_keep_for: u64 = if let Some(keep_for) = keep_for {
let seconds = keep_for.parse().map_err(|e| {
error::ErrorBadRequest(format!("field keep_for is not a number: {}", e))
})?;
2021-08-18 21:22:50 +00:00
if seconds > MAX_UPLOAD_SECONDS {
return Err(error::ErrorBadRequest(format!(
"maximum allowed validity is {} seconds, but you specified {} seconds",
2021-08-18 21:22:50 +00:00
MAX_UPLOAD_SECONDS, seconds
)));
}
seconds
} else {
2021-08-18 21:22:50 +00:00
DEFAULT_UPLOAD_SECONDS
};
2021-08-18 21:22:50 +00:00
let valid_duration = Duration::seconds(validated_keep_for as i64);
let valid_till = Local::now() + valid_duration;
2021-08-18 21:22:50 +00:00
check_auth_requirements(size, valid_duration, password, config)?;
Ok(UploadConfig {
original_name,
valid_till,
kind,
delete_on_download,
})
2020-07-09 17:27:24 +00:00
}
fn check_auth_requirements(
size: u64,
2021-08-18 21:22:50 +00:00
validated_keep_for: Duration,
password: Option<String>,
config: &config::Config,
) -> Result<(), error::Error> {
if let Some(no_auth_limits) = &config.no_auth_limits {
let requires_auth = validated_keep_for > no_auth_limits.max_time
|| validated_keep_for > no_auth_limits.large_file_max_time
&& size > no_auth_limits.large_file_size;
if requires_auth && password.as_ref() != Some(&no_auth_limits.auth_password) {
return Err(error::ErrorBadRequest(
"upload requires authentication, but authentication was incorrect",
));
}
}
Ok(())
}
2020-07-09 17:27:24 +00:00
fn get_field_name(field: &Field) -> Result<String, error::Error> {
Ok(field
2020-07-08 19:26:46 +00:00
.content_disposition()
2020-12-03 22:30:37 +00:00
.ok_or(error::ParseError::Incomplete)?
2020-07-08 19:26:46 +00:00
.get_name()
.map(|s| s.to_owned())
2020-12-03 22:30:37 +00:00
.ok_or(error::ParseError::Incomplete)?)
2020-07-08 19:26:46 +00:00
}
2020-07-09 17:27:24 +00:00
async fn parse_string(name: &str, field: actix_multipart::Field) -> Result<String, error::Error> {
let data = read_content(field).await?;
String::from_utf8(data)
.map_err(|_| error::ErrorBadRequest(format!("could not parse field {} as utf-8", name)))
}
async fn read_content(mut field: actix_multipart::Field) -> Result<Vec<u8>, error::Error> {
2020-07-08 19:26:46 +00:00
let mut data = Vec::new();
while let Some(chunk) = field.next().await {
2020-07-09 17:27:24 +00:00
data.extend(chunk.map_err(error::ErrorBadRequest)?);
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, Error> {
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")
})?;
let written_bytes = write_to_file(&mut file, field, max_file_size).await?;
Ok(written_bytes)
}
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, error::Error> {
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)?;
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!(
"exceeded maximum file size of {} bytes",
max_size
)));
}
}
file.write_all(chunk.as_ref()).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)
2020-07-09 17:27:24 +00:00
}
fn get_original_filename(field: &actix_multipart::Field) -> Option<String> {
field.content_disposition().and_then(|content_disposition| {
content_disposition
.parameters
.into_iter()
.find_map(|param| match param {
DispositionParam::Filename(filename) => Some(filename),
_ => None,
})
})
2020-07-08 19:26:46 +00:00
}