Compare commits

...

6 Commits

Author SHA1 Message Date
fa60934d60 major bug: fix retention counting
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2025-11-17 23:15:04 +05:30
6fe202b1d1 fix formula typo
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2025-11-16 18:39:35 +05:30
0566e6709c return html only if user accepts it
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2025-11-16 18:18:56 +05:30
1516480216 check key field first
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2025-11-16 18:10:40 +05:30
63a0268695 feature: add cleanup
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2025-11-16 18:03:32 +05:30
fad5ee8a30 add LICENSE
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2025-11-11 22:58:02 +05:30
8 changed files with 495 additions and 124 deletions

234
Cargo.lock generated
View File

@@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@@ -9,10 +18,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "axum" name = "autocfg"
version = "0.8.6" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes",
@@ -68,10 +83,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]] [[package]]
name = "bytes" name = "bumpalo"
version = "1.10.1" version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytes"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "cc"
version = "1.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@@ -79,6 +110,25 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@@ -88,6 +138,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@@ -215,9 +271,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.7.0" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -236,9 +292,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.17" version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -250,12 +306,46 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.177" version = "0.2.177"
@@ -333,6 +423,15 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.3"
@@ -451,6 +550,12 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.20" version = "1.0.20"
@@ -528,6 +633,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.6" version = "1.4.6"
@@ -567,9 +678,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.109" version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -704,12 +815,110 @@ dependencies = [
"wit-bindgen", "wit-bindgen",
] ]
[[package]]
name = "wasm-bindgen"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.60.2" version = "0.60.2"
@@ -804,6 +1013,7 @@ name = "yamaf"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"chrono",
"futures-util", "futures-util",
"mime_guess", "mime_guess",
"rand", "rand",

View File

@@ -7,8 +7,12 @@ edition = "2024"
[dependencies] [dependencies]
axum = { version = "0.8.6", features = ["multipart"] } axum = { version = "0.8.6", features = ["multipart"] }
chrono = "*"
futures-util = "0.3.31" futures-util = "0.3.31"
mime_guess= "*" mime_guess= "*"
rand = "*" rand = "*"
tokio = { version = "1.48.0", features = ["full"] } tokio = { version = "1.48.0", features = ["full"] }
tokio-util = { version = "0.7.17", features = ["io"] } tokio-util = { version = "0.7.17", features = ["io"] }
[features]
cleanup = []

14
LICENSE Normal file
View File

@@ -0,0 +1,14 @@
Marvel Rivals License Revision 2
(c) Amneesh Singh, 2025
Permission to use, copy, distribute, sell, or modify the compiled binaries,
source code, and documentation (the "Software") is granted only to individuals
who currently hold a higher competitive matchmaking rank in Marvel Rivals
(NetEase / Marvel Games) than the copyright holder.
All others are expressly prohibited from using, copying, distributing, selling,
or modifying the Software in any form.
The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. The authors
are NOT LIABLE FOR ANY LOSS, DAMAGE, OR MISUSE arising from its use.

View File

@@ -18,7 +18,19 @@
| EXTERNAL_HOST | User facing domain name, used to return accessible URLs | ${INTERNAL_HOST} | | EXTERNAL_HOST | User facing domain name, used to return accessible URLs | ${INTERNAL_HOST} |
| EXTERNAL_HAS_TLS | URLs in html have https if set, otherwise http | - | | EXTERNAL_HAS_TLS | URLs in html have https if set, otherwise http | - |
| MAX_FILES | This does not actually limit the number of files, and used to calculate bodysize | 10 | | MAX_FILES | This does not actually limit the number of files, and used to calculate bodysize | 10 |
| MAX_FILESIZE_MB | Determines the max filesize possible, used to return failure and calculate bodysize | 100M | | MAX_FILESIZE_MB | Determines the max filesize possible, used to return failure and calculate bodysize | 100M |
| MIN_FILEDAYS | Minimum number of days for file retention | 30 days |
| MAX_FILEDAYS | Maximum number of days for file retention | 365 days |
** Retention
File retention is as per the following formula
#+begin_src text
retention = min_days + (max_days - min_days) * (1 - file_size / max_size) ^ e
#+end_src
Cleanup happens everyday when midnight (IST) has passed
[fn:1] https://git.weirdnatto.in/natto1784/simple-filehost/ [fn:1] https://git.weirdnatto.in/natto1784/simple-filehost/

