don't reread file to perform mime guess

This commit is contained in:
neri 2022-10-25 20:55:29 +02:00
parent 7983557c5a
commit 38f487c6cb
1 changed files with 21 additions and 15 deletions

View File

@ -3,7 +3,10 @@ use actix_multipart::{Field, Multipart};
use actix_web::{error, http::header::DispositionParam, Error}; use actix_web::{error, http::header::DispositionParam, Error};
use futures_util::{StreamExt, TryStreamExt}; use futures_util::{StreamExt, TryStreamExt};
use mime::{Mime, APPLICATION_OCTET_STREAM, TEXT_PLAIN}; use mime::{Mime, APPLICATION_OCTET_STREAM, TEXT_PLAIN};
use std::path::Path; use std::{
cmp::{max, min},
path::Path,
};
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
@ -42,10 +45,10 @@ pub(crate) async fn parse_multipart(
continue; continue;
} }
original_name = uploaded_name; original_name = uploaded_name;
size = create_file(file_path, field, config.max_file_size).await?; let first_bytes;
println!("mime: {}", mime); (size, first_bytes) = create_file(file_path, field, config.max_file_size).await?;
content_type = Some(if mime == APPLICATION_OCTET_STREAM { content_type = Some(if mime == APPLICATION_OCTET_STREAM {
get_content_type(file_path) get_content_type(&first_bytes)
} else { } else {
mime_relations::get_alias(mime) mime_relations::get_alias(mime)
}); });
@ -54,8 +57,9 @@ pub(crate) async fn parse_multipart(
if original_name.is_some() { if original_name.is_some() {
continue; continue;
} }
size = create_file(file_path, field, config.max_file_size).await?; let first_bytes;
content_type = Some(get_content_type(file_path)); (size, first_bytes) = create_file(file_path, field, config.max_file_size).await?;
content_type = Some(get_content_type(&first_bytes));
} }
"delete_on_download" => { "delete_on_download" => {
delete_on_download = parse_string(name, field).await? != "false"; delete_on_download = parse_string(name, field).await? != "false";
@ -149,23 +153,25 @@ async fn create_file(
filename: &Path, filename: &Path,
field: Field, field: Field,
max_file_size: Option<u64>, max_file_size: Option<u64>,
) -> Result<u64, Error> { ) -> Result<(u64, Vec<u8>), Error> {
let mut file = File::create(&filename).await.map_err(|file_err| { let mut file = File::create(&filename).await.map_err(|file_err| {
log::error!("could not create file {:?}", file_err); log::error!("could not create file {:?}", file_err);
error::ErrorInternalServerError("could not create file") error::ErrorInternalServerError("could not create file")
})?; })?;
let written_bytes = write_to_file(&mut file, field, max_file_size).await?; write_to_file(&mut file, field, max_file_size).await
Ok(written_bytes)
} }
async fn write_to_file( async fn write_to_file(
file: &mut File, file: &mut File,
mut field: Field, mut field: Field,
max_size: Option<u64>, max_size: Option<u64>,
) -> Result<u64, error::Error> { ) -> Result<(u64, Vec<u8>), error::Error> {
let mut first_bytes = Vec::with_capacity(2048);
let mut written_bytes: u64 = 0; let mut written_bytes: u64 = 0;
while let Some(chunk) = field.next().await { while let Some(chunk) = field.next().await {
let chunk = chunk.map_err(error::ErrorBadRequest)?; 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; written_bytes += chunk.len() as u64;
if let Some(max_size) = max_size { if let Some(max_size) = max_size {
if written_bytes > max_size { if written_bytes > max_size {
@ -179,7 +185,7 @@ async fn write_to_file(
error::ErrorInternalServerError("could not write file") error::ErrorInternalServerError("could not write file")
})?; })?;
} }
Ok(written_bytes) Ok((written_bytes, first_bytes))
} }
fn get_file_metadata(field: &actix_multipart::Field) -> (Mime, Option<String>) { fn get_file_metadata(field: &actix_multipart::Field) -> (Mime, Option<String>) {
@ -195,9 +201,9 @@ fn get_file_metadata(field: &actix_multipart::Field) -> (Mime, Option<String>) {
(mime, filename) (mime, filename)
} }
fn get_content_type(path: &Path) -> Mime { fn get_content_type(bytes: &[u8]) -> Mime {
let std_path = std::path::Path::new(path.as_os_str()); tree_magic_mini::from_u8(bytes)
tree_magic_mini::from_filepath(std_path) .parse()
.and_then(|mime| mime.parse().ok()) .ok()
.unwrap_or(TEXT_PLAIN) .unwrap_or(TEXT_PLAIN)
} }