Compare commits

..

No commits in common. "9f37657fa77ad83693a1a66c7edd44e8f1e6894b" and "1dc2fff0c1bffdc6dba8f5894f739a271fc35624" have entirely different histories.

11 changed files with 47 additions and 2199 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "datatrash" name = "datatrash"
version = "1.2.0" version = "1.1.9"
authors = ["neri"] authors = ["neri"]
edition = "2021" edition = "2021"

2125
mime.types

File diff suppressed because it is too large Load Diff

View File

@ -30,15 +30,18 @@ fn get_db_url() -> String {
let auth = if let Ok(user) = env::var("DATABASE_USER") { let auth = if let Ok(user) = env::var("DATABASE_USER") {
if let Ok(pass) = env::var("DATABASE_PASS") { if let Ok(pass) = env::var("DATABASE_PASS") {
format!("{user}:{pass}@") format!("{}:{}@", user, pass)
} else { } else {
format!("{user}@") format!("{}@", user)
} }
} else { } else {
String::new() String::new()
}; };
let host = env::var("DATABASE_HOST").unwrap_or_else(|_| "localhost".to_string()); format!(
let name = env::var("DATABASE_NAME").unwrap_or_else(|_| "datatrash".to_string()); "postgresql://{auth}{host}/{name}",
format!("postgresql://{auth}{host}/{name}") auth = auth,
host = env::var("DATABASE_HOST").unwrap_or_else(|_| "localhost".to_string()),
name = env::var("DATABASE_NAME").unwrap_or_else(|_| "datatrash".to_string())
)
} }

View File

