Compare commits

..

4 Commits

Author SHA1 Message Date
b346978ddc merge upstream 2025-11-11 22:57:27 +05:30
155811c47e add LICENSE
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
2025-11-11 22:56:56 +05:30
ce14336ad2 feat: add brainrot on upload page 2025-11-10 18:40:42 +05:30
36d5fd24bc alo 2025-11-10 18:37:57 +05:30
8 changed files with 186 additions and 463 deletions

230
Cargo.lock generated
View File

@@ -2,32 +2,17 @@
# 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"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.7" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes",
@@ -82,27 +67,11 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[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"
@@ -110,25 +79,6 @@ 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"
@@ -138,12 +88,6 @@ 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"
@@ -271,9 +215,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -292,9 +236,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.18" version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -306,46 +250,12 @@ 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"
@@ -423,15 +333,6 @@ 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"
@@ -550,12 +451,6 @@ 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"
@@ -633,12 +528,6 @@ 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"
@@ -678,9 +567,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.110" version = "2.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -815,110 +704,12 @@ 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"
@@ -1013,7 +804,6 @@ 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,12 +7,8 @@ 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 = []

View File

@@ -1,5 +1,5 @@
Marvel Rivals License Revision 2 Marvel Rivals License Revision 2
(c) Amneesh Singh, 2025 (c) Author, year
Permission to use, copy, distribute, sell, or modify the compiled binaries, Permission to use, copy, distribute, sell, or modify the compiled binaries,
source code, and documentation (the "Software") is granted only to individuals source code, and documentation (the "Software") is granted only to individuals

View File

@@ -2,6 +2,7 @@
* Yet Another Mid Ahh Filehost * Yet Another Mid Ahh Filehost
adasdad
- YAMAF is a yet another mid ahh filehost for personal use. - YAMAF is a yet another mid ahh filehost for personal use.
- It is extremely simple and minimal and might break under niche circumstances like uploading many huge files. - It is extremely simple and minimal and might break under niche circumstances like uploading many huge files.
- It uses axum unlike its predecessors[fn:1][fn:2], both of which were written in rust some time ago now. - It uses axum unlike its predecessors[fn:1][fn:2], both of which were written in rust some time ago now.
@@ -18,19 +19,7 @@
| 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,14 +47,17 @@
cargoArtifacts = craneLib.buildDepsOnly commonArgs; cargoArtifacts = craneLib.buildDepsOnly commonArgs;
yamaf = pkgs.callPackage ./nix/yamaf.nix { yamaf = craneLib.buildPackage (
inherit craneLib lib src; commonArgs
}; // {
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";

View File

@@ -1,15 +0,0 @@
{
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,45 +1,110 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{TITLE}}</title> <title>{{TITLE}}</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #fafafa;
color: #222;
margin: 2rem auto;
max-width: 800px;
padding: 1.5rem;
line-height: 1.6;
}
h1 {
color: #111;
font-size: 2rem;
margin-bottom: 1rem;
}
code {
background: #eee;
padding: 0.3rem 0.5rem;
border-radius: 4px;
display: block;
margin: 0.5rem 0;
white-space: pre-wrap;
}
form {
margin-top: 1.5rem;
padding: 1rem;
background: #fff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
button {
margin-top: 1rem;
background: #007bff;
color: #fff;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
}
button:hover {
background: #0056b3;
}
iframe {
display: block;
margin: 2rem auto;
max-width: 100%;
border-radius: 8px;
}
#progress-container {
margin-top: 1rem;
}
</style>
</head> </head>
<body> <body>
<h1>{{TITLE}}</h1> <h1>{{TITLE}}</h1>
<p> <p>
Use curl to upload: You can upload files directly or use <code>curl</code>:
<br>
<code>
curl -F file=@"[file]" {{USER_URL}}
</code>
<br>
<br>
If key is enabled then a field "key" will be required.
<br>
Make sure the key is the first field in the multipart before any files.
<br>
<code>
curl -F "key=[key]" -F file=@"[file]" -F file=@"[file2]" {{USER_URL}}
</code>
</p> </p>
<code>
curl -F file=@"[file]" {{USER_URL}}
</code>
<p>
If key authentication is enabled, include a <strong>"key"</strong> field before the file(s):
</p>
<code>
curl -F "key=[key]" -F file=@"[file]" -F file=@"[file2]" {{USER_URL}}
</code>
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
{{KEY_FIELD}} {{KEY_FIELD}}
<input type="file" name="file" multiple> <input type="file" name="file" multiple />
<br> <br />
<button type="submit">Upload</button> <button type="submit">Upload</button>
</form> </form>
<!-- CLEANUP_START --> <div id="progress-container"></div>
<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 -->
<!-- 🎥 Embedded YouTube video -->
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/lgox5KTyzdc?si=nPLAWSf0cx_tjVNH"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
></iframe>
</body>
</html> </html>

View File

@@ -6,10 +6,11 @@ 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; use tokio::fs::File;
use tokio_util::io::ReaderStream; use tokio_util::io::ReaderStream;
struct Config { struct Config {
@@ -22,14 +23,12 @@ 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(|_| "Yet Another Mid Ahh Filehost".to_string()); let title = env::var("TITLE").unwrap_or_else(|_| "Simpler 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")
@@ -55,18 +54,6 @@ 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,
@@ -77,8 +64,6 @@ static CONFIG: LazyLock<Config> = LazyLock::new(|| {
external_protocol, external_protocol,
max_filesize, max_filesize,
max_bodysize, max_bodysize,
min_filedays,
max_filedays,
} }
}); });
@@ -101,35 +86,6 @@ 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();
} }
@@ -152,19 +108,6 @@ 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
}); });
@@ -210,7 +153,10 @@ 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() || c == '.' { if c.is_ascii_alphanumeric() {
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 {
@@ -222,94 +168,81 @@ fn clean_filename(filename: &str) -> String {
slug.trim_matches('-').to_string() slug.trim_matches('-').to_string()
} }
async fn upload( async fn upload(mut payload: Multipart) -> Result<impl IntoResponse, YamafError> {
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() {
if field.name() == Some("file") { match field.name() {
let filename = field Some("key") => {
.file_name() if let Ok(ref key) = CONFIG.key {
.map_or(format!("{}-upload", random(10)), |filename| { let bytes = field
format!("{}-{}", random(4), clean_filename(filename)) .bytes()
}); .await
.map_err(|e| YamafError::BadRequest(format!("Error reading key: {e}")))?;
let save_path = std::path::Path::new(&CONFIG.root_dir).join(&filename); let s = String::from_utf8(bytes.to_vec())
.map_err(|_| YamafError::InternalError("Invalid key format".into()))?;
let mut file = fs::File::create(&save_path) if s != *key {
.await return Err(YamafError::BadRequest("Wrong key".into()));
.map_err(|_| YamafError::InternalError("Internal i/o error".into()))?; }
let mut written: usize = 0; found_key = true;
}
}
while let Some(chunk) = field Some("file") => {
.chunk() if CONFIG.key.is_ok() && found_key == false {
.await return Err(YamafError::BadRequest("Missing key".into()));
.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));
} }
file.write_all(&chunk) let filename = field
.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 url = format!( let mut written: usize = 0;
"{proto}://{host}/{file}",
proto = CONFIG.external_protocol, while let Some(chunk) = field
host = CONFIG.external_host, .chunk()
file = filename, .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()))?;
}
if wants_html {
responses.push(format!( responses.push(format!(
r#"<a href="{url}">{url}</a> (size ~ {size:.2}k)"#, r#"<a href="{proto}://{host}/{file}">{proto}://{host}/{file}</a> (size ~ {size:.2}k)"#,
url = url, proto = CONFIG.external_protocol,
size = written as f64 / 1024.0, host = CONFIG.external_host,
file = filename,
size = written as f64 / 1024 as f64
)); ));
} else {
responses.push(url);
} }
None | Some(_) => {}
} }
} }
@@ -317,24 +250,20 @@ async fn upload(
return Err(YamafError::BadRequest("No files uploaded".into())); return Err(YamafError::BadRequest("No files uploaded".into()));
} }
if wants_html { Ok(Html(format!(
Ok(Html(format!( "Here are your file(s):<br>{}",
"Here are your file(s):<br>{}", responses.join("<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 = fs::metadata(&path) let metadata = tokio::fs::metadata(&path)
.await .await
.map_err(|_| YamafError::FileNotFound)?; .map_err(|_| YamafError::FileNotFound)?;
let file = fs::File::open(&path) let file = 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();
@@ -360,37 +289,3 @@ 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(())
}