View File

@@ -47,17 +47,14 @@
cargoArtifacts = craneLib.buildDepsOnly commonArgs; cargoArtifacts = craneLib.buildDepsOnly commonArgs;
yamaf = craneLib.buildPackage ( yamaf = pkgs.callPackage ./nix/yamaf.nix {
commonArgs inherit craneLib lib src;
// { };
inherit cargoArtifacts;
doCheck = false;
}
);
in in
{ {
packages = { packages = {
inherit yamaf; inherit yamaf;
yamaf-all = yamaf.override { withCleanup = true; };
default = yamaf; default = yamaf;
image = pkgs.dockerTools.buildImage { image = pkgs.dockerTools.buildImage {
name = "yamaf"; name = "yamaf";

15
nix/yamaf.nix Normal file
View File

@@ -0,0 +1,15 @@
{
craneLib,
lib,
src,
withCleanup ? false,
}:
let
cargoArtifacts = craneLib.buildDepsOnly { inherit src; };
in
craneLib.buildPackage {
inherit cargoArtifacts src;
doCheck = false;
cargoExtraArgs = lib.optionalString withCleanup "--features cleanup";
}

View File

@@ -1,31 +1,45 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>{{TITLE}}</title> <title>{{TITLE}}</title>
</head> </head>
<body> <body>
<h1>{{TITLE}}</h1> <h1>{{TITLE}}</h1>
<p>
Use curl to upload: <p>
<br> Use curl to upload:
<code> <br>
curl -F file=@"[file]" {{USER_URL}} <code>
</code> curl -F file=@"[file]" {{USER_URL}}
<br> </code>
<br> <br>
If key is enabled then a field "key" will be required. <br>
<br> If key is enabled then a field "key" will be required.
Make sure the key is the first field in the multipart before any files. <br>
<br> Make sure the key is the first field in the multipart before any files.
<code> <br>
curl -F "key=[key]" -F file=@"[file]" -F file=@"[file2]" {{USER_URL}} <code>
</code> curl -F "key=[key]" -F file=@"[file]" -F file=@"[file2]" {{USER_URL}}
</p> </code>
<form method="post" enctype="multipart/form-data"> </p>
{{KEY_FIELD}}
<input type="file" name="file" multiple> <form method="post" enctype="multipart/form-data">
<br> {{KEY_FIELD}}
<button type="submit">Upload</button> <input type="file" name="file" multiple>
</form> <br>
<div id="progress-container"></div> <button type="submit">Upload</button>
</form>
<!-- CLEANUP_START -->
<h4>Cleanup</h4>
File retention is as per the following formula
<pre><code>
max_size = {{MAX_SIZE}} // in megabytes
max_days = {{MAX_DAYS}}
min_days = {{MIN_DAYS}}
retention = min_days + (max_days - min_days) * (1 - file_size / max_size) ^ e
</code></pre>
Cleanup happens everyday when midnight (IST) has passed
<!-- CLEANUP_END -->
</html> </html>

View File

@@ -6,11 +6,10 @@ use axum::{
response::{Html, IntoResponse, Response}, response::{Html, IntoResponse, Response},
routing::get, routing::get,
}; };
use rand::{Rng, distr::Alphanumeric}; use rand::{Rng, distr::Alphanumeric};
use std::{env, net::SocketAddr, sync::LazyLock}; use std::{env, net::SocketAddr, sync::LazyLock};
use std::{env::VarError, path::PathBuf}; use std::{env::VarError, path::PathBuf};
use tokio::fs::File; use tokio::fs;
use tokio_util::io::ReaderStream; use tokio_util::io::ReaderStream;
struct Config { struct Config {
@@ -23,12 +22,14 @@ struct Config {
external_protocol: &'static str, external_protocol: &'static str,
max_filesize: usize, max_filesize: usize,
max_bodysize: usize, max_bodysize: usize,
min_filedays: usize,
max_filedays: usize,
} }
static CONFIG: LazyLock<Config> = LazyLock::new(|| { static CONFIG: LazyLock<Config> = LazyLock::new(|| {
let root_dir = env::var("ROOT_DIR").unwrap_or_else(|_| "/var/files".to_string()); let root_dir = env::var("ROOT_DIR").unwrap_or_else(|_| "/var/files".to_string());
let key = env::var("KEY"); let key = env::var("KEY");
let title = env::var("TITLE").unwrap_or_else(|_| "Simpler Filehost".to_string()); let title = env::var("TITLE").unwrap_or_else(|_| "Yet Another Mid Ahh Filehost".to_string());
let internal_host = env::var("INTERNAL_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); let internal_host = env::var("INTERNAL_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
let internal_port = env::var("INTERNAL_PORT") let internal_port = env::var("INTERNAL_PORT")
@@ -54,6 +55,18 @@ static CONFIG: LazyLock<Config> = LazyLock::new(|| {
<< 20; << 20;
let max_bodysize = max_files * max_filesize * 2; let max_bodysize = max_files * max_filesize * 2;
let min_filedays = env::var("MIN_FILEDAYS")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(30);
let max_filedays = env::var("MAX_FILEDAYS")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(365);
assert!(max_filedays >= min_filedays);
Config { Config {
root_dir, root_dir,
key, key,
@@ -64,6 +77,8 @@ static CONFIG: LazyLock<Config> = LazyLock::new(|| {
external_protocol, external_protocol,
max_filesize, max_filesize,
max_bodysize, max_bodysize,
min_filedays,
max_filedays,
} }
}); });
@@ -86,6 +101,35 @@ async fn main() {
addr, CONFIG.root_dir addr, CONFIG.root_dir
); );
/* cleanup cronjob */
#[cfg(feature = "cleanup")]
tokio::spawn(async move {
use chrono::{Local, NaiveDate};
let mut last_cleanup_day: NaiveDate = Local::now().date_naive();
loop {
use tokio::time::{Duration, sleep};
/* every 30 minutes */
sleep(Duration::from_secs(30 * 60)).await;
let now = Local::now();
let today = now.date_naive();
/* if the date changed, midnight has passed */
if today > last_cleanup_day {
println!("attempting cleanup at {}", now.to_string());
if let Err(e) = cleanup_directory().await {
eprintln!("cleanup failed: {}", e);
} else {
println!("cleanup succeeded!");
last_cleanup_day = today;
}
}
}
});
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
@@ -108,6 +152,19 @@ static INDEX_HTML: LazyLock<String> = LazyLock::new(|| {
}, },
); );
if cfg!(feature = "cleanup") {
html = html
.replace("{{MAX_SIZE}}", &(CONFIG.max_filesize >> 20).to_string())
.replace("{{MAX_DAYS}}", &CONFIG.max_filedays.to_string())
.replace("{{MIN_DAYS}}", &CONFIG.min_filedays.to_string());
} else {
if let Some(start) = html.find("<!-- CLEANUP_START -->") {
if let Some(end) = html.find("<!-- CLEANUP_END -->") {
html.replace_range(start..end + "<!-- CLEANUP_END -->".len(), "");
}
}
}
html html
}); });
@@ -153,10 +210,7 @@ fn clean_filename(filename: &str) -> String {
let mut prev_dash = false; let mut prev_dash = false;
for c in filename.to_lowercase().chars() { for c in filename.to_lowercase().chars() {
if c.is_ascii_alphanumeric() { if c.is_ascii_alphanumeric() || c == '.' {
slug.push(c);
prev_dash = false;
} else if c == '.' {
slug.push(c); slug.push(c);
prev_dash = false; prev_dash = false;
} else if !prev_dash { } else if !prev_dash {
@@ -168,81 +222,94 @@ fn clean_filename(filename: &str) -> String {
slug.trim_matches('-').to_string() slug.trim_matches('-').to_string()
} }
async fn upload(mut payload: Multipart) -> Result<impl IntoResponse, YamafError> { async fn upload(
headers: HeaderMap,
mut payload: Multipart,
) -> Result<impl IntoResponse, YamafError> {
let mut responses = Vec::new(); let mut responses = Vec::new();
let mut found_key = false;
let wants_html = headers
.get("accept")
.and_then(|h| h.to_str().ok())
.map(|a| a.contains("text/html"))
.unwrap_or(false);
if let Ok(ref key) = CONFIG.key {
if let Some(field) = payload.next_field().await.unwrap() {
if field.name() == Some("key") {
let bytes = field
.bytes()
.await
.map_err(|e| YamafError::BadRequest(format!("Error reading key: {e}")))?;
let s = String::from_utf8(bytes.to_vec())
.map_err(|_| YamafError::InternalError("Invalid key format".into()))?;
if s != *key {
return Err(YamafError::BadRequest("Wrong key".into()));
}
} else {
return Err(YamafError::BadRequest("Missing key".into()));
}
} else {
return Err(YamafError::BadRequest("Missing key".into()));
}
}
while let Some(mut field) = payload.next_field().await.unwrap() { while let Some(mut field) = payload.next_field().await.unwrap() {
match field.name() { if field.name() == Some("file") {
Some("key") => { let filename = field
if let Ok(ref key) = CONFIG.key { .file_name()
let bytes = field .map_or(format!("{}-upload", random(10)), |filename| {
.bytes() format!("{}-{}", random(4), clean_filename(filename))
.await });
.map_err(|e| YamafError::BadRequest(format!("Error reading key: {e}")))?;
let s = String::from_utf8(bytes.to_vec()) let save_path = std::path::Path::new(&CONFIG.root_dir).join(&filename);
.map_err(|_| YamafError::InternalError("Invalid key format".into()))?;
if s != *key { let mut file = fs::File::create(&save_path)
return Err(YamafError::BadRequest("Wrong key".into())); .await
} .map_err(|_| YamafError::InternalError("Internal i/o error".into()))?;
found_key = true; let mut written: usize = 0;
}
}
Some("file") => { while let Some(chunk) = field
if CONFIG.key.is_ok() && found_key == false { .chunk()
return Err(YamafError::BadRequest("Missing key".into())); .await
.map_err(|err| YamafError::InternalError(err.to_string()))?
{
use tokio::io::AsyncWriteExt;
written = written
.checked_add(chunk.len())
.ok_or_else(|| YamafError::BadRequest("File too large".into()))?;
if written > CONFIG.max_filesize {
_ = fs::remove_file(&save_path).await;
return Err(YamafError::FileTooBig(filename));
} }
let filename = field file.write_all(&chunk)
.file_name()
.map_or(format!("{}-upload", random(10)), |filename| {
format!("{}-{}", random(4), clean_filename(filename))
});
let save_path = std::path::Path::new(&CONFIG.root_dir).join(&filename);
let mut file = File::create(&save_path)
.await .await
.map_err(|_| YamafError::InternalError("Internal i/o error".into()))?; .map_err(|_| YamafError::InternalError("Internal i/o error".into()))?;
let mut written: usize = 0;
while let Some(chunk) = field
.chunk()
.await
.map_err(|err| YamafError::InternalError(err.to_string()))?
{
use tokio::io::AsyncWriteExt;
written = written
.checked_add(chunk.len())
.ok_or_else(|| YamafError::BadRequest("File too large".into()))?;
if written > CONFIG.max_filesize {
_ = tokio::fs::remove_file(&save_path).await;
return Err(YamafError::FileTooBig(filename));
}
file.write_all(&chunk)
.await
.map_err(|_| YamafError::InternalError("Internal i/o error".into()))?;
}
responses.push(format!(
r#"<a href="{proto}://{host}/{file}">{proto}://{host}/{file}</a> (size ~ {size:.2}k)"#,
proto = CONFIG.external_protocol,
host = CONFIG.external_host,
file = filename,
size = written as f64 / 1024 as f64
));
} }
None | Some(_) => {} let url = format!(
"{proto}://{host}/{file}",
proto = CONFIG.external_protocol,
host = CONFIG.external_host,
file = filename,
);
if wants_html {
responses.push(format!(
r#"<a href="{url}">{url}</a> (size ~ {size:.2}k)"#,
url = url,
size = written as f64 / 1024.0,
));
} else {
responses.push(url);
}
} }
} }
@@ -250,20 +317,24 @@ async fn upload(mut payload: Multipart) -> Result<impl IntoResponse, YamafError>
return Err(YamafError::BadRequest("No files uploaded".into())); return Err(YamafError::BadRequest("No files uploaded".into()));
} }
Ok(Html(format!( if wants_html {
"Here are your file(s):<br>{}", Ok(Html(format!(
responses.join("<br>") "Here are your file(s):<br>{}",
)) responses.join("<br>")
.into_response()) ))
.into_response())
} else {
Ok(responses.join("\n").into_response())
}
} }
async fn serve_file(Path(filename): Path<String>) -> Result<impl IntoResponse, YamafError> { async fn serve_file(Path(filename): Path<String>) -> Result<impl IntoResponse, YamafError> {
let path = PathBuf::from(&CONFIG.root_dir).join(&filename); let path = PathBuf::from(&CONFIG.root_dir).join(&filename);
let metadata = tokio::fs::metadata(&path) let metadata = fs::metadata(&path)
.await .await
.map_err(|_| YamafError::FileNotFound)?; .map_err(|_| YamafError::FileNotFound)?;
let file = File::open(&path) let file = fs::File::open(&path)
.await .await
.map_err(|_| YamafError::FileNotFound)?; .map_err(|_| YamafError::FileNotFound)?;
let mime = mime_guess::from_path(&path).first_or_octet_stream(); let mime = mime_guess::from_path(&path).first_or_octet_stream();
@@ -289,3 +360,37 @@ async fn serve_file(Path(filename): Path<String>) -> Result<impl IntoResponse, Y
Ok((StatusCode::OK, headers, body).into_response()) Ok((StatusCode::OK, headers, body).into_response())
} }
#[cfg(feature = "cleanup")]
async fn cleanup_directory() -> Result<(), std::io::Error> {
let dir = std::path::Path::new(&CONFIG.root_dir);
let mut entries = fs::read_dir(dir).await?;
while let Some(entry) = entries.next_entry().await? {
use std::os::unix::fs::MetadataExt;
use std::time::SystemTime;
let path = entry.path();
let meta = entry.metadata().await?;
let modified = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
let age = SystemTime::now()
.duration_since(modified)
.unwrap_or_default();
/* min_days + (max_days - min_days) * (1 - size/max_size)^e */
let retention = (CONFIG.min_filedays as f64
+ (CONFIG.max_filedays - CONFIG.min_filedays) as f64
* (1.0 - (meta.size() as f64 / CONFIG.max_filesize as f64))
.powf(std::f64::consts::E))
* 24.0
* 60.0
* 60.0;
if meta.is_file() && age.as_secs_f64() > retention {
fs::remove_file(&path).await?;
println!("Deleted {:?}", path);
}
}
Ok(())
}