@ -41,7 +41,8 @@ pub async fn download(
path.push(&file_id); path.push(&file_id);
let mime = Mime::from_str(&content_type).unwrap_or(APPLICATION_OCTET_STREAM); let mime = Mime::from_str(&content_type).unwrap_or(APPLICATION_OCTET_STREAM);
let mut response = match get_view_type(&req, &mime, &path, delete).await { let mime = mime_relations::get_alias(&mime);
let mut response = match get_view_type(&req, mime, &path, delete).await {
ViewType::Raw => build_file_response(false, &file_name, path, mime, &req).await, ViewType::Raw => build_file_response(false, &file_name, path, mime, &req).await,
ViewType::Download => build_file_response(true, &file_name, path, mime, &req).await, ViewType::Download => build_file_response(true, &file_name, path, mime, &req).await,
ViewType::Html => build_text_response(&path).await, ViewType::Html => build_text_response(&path).await,
@ -145,7 +146,7 @@ async fn build_file_response(
download: bool, download: bool,
file_name: &str, file_name: &str,
path: PathBuf, path: PathBuf,
mime: Mime, mime: &Mime,
req: &HttpRequest, req: &HttpRequest,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let content_disposition = ContentDisposition { let content_disposition = ContentDisposition {
@ -161,7 +162,7 @@ async fn build_file_response(
log::error!("file could not be read {:?}", file_err); log::error!("file could not be read {:?}", file_err);
error::ErrorInternalServerError("this file should be here but could not be found") error::ErrorInternalServerError("this file should be here but could not be found")
})? })?
.set_content_type(mime) .set_content_type(mime.clone())
.set_content_disposition(content_disposition); .set_content_disposition(content_disposition);
let mut response = file.into_response(req); let mut response = file.into_response(req);

View File

@ -1,26 +1,29 @@
use std::{ use std::{collections::HashMap, str::FromStr};
collections::{HashMap, VecDeque},
str::FromStr,
};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use mime::Mime; use mime::Mime;
lazy_static! { lazy_static! {
static ref ALIASES: HashMap<Mime, Mime> = load_mime_aliases(); static ref ALIASES: HashMap<Mime, Mime> = get_mime_aliases();
static ref PARENTS: Vec<(Mime, Mime)> = load_mime_parent_relations(); static ref PARENTS: Vec<(Mime, Mime)> = get_mime_parent_relations();
static ref EXTENSIONS: HashMap<Mime, &'static str> = load_mime_extensions();
} }
fn load_mime_aliases() -> HashMap<Mime, Mime> { fn get_mime_aliases() -> HashMap<Mime, Mime> {
tree_magic_db::aliases() tree_magic_db::aliases()
.lines() .lines()
.flat_map(|line| line.split_once(' ')) .flat_map(|line| line.split_once(' '))
.flat_map(|(alias, mime)| Some((Mime::from_str(alias).ok()?, Mime::from_str(mime).ok()?))) .flat_map(|(a, b)| Some((Mime::from_str(a).ok()?, Mime::from_str(b).ok()?)))
.collect() .collect()
} }
fn load_mime_parent_relations() -> Vec<(Mime, Mime)> { pub(crate) fn get_alias(mimetype: &Mime) -> &Mime {
match ALIASES.get(mimetype) {
Some(x) => x,
None => mimetype,
}
}
fn get_mime_parent_relations() -> Vec<(Mime, Mime)> {
tree_magic_db::subclasses() tree_magic_db::subclasses()
.lines() .lines()
.flat_map(|line| line.split_once(' ')) .flat_map(|line| line.split_once(' '))
@ -30,22 +33,6 @@ fn load_mime_parent_relations() -> Vec<(Mime, Mime)> {
.collect() .collect()
} }
fn load_mime_extensions() -> HashMap<Mime, &'static str> {
include_str!("../mime.types")
.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.map(|line| line.split_whitespace())
.flat_map(|mut elements| Some((Mime::from_str(elements.next()?).ok()?, elements.next()?)))
.flat_map(|(mime, extension)| {
Some((ALIASES.get(&mime).unwrap_or(&mime).clone(), extension))
})
.collect()
}
pub(crate) fn get_alias(mimetype: Mime) -> Mime {
ALIASES.get(&mimetype).cloned().unwrap_or(mimetype)
}
fn get_mime_parents(mimetype: &Mime) -> Vec<&Mime> { fn get_mime_parents(mimetype: &Mime) -> Vec<&Mime> {
PARENTS PARENTS
.iter() .iter()
@ -59,17 +46,3 @@ pub(crate) fn matches_text(mime: &Mime) -> bool {
} }
return get_mime_parents(mime).into_iter().any(matches_text); return get_mime_parents(mime).into_iter().any(matches_text);
} }
pub(crate) fn get_extension(mimetype: &Mime) -> Option<&'static str> {
let mut queue = VecDeque::new();
queue.push_back(mimetype);
dbg!(&*EXTENSIONS);
while let Some(mime) = queue.pop_front() {
dbg!(mime);
match EXTENSIONS.get(mimetype).copied() {
Some(ext) => return Some(ext),
None => queue.extend(get_mime_parents(mime)),
}
}
None
}

View File

@ -1,4 +1,4 @@
use crate::{config, mime_relations}; use crate::config;
use actix_multipart::{Field, Multipart}; 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};
@ -47,7 +47,7 @@ pub(crate) async fn parse_multipart(
content_type = Some(if mime == APPLICATION_OCTET_STREAM { content_type = Some(if mime == APPLICATION_OCTET_STREAM {
get_content_type(file_path) get_content_type(file_path)
} else { } else {
mime_relations::get_alias(mime) mime.clone()
}); });
} }
"text" => { "text" => {
@ -72,7 +72,7 @@ pub(crate) async fn parse_multipart(
let keep_for = keep_for_seconds let keep_for = keep_for_seconds
.map(|k| k.parse()) .map(|k| k.parse())
.transpose() .transpose()
.map_err(|e| error::ErrorBadRequest(format!("field keep_for is not a number: {e}")))? .map_err(|e| error::ErrorBadRequest(format!("field keep_for is not a number: {}", e)))?
.map(Duration::seconds) .map(Duration::seconds)
.unwrap_or(DEFAULT_UPLOAD_DURATION); .unwrap_or(DEFAULT_UPLOAD_DURATION);
let valid_till = OffsetDateTime::now_utc() + keep_for; let valid_till = OffsetDateTime::now_utc() + keep_for;
@ -104,7 +104,8 @@ fn check_requirements(
if *keep_for > MAX_UPLOAD_DURATION { if *keep_for > MAX_UPLOAD_DURATION {
return Err(error::ErrorBadRequest(format!( return Err(error::ErrorBadRequest(format!(
"maximum allowed validity is {MAX_UPLOAD_DURATION}, but you specified {keep_for}" "maximum allowed validity is {}, but you specified {}",
MAX_UPLOAD_DURATION, keep_for
))); )));
} }
@ -134,7 +135,7 @@ fn get_field_name(field: &Field) -> Result<String, error::Error> {
async fn parse_string(name: &str, field: actix_multipart::Field) -> Result<String, error::Error> { async fn parse_string(name: &str, field: actix_multipart::Field) -> Result<String, error::Error> {
let data = read_content(field).await?; let data = read_content(field).await?;
String::from_utf8(data) String::from_utf8(data)
.map_err(|_| error::ErrorBadRequest(format!("could not parse field {name} as utf-8"))) .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> { async fn read_content(mut field: actix_multipart::Field) -> Result<Vec<u8>, error::Error> {
@ -170,7 +171,8 @@ async fn write_to_file(
if let Some(max_size) = max_size { if let Some(max_size) = max_size {
if written_bytes > max_size { if written_bytes > max_size {
return Err(error::ErrorBadRequest(format!( return Err(error::ErrorBadRequest(format!(
"exceeded maximum file size of {max_size} bytes" "exceeded maximum file size of {} bytes",
max_size
))); )));
} }
} }

View File

@ -34,7 +34,7 @@ impl KeyExtractor for ForwardedPeerIpKeyExtractor {
.wait_time_from(DefaultClock::default().now()) .wait_time_from(DefaultClock::default().now())
.as_secs(); .as_secs();
( (
format!("too many requests, retry in {wait_time}s"), format!("too many requests, retry in {}s", wait_time),
ContentType::plaintext(), ContentType::plaintext(),
) )
} }

View File

@ -61,7 +61,7 @@ fn render_file_size(size: u64) -> String {
let magnitude = cmp::min((size as f64).log(1024.0) as u32, 5); let magnitude = cmp::min((size as f64).log(1024.0) as u32, 5);
let prefix = ["", "ki", "Mi", "Gi", "Ti", "Pi"][magnitude as usize]; let prefix = ["", "ki", "Mi", "Gi", "Ti", "Pi"][magnitude as usize];
let value = size / (1024_u64.pow(magnitude)); let value = size / (1024_u64.pow(magnitude));
format!("{value}{prefix}B") format!("{}{}B", value, prefix)
} }
fn render_duration(duration: Duration) -> String { fn render_duration(duration: Duration) -> String {
@ -88,8 +88,8 @@ fn render_duration(duration: Duration) -> String {
fn pluralize(number: i64, word: &str, suffix: &str) -> Option<String> { fn pluralize(number: i64, word: &str, suffix: &str) -> Option<String> {
match number { match number {
0 => None, 0 => None,
1 => Some(format!("{number} {word}")), 1 => Some(format!("{} {}", number, word)),
_ => Some(format!("{number} {word}{suffix}")), _ => Some(format!("{} {}{}", number, word, suffix)),
} }
} }

View File

@ -2,7 +2,7 @@ use std::io::ErrorKind;
use crate::config::Config; use crate::config::Config;
use crate::multipart::UploadConfig; use crate::multipart::UploadConfig;
use crate::{mime_relations, multipart, template}; use crate::{multipart, template};
use actix_files::NamedFile; use actix_files::NamedFile;
use actix_multipart::Multipart; use actix_multipart::Multipart;
use actix_web::http::header::LOCATION; use actix_web::http::header::LOCATION;
@ -64,12 +64,9 @@ pub async fn upload(
} }
}; };
let file_name = original_name.clone().unwrap_or_else(|| { let file_name = original_name
format!( .clone()
"{file_id}.{}", .unwrap_or_else(|| format!("{}.txt", file_id));
mime_relations::get_extension(&content_type).unwrap_or("txt")
)
});
let db_insert = sqlx::query( let db_insert = sqlx::query(
"INSERT INTO Files (file_id, file_name, content_type, valid_till, delete_on_download) \ "INSERT INTO Files (file_id, file_name, content_type, valid_till, delete_on_download) \
VALUES ($1, $2, $3, $4, $5)", VALUES ($1, $2, $3, $4, $5)",
@ -107,15 +104,15 @@ pub async fn upload(
let redirect = if let Some(original_name) = original_name.as_ref() { let redirect = if let Some(original_name) = original_name.as_ref() {
let encoded_name = urlencoding::encode(original_name); let encoded_name = urlencoding::encode(original_name);
format!("/upload/{file_id}/{encoded_name}") format!("/upload/{}/{}", file_id, encoded_name)
} else { } else {
format!("/upload/{file_id}") format!("/upload/{}", file_id)
}; };
let url = get_file_url(&req, &file_id, original_name.as_deref()); let url = get_file_url(&req, &file_id, original_name.as_deref());
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.insert_header((LOCATION, redirect)) .insert_header((LOCATION, redirect))
.body(format!("{url}\n"))) .body(format!("{}\n", url)))
} }
async fn create_unique_file( async fn create_unique_file(
@ -148,12 +145,11 @@ fn gen_file_id() -> String {
} }
fn get_file_url(req: &HttpRequest, id: &str, name: Option<&str>) -> String { fn get_file_url(req: &HttpRequest, id: &str, name: Option<&str>) -> String {
let host = template::get_host_url(req);
if let Some(name) = name { if let Some(name) = name {
let encoded_name = urlencoding::encode(name); let encoded_name = urlencoding::encode(name);
format!("{host}/{id}/{encoded_name}") format!("{}/{}/{}", template::get_host_url(req), id, encoded_name)
} else { } else {
format!("{host}/{id}") format!("{}/{}", template::get_host_url(req), id)
} }
} }

View File

@ -15,7 +15,6 @@
<button id="copy" data-copy="#text" class="button hidden"> <button id="copy" data-copy="#text" class="button hidden">
text kopieren text kopieren
</button> </button>
<a class="button" href="?raw">roh anzeigen</a>
</main> </main>
<footer> <footer>
<a <a

View File

@ -24,7 +24,6 @@
link kopieren link kopieren
</button> </button>
<a class="button" href="?dl">als text herunterladen</a> <a class="button" href="?dl">als text herunterladen</a>
<a class="button" href="?raw">roh anzeigen</a>
</main> </main>
<footer> <footer>
<a <a