126 lines
4.6 KiB
Rust
126 lines
4.6 KiB
Rust
use crate::file_kind::FileKind;
|
|
use actix_multipart::{Field, Multipart};
|
|
use actix_web::{error, http::header::DispositionParam};
|
|
use async_std::{fs, fs::File, path::Path, prelude::*};
|
|
use chrono::{prelude::*, Duration};
|
|
use futures::{StreamExt, TryStreamExt};
|
|
|
|
pub(crate) async fn parse_multipart(
|
|
mut payload: Multipart,
|
|
file_id: &str,
|
|
filename: &Path,
|
|
) -> Result<(Option<String>, DateTime<Local>, FileKind), error::Error> {
|
|
let mut original_name: Option<String> = None;
|
|
let mut timeout: Option<String> = None;
|
|
let mut kind: Option<FileKind> = None;
|
|
|
|
while let Ok(Some(field)) = payload.try_next().await {
|
|
let name = get_field_name(&field)?;
|
|
let name = name.as_str();
|
|
match name {
|
|
"validity_secs" => {
|
|
timeout = Some(parse_string(name, field).await?);
|
|
}
|
|
"content" => {
|
|
let file_original_name = get_original_filename(&field);
|
|
if file_original_name == None || file_original_name.as_deref() == Some("") {
|
|
continue;
|
|
}
|
|
println!("got content");
|
|
original_name = file_original_name;
|
|
kind = Some(FileKind::BINARY);
|
|
let mut file = fs::File::create(&filename)
|
|
.await
|
|
.map_err(|_| error::ErrorInternalServerError("could not create file"))?;
|
|
write_to_file(&mut file, field)
|
|
.await
|
|
.map_err(|_| error::ErrorInternalServerError("could not write file"))?;
|
|
}
|
|
"text_content" => {
|
|
if original_name.is_some() {
|
|
continue;
|
|
}
|
|
println!("got text content");
|
|
original_name = Some(format!("{}.txt", file_id));
|
|
kind = Some(FileKind::TEXT);
|
|
let mut file = fs::File::create(&filename)
|
|
.await
|
|
.map_err(|_| error::ErrorInternalServerError("could not create file"))?;
|
|
write_to_file(&mut file, field)
|
|
.await
|
|
.map_err(|_| error::ErrorInternalServerError("could not write file"))?;
|
|
}
|
|
_ => {}
|
|
};
|
|
}
|
|
|
|
if let Some(original_name) = &original_name {
|
|
if original_name.len() > 255 {
|
|
return Err(error::ErrorBadRequest("filename is too long"));
|
|
}
|
|
}
|
|
|
|
let validity_secs = timeout
|
|
.ok_or_else(|| error::ErrorBadRequest("field validity_secs not set"))?
|
|
.parse()
|
|
.map_err(|e| {
|
|
error::ErrorBadRequest(format!("field validity_secs is not a number: {}", e))
|
|
})?;
|
|
let max_validity_secs = Duration::days(31).num_seconds();
|
|
if validity_secs > max_validity_secs {
|
|
return Err(error::ErrorBadRequest(format!(
|
|
"maximum allowed validity is {} seconds, but you specified {} seconds",
|
|
max_validity_secs, validity_secs
|
|
)));
|
|
}
|
|
let valid_till = Local::now() + Duration::seconds(validity_secs);
|
|
let kind = kind.ok_or_else(|| error::ErrorBadRequest("no content found"))?;
|
|
Ok((original_name, valid_till, kind))
|
|
}
|
|
|
|
fn get_field_name(field: &Field) -> Result<String, error::Error> {
|
|
Ok(field
|
|
.content_disposition()
|
|
.ok_or_else(|| error::ParseError::Incomplete)?
|
|
.get_name()
|
|
.map(|s| s.to_owned())
|
|
.ok_or_else(|| error::ParseError::Incomplete)?)
|
|
}
|
|
|
|
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> {
|
|
let mut data = Vec::new();
|
|
while let Some(chunk) = field.next().await {
|
|
data.extend(chunk.map_err(error::ErrorBadRequest)?);
|
|
}
|
|
Ok(data)
|
|
}
|
|
|
|
async fn write_to_file(
|
|
file: &mut File,
|
|
mut field: actix_multipart::Field,
|
|
) -> Result<(), error::Error> {
|
|
while let Some(chunk) = field.next().await {
|
|
file.write_all(chunk.map_err(error::ErrorBadRequest)?.as_ref())
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
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,
|
|
})
|
|
})
|
|
}
|