Compare commits
4 Commits
1dc2fff0c1
...
9f37657fa7
Author | SHA1 | Date |
---|---|---|
neri | 9f37657fa7 | |
neri | 171bfc98a9 | |
neri | d7b6d31198 | |
neri | f6628e63da |
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "datatrash"
|
name = "datatrash"
|
||||||
version = "1.1.9"
|
version = "1.2.0"
|
||||||
authors = ["neri"]
|
authors = ["neri"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
13
src/db.rs
13
src/db.rs
|
@ -30,18 +30,15 @@ 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()
|
||||||
};
|
};
|
||||||
|
|
||||||
format!(
|
let host = env::var("DATABASE_HOST").unwrap_or_else(|_| "localhost".to_string());
|
||||||
"postgresql://{auth}{host}/{name}",
|
let name = env::var("DATABASE_NAME").unwrap_or_else(|_| "datatrash".to_string());
|
||||||
auth = auth,
|
format!("postgresql://{auth}{host}/{name}")
|
||||||
host = env::var("DATABASE_HOST").unwrap_or_else(|_| "localhost".to_string()),
|
|
||||||
name = env::var("DATABASE_NAME").unwrap_or_else(|_| "datatrash".to_string())
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,8 +41,7 @@ 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 mime = mime_relations::get_alias(&mime);
|
let mut response = match get_view_type(&req, &mime, &path, delete).await {
|
||||||
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,
|
||||||
|
@ -146,7 +145,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 {
|
||||||
|
@ -162,7 +161,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.clone())
|
.set_content_type(mime)
|
||||||
.set_content_disposition(content_disposition);
|
.set_content_disposition(content_disposition);
|
||||||
|
|
||||||
let mut response = file.into_response(req);
|
let mut response = file.into_response(req);
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{
|
||||||
|
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> = get_mime_aliases();
|
static ref ALIASES: HashMap<Mime, Mime> = load_mime_aliases();
|
||||||
static ref PARENTS: Vec<(Mime, Mime)> = get_mime_parent_relations();
|
static ref PARENTS: Vec<(Mime, Mime)> = load_mime_parent_relations();
|
||||||
|
static ref EXTENSIONS: HashMap<Mime, &'static str> = load_mime_extensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mime_aliases() -> HashMap<Mime, Mime> {
|
fn load_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(|(a, b)| Some((Mime::from_str(a).ok()?, Mime::from_str(b).ok()?)))
|
.flat_map(|(alias, mime)| Some((Mime::from_str(alias).ok()?, Mime::from_str(mime).ok()?)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_alias(mimetype: &Mime) -> &Mime {
|
fn load_mime_parent_relations() -> Vec<(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(' '))
|
||||||
|
@ -33,6 +30,22 @@ fn get_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()
|
||||||
|
@ -46,3 +59,17 @@ 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
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config;
|
use crate::{config, mime_relations};
|
||||||
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.clone()
|
mime_relations::get_alias(mime)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
"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,8 +104,7 @@ 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 {}, but you specified {}",
|
"maximum allowed validity is {MAX_UPLOAD_DURATION}, but you specified {keep_for}"
|
||||||
MAX_UPLOAD_DURATION, keep_for
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +134,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 {} as utf-8", name)))
|
.map_err(|_| error::ErrorBadRequest(format!("could not parse field {name} as utf-8")))
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
|
@ -171,8 +170,7 @@ 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 {} bytes",
|
"exceeded maximum file size of {max_size} bytes"
|
||||||
max_size
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}s", wait_time),
|
format!("too many requests, retry in {wait_time}s"),
|
||||||
ContentType::plaintext(),
|
ContentType::plaintext(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!("{}{}B", value, prefix)
|
format!("{value}{prefix}B")
|
||||||
}
|
}
|
||||||
|
|
||||||
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}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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::{multipart, template};
|
use crate::{mime_relations, 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,9 +64,12 @@ pub async fn upload(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_name = original_name
|
let file_name = original_name.clone().unwrap_or_else(|| {
|
||||||
.clone()
|
format!(
|
||||||
.unwrap_or_else(|| format!("{}.txt", file_id));
|
"{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)",
|
||||||
|
@ -104,15 +107,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!("{}\n", url)))
|
.body(format!("{url}\n")))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_unique_file(
|
async fn create_unique_file(
|
||||||
|
@ -145,11 +148,12 @@ 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!("{}/{}/{}", template::get_host_url(req), id, encoded_name)
|
format!("{host}/{id}/{encoded_name}")
|
||||||
} else {
|
} else {
|
||||||
format!("{}/{}", template::get_host_url(req), id)
|
format!("{host}/{id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<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
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue