ensure file ids are unique

This commit is contained in:
neri 2021-09-11 02:08:47 +02:00
parent 63fff3adf3
commit c520b3d469
4 changed files with 55 additions and 24 deletions

View File

@ -36,7 +36,7 @@ pub(crate) async fn delete_by_id(
file_id: &str, file_id: &str,
files_dir: &Path, files_dir: &Path,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
delete_content(file_id, &files_dir).await?; delete_content(file_id, files_dir).await?;
sqlx::query("DELETE FROM files WHERE file_id = $1") sqlx::query("DELETE FROM files WHERE file_id = $1")
.bind(file_id) .bind(file_id)
.execute(db) .execute(db)

View File

@ -102,7 +102,7 @@ fn build_file_response(
} else { } else {
DispositionType::Inline DispositionType::Inline
}, },
parameters: get_disposition_params(&file_name), parameters: get_disposition_params(file_name),
}; };
let file = NamedFile::open(path) let file = NamedFile::open(path)
.map_err(|file_err| { .map_err(|file_err| {

View File

@ -1,7 +1,7 @@
use crate::{config, file_kind::FileKind}; use crate::{config, file_kind::FileKind};
use actix_multipart::{Field, Multipart}; use actix_multipart::{Field, Multipart};
use actix_web::{error, http::header::DispositionParam}; use actix_web::{error, http::header::DispositionParam, Error};
use async_std::{fs, fs::File, path::Path, prelude::*}; use async_std::{fs::File, path::Path, prelude::*};
use chrono::{prelude::*, Duration}; use chrono::{prelude::*, Duration};
use futures::{StreamExt, TryStreamExt}; use futures::{StreamExt, TryStreamExt};
@ -18,7 +18,7 @@ pub(crate) struct UploadConfig {
pub(crate) async fn parse_multipart( pub(crate) async fn parse_multipart(
mut payload: Multipart, mut payload: Multipart,
file_id: &str, file_id: &str,
filename: &Path, file_name: &Path,
config: &config::Config, config: &config::Config,
) -> Result<UploadConfig, error::Error> { ) -> Result<UploadConfig, error::Error> {
let mut original_name: Option<String> = None; let mut original_name: Option<String> = None;
@ -42,11 +42,7 @@ pub(crate) async fn parse_multipart(
} }
original_name = file_original_name; original_name = file_original_name;
kind = Some(FileKind::Binary); kind = Some(FileKind::Binary);
let mut file = fs::File::create(&filename).await.map_err(|file_err| { size = create_file(file_name, field, config.max_file_size).await?;
log::error!("could not create file {:?}", file_err);
error::ErrorInternalServerError("could not create file")
})?;
size = write_to_file(&mut file, field, config.max_file_size).await?;
} }
"text" => { "text" => {
if original_name.is_some() { if original_name.is_some() {
@ -54,11 +50,7 @@ pub(crate) async fn parse_multipart(
} }
original_name = Some(format!("{}.txt", file_id)); original_name = Some(format!("{}.txt", file_id));
kind = Some(FileKind::Text); kind = Some(FileKind::Text);
let mut file = fs::File::create(&filename).await.map_err(|file_err| { size = create_file(file_name, field, config.max_file_size).await?;
log::error!("could not create file {:?}", file_err);
error::ErrorInternalServerError("could not create file")
})?;
size = write_to_file(&mut file, field, config.max_file_size).await?;
} }
"delete_on_download" => { "delete_on_download" => {
delete_on_download = dbg!(parse_string(name, field).await?) != "false"; delete_on_download = dbg!(parse_string(name, field).await?) != "false";
@ -145,9 +137,22 @@ async fn read_content(mut field: actix_multipart::Field) -> Result<Vec<u8>, erro
Ok(data) Ok(data)
} }
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)
}
async fn write_to_file( async fn write_to_file(
file: &mut File, file: &mut File,
mut field: actix_multipart::Field, mut field: Field,
max_size: Option<u64>, max_size: Option<u64>,
) -> Result<u64, error::Error> { ) -> Result<u64, error::Error> {
let mut written_bytes: u64 = 0; let mut written_bytes: u64 = 0;

View File

@ -1,3 +1,4 @@
use std::io::ErrorKind;
use std::{cmp, vec}; use std::{cmp, vec};
use crate::config::Config; use crate::config::Config;
@ -6,7 +7,11 @@ use crate::multipart;
use crate::multipart::UploadConfig; use crate::multipart::UploadConfig;
use actix_multipart::Multipart; use actix_multipart::Multipart;
use actix_web::{error, web, Error, HttpResponse}; use actix_web::{error, web, Error, HttpResponse};
use async_std::{channel::Sender, fs}; use async_std::{
channel::Sender,
fs::{self, OpenOptions},
path::PathBuf,
};
use chrono::Duration; use chrono::Duration;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
@ -142,11 +147,12 @@ pub async fn upload(
expiry_watch_sender: web::Data<Sender<()>>, expiry_watch_sender: web::Data<Sender<()>>,
config: web::Data<Config>, config: web::Data<Config>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let file_id = gen_file_id(); let (file_id, file_name) = create_unique_file(&config).await.map_err(|file_err| {
let mut filename = config.files_dir.clone(); log::error!("could not create file {:?}", file_err);
filename.push(&file_id); error::ErrorInternalServerError("could not create file")
})?;
let parsed_multipart = multipart::parse_multipart(payload, &file_id, &filename, &config).await; let parsed_multipart = multipart::parse_multipart(payload, &file_id, &file_name, &config).await;
let UploadConfig { let UploadConfig {
original_name, original_name,
valid_till, valid_till,
@ -155,8 +161,8 @@ pub async fn upload(
} = match parsed_multipart { } = match parsed_multipart {
Ok(data) => data, Ok(data) => data,
Err(err) => { Err(err) => {
if filename.exists().await { if file_name.exists().await {
fs::remove_file(filename).await.map_err(|file_err| { fs::remove_file(file_name).await.map_err(|file_err| {
log::error!("could not remove file {:?}", file_err); log::error!("could not remove file {:?}", file_err);
error::ErrorInternalServerError( error::ErrorInternalServerError(
"could not parse multipart; could not remove file", "could not parse multipart; could not remove file",
@ -180,7 +186,7 @@ pub async fn upload(
.await; .await;
if let Err(db_err) = db_insert { if let Err(db_err) = db_insert {
log::error!("could not insert into datebase {:?}", db_err); log::error!("could not insert into datebase {:?}", db_err);
fs::remove_file(filename).await.map_err(|file_err| { fs::remove_file(file_name).await.map_err(|file_err| {
log::error!("could not remove file {:?}", file_err); log::error!("could not remove file {:?}", file_err);
error::ErrorInternalServerError( error::ErrorInternalServerError(
"could not insert file into database; could not remove file", "could not insert file into database; could not remove file",
@ -215,6 +221,26 @@ pub async fn upload(
.body(format!("{}\n", url))) .body(format!("{}\n", url)))
} }
async fn create_unique_file(
config: &web::Data<Config>,
) -> Result<(String, PathBuf), std::io::Error> {
loop {
let file_id = gen_file_id();
let mut file_name = config.files_dir.clone();
file_name.push(&file_id);
match OpenOptions::new()
.write(true)
.create_new(true)
.open(&file_name)
.await
{
Ok(_) => return Ok((file_id, file_name)),
Err(error) if error.kind() == ErrorKind::AlreadyExists => continue,
Err(error) => return Err(error),
}
}
}
fn gen_file_id() -> String { fn gen_file_id() -> String {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let mut id = String::with_capacity(5); let mut id = String::with_capacity(5);