feat: replace config with figment and a bunch more
format, check and test / cargo fmt (push) Failing after 3m4s Details
format, check and test / cargo test (push) Failing after 3m44s Details

I also replaced magic-tree-mini with the full magic lib, as it's better.

I also updated all the crates.

oh yeah I added a robots.txt

I kinda started working on something else, then did another thing half way through
then I did another thing.

audron is a champion, a true hero.
main
Volkor 2023-06-28 00:25:33 +10:00
parent 567dcc3c03
commit 1ccc82cac0
Signed by: Volkor
SSH Key Fingerprint: SHA256:taX3XcC6grYv7+eTzBsIUNCVFgMzh7gkVgxliSh69ek
15 changed files with 1198 additions and 958 deletions

1565
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
nanoid = "0.4.0"
chrono = "0.4.23"
rand = "0.8.5"
config = "0.13.3"
figment = { version = "0.10.10", features = ["toml"] }
lazy_static = "1.4.0"
tree_magic_mini = "3.0.3"
chrono-humanize = "0.2.2"
magic = "0.13.0"
serde_derive = "1.0.164"
serde = "1.0.164"

42
src/config.rs Normal file
View File

@ -0,0 +1,42 @@
use serde_derive::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub nginx_sendfile: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DatabaseConfig {
pub sql_backend: String,
pub url: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct LoggingConfig {
pub level: String,
pub metrics: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OperationsConfig {
pub filename_size: u8,
pub adminkey_size: u8,
pub apikey_size: u8,
pub engine_mode: i64,
pub cleaner_interval: i32,
pub file_expiry_min: u32,
pub file_expiry_max: u32,
pub max_filesize: u64,
pub banned_mimetype: Vec<String>,
pub unsafe_mimetype: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AppConfig {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub logging: LoggingConfig,
pub operations: OperationsConfig,
}

View File

@ -277,12 +277,15 @@ fn update_expiry_override() {
/// This function gets a array of all the files uploaded by a specific API Key.
/// This allows for the select few who hold an API key to view the information about the files they upload.
pub async fn get_my_files(sqlconn: &Pool<Sqlite>, ip: &str) -> Result<Vec<FileMetric>, sqlx::Error> {
pub async fn get_my_files(
sqlconn: &Pool<Sqlite>,
ip: &str,
) -> Result<Vec<FileMetric>, sqlx::Error> {
let result = sqlx::query!(
"SELECT file, filesize, mimetype, views, expiry, expiry_override, ip, isDeleted
FROM files
WHERE ip = ?",
ip
ip
)
.fetch_all(sqlconn)
.await?;
@ -300,7 +303,7 @@ pub async fn get_my_files(sqlconn: &Pool<Sqlite>, ip: &str) -> Result<Vec<FileMe
filesize: row.filesize,
views: row.views.unwrap(),
expiry: row.expiry,
is_deleted
is_deleted,
};
files.push(file);
}
@ -421,7 +424,7 @@ pub async fn get_file_metrics(sqlconn: &Pool<Sqlite>) -> Option<Vec<FileMetric>>
filesize: row.filesize,
views: row.views.unwrap(),
expiry: row.expiry,
is_deleted: false
is_deleted: false,
};
// Then add the struct to the Vec
filevec.push(file);

View File

@ -37,8 +37,8 @@ where
// This should check if the adminkey exists for another file, and regenerate if it does.
pub async fn generate_adminkey(sqlconn: &Pool<Sqlite>) -> String {
let adminkey_size: usize = CONFIG
.get_int("operations.adminkey_size")
.expect("Couldn't find 'adminkey_size' in config. :(")
.operations
.adminkey_size
.try_into()
.expect("Problem with converting adminkey_size to usize");
let adminkey = nanoid!(adminkey_size);
@ -54,9 +54,7 @@ pub async fn calculate_expiry<S>(sqlconn: &Pool<Sqlite>, filename: S, filesize:
where
S: AsRef<str>,
{
let engine_mode: i64 = CONFIG
.get_int("operations.engine_mode")
.expect("Couldn't find 'engine_mode' in config. :(");
let engine_mode: i64 = CONFIG.operations.engine_mode;
let expiry = match engine_mode {
1 => engine_1(sqlconn, filename.as_ref()).await,
@ -87,8 +85,8 @@ where
{
// Read the file expiry settings from the config
let file_expiry_min: i32 = CONFIG
.get_int("operations.file_expiry_min")
.expect("Couldn't find 'file_expiry_min' in config. :(")
.operations
.file_expiry_min
.try_into()
.expect("The file_expiry_min is wayyyy to big.");
@ -115,15 +113,9 @@ async fn engine_2(filesize: i32) -> i32 {
// audron REVIEW: also, probably make that engine match use enums and give them descriptive names
// Load settings from config
let file_expiry_min: f64 = CONFIG
.get_float("operations.file_expiry_min")
.expect("Couldn't find 'file_expiry_min' in config. :(");
let file_expiry_max: f64 = CONFIG
.get_float("operations.file_expiry_max")
.expect("Couldn't find 'file_expiry_min' in config. :(");
let max_filesize: f64 = CONFIG
.get_float("operations.max_filesize")
.expect("Couldn't find 'max_filesize' in config. :(");
let file_expiry_min: f64 = CONFIG.operations.file_expiry_min.into();
let file_expiry_max: f64 = CONFIG.operations.file_expiry_max.into();
let max_filesize: f64 = CONFIG.operations.max_filesize as f64;
let time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)

View File

@ -68,7 +68,7 @@ pub async fn delete_file(req: &mut Request, res: &mut Response) {
};
render_template(res, &headers, "error.html", template, StatusCode::OK).await
} else {
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Json(r#"{"deletion": "success"}"#));
}
}
@ -94,7 +94,7 @@ pub async fn delete_file(req: &mut Request, res: &mut Response) {
)
.await
} else {
res.set_status_code(StatusCode::UNAUTHORIZED);
res.status_code(StatusCode::UNAUTHORIZED);
res.render(Text::Json(r#"{"error": "InvalidAdminKey"}"#));
}
}

View File

@ -23,12 +23,5 @@ pub async fn index(req: &mut Request, res: &mut Response) {
),
..Default::default()
};
render_template(
res,
headers,
"upload.html",
template,
StatusCode::OK,
)
.await
render_template(res, headers, "upload.html", template, StatusCode::OK).await
}

View File

@ -1,6 +1,13 @@
use salvo::{handler, Request, Response, hyper::header::HOST, prelude::StatusCode};
use salvo::{handler, hyper::header::HOST, prelude::StatusCode, Request, Response};
use crate::{db, SQLITE, handlers::{TemplateStruct, render_template, convert_file_size, convert_unix_timestamp, count_file_metrics}};
use crate::{
db,
handlers::{
convert_file_size, convert_unix_timestamp, count_file_metrics, render_template,
TemplateStruct,
},
SQLITE,
};
use super::guess_ip;
@ -14,7 +21,7 @@ pub async fn list_files(req: &mut Request, res: &mut Response) {
// Get the IP of the person viewing, so we can show their files
let headers = req.headers();
let remote_addr = &req.remote_addr().unwrap().clone();
let remote_addr = &req.remote_addr().clone();
let ip = guess_ip(headers, remote_addr);
tracing::debug!("list_files(remote_addr, ip): {:?}, {:?}", remote_addr, ip);
@ -77,12 +84,15 @@ pub async fn list_files(req: &mut Request, res: &mut Response) {
render_template(res, headers, template_filename, template, status_code).await;
}
}
// Close the table when all files are 'rendered'
html.push_str("</table>");
// Log this to the server console
tracing::info!("New Request: /my_files ({} Files)", count_file_metrics(&files) );
// Log this to the server console
tracing::info!(
"New Request: /my_files ({} Files)",
count_file_metrics(&files)
);
let template_filename = "my_files.html";
let template = TemplateStruct {

View File

@ -1,18 +1,22 @@
use std::path::PathBuf;
use std::{path::PathBuf, time::Duration};
use chrono::{DateTime, Utc};
use chrono_humanize::Humanize;
use ramhorns::{Content, Ramhorns};
use salvo::{
addr::SocketAddr,
conn::SocketAddr,
hyper::{header::HOST, HeaderMap},
prelude::StatusCode,
writer::Text,
Response,
};
use chrono::{DateTime, Utc};
use chrono_humanize::Humanize;
use sqlx::Error;
use tokio::{task, time};
use crate::{CONFIG, db::FileMetric};
use crate::{
db::{self, FileMetric},
engine, CONFIG, COOKIE, SQLITE,
};
// Submodules for each request handler
pub mod delete_file;
@ -132,10 +136,13 @@ pub async fn render_template(
};
// Tell the engine to render the template assigned above
let rendered = tpls.get(template_filename).expect("Couldn't find the template filename").render(&template);
let rendered = tpls
.get(template_filename)
.expect("Couldn't find the template filename")
.render(&template);
// Set the status code and render the completed page
res.set_status_code(status_code);
res.status_code(status_code);
res.render(Text::Html(rendered));
}
@ -146,9 +153,7 @@ where
S: AsRef<str>,
{
// Load the banned files from the config
let banned = CONFIG
.get_array("operations.banned_mimetype")
.expect("Couldn't find 'banned_mimetype' in config. :(");
let banned = &CONFIG.operations.banned_mimetype;
// Convert the provided mimetype to lowercase for case-insensitive comparison
let lowercase_mimetype = mimetype.as_ref().to_lowercase();
@ -157,11 +162,7 @@ where
let lowercase_banned: Vec<String> = banned
.iter()
.map(|mime| {
let mime_str = mime
.clone()
.into_string()
.expect("Invalid banned mimetype in config")
.to_lowercase();
let mime_str = mime.clone().to_lowercase();
tracing::debug!("Banned mimetype: {}", mime_str);
mime_str
})
@ -172,7 +173,6 @@ where
lowercase_banned.contains(&lowercase_mimetype)
}
/// Convert the filesize to a human readable number
fn convert_file_size(file_size: i64) -> String {
let units = ["B", "KB", "MB", "GB", "TB"];
@ -207,4 +207,58 @@ fn count_file_metrics(file_metrics: &Result<Vec<FileMetric>, Error>) -> usize {
Ok(metrics) => metrics.len(),
Err(_) => 0,
}
}
}
/// Guess the mimetype
/// This uses the magic library, which seems to be the most accurate out of the ones I've seen.
/// As the name suggests, this is magic. I have no idea HOW it works, but it should.
/// This doesn't need the full file loaded into memory, so it loads 2048 bytes.
fn detect_mime_type(file_data: &[u8]) -> Result<String, magic::MagicError> {
COOKIE.with(|cookie| {
let result = cookie.buffer(file_data)?;
Ok(result)
})
}
// This spawns a tokio task to run a interval timer forever.
// the interval timer runs every 'period' seconds.
pub fn cleaner_thread(period: i32) {
let _forever = task::spawn(async move {
let mut interval = time::interval(Duration::from_secs(period.try_into().unwrap()));
let sqlconn = SQLITE.get().unwrap();
loop {
// Wait for the next interval
interval.tick().await;
// Get a vec of files that will expire this loop.
// Also I'm sorta just realising how annoying it is to pass sqlconn through like 4 functions deep just to do something.
let old_files_result = db::get_old_files(sqlconn).await;
match old_files_result {
Ok(file_list) => {
tracing::info!("Running Cleaner");
for file in file_list {
// Delete the file from the database
db::delete_file(sqlconn, &file).await.unwrap_or_else(|err| {
tracing::error!(
"Failed to delete file from database: {}, error: {:?}",
&file,
err
);
0
});
// Delete the file from the filesystem
engine::delete_file(&file).await.unwrap_or_else(|err| {
tracing::error!("Failed to delete file from database: {:?}", err);
});
}
tracing::info!("Cleaner finished");
}
Err(err) => {
tracing::error!("Error getting files to expire: {}", err);
// Return a empty Vec so it doesn't delete anything
return Vec::<String>::new();
}
}
}
});
}

View File

@ -1,8 +1,7 @@
#[cfg(test)]
mod tests {
use crate::handlers::{guess_ip, is_mimetype_banned};
use config::Config;
use salvo::addr::SocketAddr as SalvoSocketAddr;
use crate::handlers::{detect_mime_type, guess_ip, is_mimetype_banned};
use salvo::conn::SocketAddr as SalvoSocketAddr;
use salvo::hyper::HeaderMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
@ -102,4 +101,181 @@ mod tests {
let result = is_mimetype_banned("aUdIo/MP4").await;
assert!(!result);
}
#[tokio::test]
async fn test_detect_mime_type() {
// Test case 1: Valid file data
let file_data = b"This is a test file";
let result = detect_mime_type(file_data);
println!("Test 1: Valid file data: {:?}", result);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "text/plain");
// Test case 2: Empty file data
let file_data = b"";
let result = detect_mime_type(file_data);
println!("Test 2: Empty file data Result: {:?}", result);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "application/x-empty");
// Test case 4: putty.exe
let file_data: &[u8] = &[
// Putty.exe trimmed at 2048
// This is stupid, and probably not needed for a test lol.
0x4D, 0x5A, 0x78, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4,
0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70,
0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F, 0x74, 0x20,
0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20,
0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x24, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0x86,
0x0A, 0x00, 0x91, 0x10, 0x5C, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF0, 0x00, 0x22, 0x00, 0x0B, 0x02, 0x0E, 0x00, 0x00, 0x5A, 0x0E, 0x00, 0x00, 0x70,
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x88, 0x0B, 0x00, 0x00, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x19, 0x00, 0x00, 0x04, 0x00, 0x00, 0x44, 0xA8,
0x19, 0x00, 0x02, 0x00, 0x60, 0x81, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x11,
0x12, 0x00, 0xB4, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x13, 0x00, 0x58, 0x9F, 0x05, 0x00,
0x00, 0xD0, 0x12, 0x00, 0x78, 0x6C, 0x00, 0x00, 0x00, 0xCE, 0x18, 0x00, 0x28, 0x57,
0x00, 0x00, 0x00, 0x40, 0x19, 0x00, 0xA8, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0E, 0x10, 0x00, 0x28, 0x00, 0x00, 0x00,
0xE0, 0xA6, 0x0F, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x28, 0x1D, 0x12, 0x00, 0xE0, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00,
0x66, 0x59, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x5A, 0x0E, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x60, 0x2E, 0x72, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0xBC, 0xFF,
0x03, 0x00, 0x00, 0x70, 0x0E, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x5E, 0x0E, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x40, 0x2E, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x9C, 0x55, 0x00, 0x00,
0x00, 0x70, 0x12, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x5E, 0x12, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0,
0x2E, 0x70, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x78, 0x6C, 0x00, 0x00, 0x00, 0xD0,
0x12, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x6E, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x2E, 0x30,
0x30, 0x63, 0x66, 0x67, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x40, 0x13, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0xDC, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x2E, 0x67, 0x78, 0x66,
0x67, 0x00, 0x00, 0x00, 0x60, 0x2A, 0x00, 0x00, 0x00, 0x50, 0x13, 0x00, 0x00, 0x2C,
0x00, 0x00, 0x00, 0xDE, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x2E, 0x74, 0x6C, 0x73, 0x00, 0x00,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x80, 0x13, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x0A, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x5F, 0x52, 0x44, 0x41, 0x54, 0x41, 0x00, 0x00,
0x5C, 0x01, 0x00, 0x00, 0x00, 0x90, 0x13, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0C,
0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x40, 0x2E, 0x72, 0x73, 0x72, 0x63, 0x00, 0x00, 0x00, 0x58, 0x9F,
0x05, 0x00, 0x00, 0xA0, 0x13, 0x00, 0x00, 0xA0, 0x05, 0x00, 0x00, 0x0E, 0x13, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x40, 0x2E, 0x72, 0x65, 0x6C, 0x6F, 0x63, 0x00, 0x00, 0xA8, 0x1E, 0x00, 0x00,
0x00, 0x40, 0x19, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xAE, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x42,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x56, 0x57, 0x53, 0x48, 0x83, 0xEC, 0x40, 0x48, 0x89, 0xD6, 0x48, 0x89,
0xCF, 0x4C, 0x89, 0x44, 0x24, 0x70, 0x4C, 0x89, 0x4C, 0x24, 0x78, 0x48, 0x8B, 0x05,
0x32, 0x60, 0x12, 0x00, 0x48, 0x31, 0xE0, 0x48, 0x89, 0x44, 0x24, 0x38, 0x48, 0x8D,
0x5C, 0x24, 0x70, 0x48, 0x89, 0x5C, 0x24, 0x30, 0xE8, 0xBB, 0x61, 0x00, 0x00, 0x48,
0x8B, 0x08, 0x48, 0x83, 0xC9, 0x01, 0x48, 0x89, 0x5C, 0x24, 0x28, 0x48, 0xC7, 0x44,
0x24, 0x20, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xFA, 0x49, 0xC7, 0xC0, 0xFF, 0xFF,
0xFF, 0xFF, 0x49, 0x89, 0xF1, 0xE8, 0x9C, 0x98, 0x0B, 0x00, 0x89, 0xC6, 0x85, 0xC0,
0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x48, 0xF0, 0x48, 0x8B, 0x4C, 0x24, 0x38, 0x48,
0x31, 0xE1, 0xE8, 0x8B, 0x72, 0x0B, 0x00, 0x89, 0xF0, 0x48, 0x83, 0xC4, 0x40, 0x5B,
0x5F, 0x5E, 0xC3, 0xCC, 0x56, 0x48, 0x83, 0xEC, 0x70, 0x48, 0x8B, 0x05, 0xC4, 0x5F,
0x12, 0x00, 0x48, 0x31, 0xE0, 0x48, 0x89, 0x44, 0x24, 0x68, 0x48, 0x8B, 0x35, 0x6D,
0x6E, 0x12, 0x00, 0x48, 0x85, 0xF6, 0x74, 0x20, 0x48, 0x83, 0x3D, 0x68, 0x6E, 0x12,
0x00, 0x00, 0x74, 0x3A, 0x48, 0x8B, 0x4C, 0x24, 0x68, 0x48, 0x31, 0xE1, 0xE8, 0x49,
0x72, 0x0B, 0x00, 0x48, 0x89, 0xF0, 0x48, 0x83, 0xC4, 0x70, 0x5E, 0xC3, 0x4C, 0x8B,
0x05, 0x49, 0x61, 0x0E, 0x00, 0x31, 0xC9, 0x31, 0xD2, 0xE8, 0x60, 0x0F, 0x04, 0x00,
0x48, 0x89, 0xC6, 0x48, 0x89, 0x05, 0x2E, 0x6E, 0x12, 0x00, 0x48, 0x83, 0x3D, 0x2E,
0x6E, 0x12, 0x00, 0x00, 0x75, 0xC6, 0xC7, 0x44, 0x24, 0x20, 0x00, 0x00, 0x00, 0x00,
0x48, 0x8D, 0x05, 0x6D, 0x00, 0x00, 0x00, 0x48, 0x89, 0x44, 0x24, 0x28, 0x48, 0xC7,
0x44, 0x24, 0x30, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x0D, 0x10, 0x6E, 0x12, 0x00,
0x48, 0x89, 0x4C, 0x24, 0x38, 0xBA, 0xC8, 0x00, 0x00, 0x00, 0xFF, 0x15, 0x20, 0x10,
0x12, 0x00, 0x48, 0x89, 0x44, 0x24, 0x40, 0xBA, 0x01, 0x7F, 0x00, 0x00, 0x31, 0xC9,
0xFF, 0x15, 0x06, 0x10, 0x12, 0x00, 0x48, 0x89, 0x44, 0x24, 0x48, 0x0F, 0x57, 0xC0,
0x0F, 0x11, 0x44, 0x24, 0x50, 0x48, 0x8B, 0x05, 0xCA, 0x6D, 0x12, 0x00, 0x48, 0x89,
0x44, 0x24, 0x60, 0x48, 0x8D, 0x4C, 0x24, 0x20, 0xFF, 0x15, 0x62, 0x10, 0x12, 0x00,
0x48, 0x8B, 0x35, 0xB3, 0x6D, 0x12, 0x00, 0xE9, 0x50, 0xFF, 0xFF, 0xFF, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0xCC, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x56, 0x57,
0x55, 0x53, 0x48, 0x81, 0xEC, 0x18, 0x09, 0x00, 0x00, 0x4D, 0x89, 0xCD, 0x4D, 0x89,
0xC7, 0x89, 0xD6, 0x49, 0x89, 0xCE, 0x48, 0x8B, 0x05, 0xCB, 0x5E, 0x12, 0x00, 0x48,
0x31, 0xE0, 0x48, 0x89, 0x84, 0x24, 0x10, 0x09, 0x00, 0x00, 0x81, 0xFA, 0xFF, 0x01,
0x00, 0x00, 0x0F, 0x8F, 0x8A, 0x00, 0x00, 0x00, 0x8D, 0x86, 0x60, 0xFF, 0xFF, 0xFF,
0x83, 0xF8, 0x77, 0x0F, 0x87, 0xC1, 0x01, 0x00, 0x00, 0x48, 0x8D, 0x0D, 0xC2, 0x39,
0x00, 0x00, 0x48, 0x63, 0x04, 0x81, 0x48, 0x01, 0xC8, 0xFF, 0xE0, 0xB9, 0x03, 0x00,
0x00, 0x00, 0x44, 0x89, 0xEA, 0xE8, 0x08, 0xEE, 0x02, 0x00, 0x4C, 0x89, 0xF8, 0x48,
0x83, 0xE0, 0xFD, 0x48, 0x3D, 0xE5, 0x00, 0x00, 0x00, 0x0F, 0x85, 0xDF, 0x02, 0x00,
0x00, 0x81, 0xFE, 0x00, 0x01, 0x00, 0x00, 0x0F, 0x85, 0x8C, 0x24, 0x00, 0x00, 0x4C,
0x89, 0xB4, 0x24, 0x10, 0x01, 0x00, 0x00, 0xC7, 0x84, 0x24, 0x18, 0x01, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x4C, 0x89, 0xBC, 0x24, 0x20, 0x01, 0x00, 0x00, 0x41, 0x81,
0xE5, 0xFF, 0xDF, 0x00, 0x00, 0x4C, 0x89, 0xAC, 0x24, 0x28, 0x01, 0x00, 0x00, 0x48,
0x8D, 0x8C, 0x24, 0x10, 0x01, 0x00, 0x00, 0xFF, 0x15, 0x89, 0x10, 0x12, 0x00, 0x31,
0xF6, 0xE9, 0x61, 0x24, 0x00, 0x00, 0x81, 0xFE, 0xDF, 0x02, 0x00, 0x00, 0x0F, 0x8F,
0x91, 0x01, 0x00, 0x00, 0x8D, 0x86, 0x00, 0xFE, 0xFF, 0xFF, 0x83, 0xF8, 0x32, 0x0F,
0x87, 0xBF, 0x03, 0x00, 0x00, 0x48, 0x8D, 0x0D, 0x0C, 0x3B, 0x00, 0x00, 0x48, 0x63,
0x04, 0x81, 0x48, 0x01, 0xC8, 0xFF, 0xE0, 0x4C, 0x39, 0x3D, 0x88, 0x80, 0x12, 0x00,
0x75, 0x15, 0x4C, 0x39, 0x2D, 0x87, 0x80, 0x12, 0x00, 0x75, 0x0C, 0x81, 0x3D, 0x57,
0x80, 0x12, 0x00, 0x00, 0x02, 0x00, 0x00, 0x74, 0x1F, 0xB1, 0x01, 0xE8, 0x5A, 0x5E,
0x00, 0x00, 0x4C, 0x89, 0x3D, 0x63, 0x80, 0x12, 0x00, 0x4C, 0x89, 0x2D, 0x64, 0x80,
0x12, 0x00, 0xC7, 0x05, 0x36, 0x80, 0x12, 0x00, 0x00, 0x02, 0x00, 0x00, 0xB9, 0x05,
0x00, 0x00, 0x00, 0x44, 0x89, 0xEA, 0xE8, 0x35, 0xED, 0x02, 0x00, 0x41, 0xF6, 0xC7,
0x13, 0x0F, 0x84, 0x1F, 0x1E, 0x00, 0x00, 0xFF, 0x15, 0x5D, 0x0D, 0x12, 0x00, 0x4C,
0x39, 0xF0, 0x0F, 0x85, 0x10, 0x1E, 0x00, 0x00, 0x31, 0xC0, 0x41, 0xF6, 0xC7, 0x10,
0x0F, 0x94, 0xC0, 0x83, 0xC8, 0x02, 0x41, 0xF6, 0xC7, 0x01, 0xBE, 0x01, 0x00, 0x00,
0x00, 0x0F, 0x44, 0xF0, 0xE8, 0x6F, 0xB3, 0x00, 0x00, 0x41, 0x89, 0xC6, 0x44, 0x89,
0xFF, 0x83, 0xE7, 0x08, 0xC1, 0xEF, 0x03, 0x41, 0x83, 0xE7, 0x04, 0x41, 0xC1, 0xEF,
0x02, 0x44, 0x89, 0xE9, 0xC1, 0xF9, 0x10, 0x8B, 0x2D, 0x4F, 0x77, 0x12, 0x00, 0x89,
0xC8, 0x29, 0xE8, 0x83, 0xC0, 0x01, 0x45, 0x85, 0xED, 0x0F, 0x49, 0xC1, 0x2B, 0x05,
0x94, 0x77, 0x12, 0x00, 0x99, 0xF7, 0xFD, 0x89, 0xC5, 0x44, 0x89, 0xE9, 0xC1, 0xE1,
0x10, 0x41, 0x0F, 0xBF, 0xD5, 0x8B, 0x1D, 0x23, 0x77, 0x12, 0x00, 0x89, 0xD0, 0x29,
0xD8, 0x83, 0xC0, 0x01, 0x85, 0xC9, 0x0F, 0x49, 0xC2, 0x2B, 0x05, 0x71, 0x77, 0x12,
0x00, 0x99, 0xF7, 0xFB, 0x89, 0xC3, 0x89, 0xF1, 0xE8, 0x69, 0xB3, 0x00, 0x00, 0x48,
0x8B, 0x0D, 0x4A, 0x77, 0x12, 0x00, 0x44, 0x88, 0x74, 0x24, 0x40, 0x40, 0x88, 0x7C,
0x24, 0x38, 0x44, 0x88, 0x7C, 0x24, 0x30, 0x89, 0x6C, 0x24, 0x28, 0x89, 0x5C, 0x24,
0x20, 0x89, 0xF2, 0x41, 0x89, 0xC0, 0x41, 0xB9, 0x04, 0x00, 0x00, 0x00, 0xE8, 0x7B,
0x25, 0x01, 0x00, 0x31, 0xF6, 0xE9, 0x1B, 0x23, 0x00, 0x00, 0x8D, 0x46, 0xFF, 0x83,
0xF8, 0x50, 0x0F, 0x87, 0x42, 0x0B, 0x00, 0x00, 0x48, 0x8D, 0x0D, 0xB1, 0x36, 0x00,
0x00, 0x48, 0x63, 0x04, 0x81, 0x48, 0x01, 0xC8, 0xFF, 0xE0, 0x48, 0x8B, 0x0D, 0x99,
0x6B, 0x12, 0x00, 0xBA, 0x89, 0x00, 0x00, 0x00, 0xE8, 0x37, 0xF6, 0x03, 0x00, 0x80,
0x3D, 0x90, 0x5C, 0x12, 0x00, 0x00, 0x75, 0x0B, 0xB9, 0x01, 0x00, 0x00, 0x00, 0xFF,
0x15, 0xD3, 0x0E, 0x12, 0x00, 0xC6, 0x05, 0x7C, 0x5C, 0x12, 0x00, 0x01, 0x31, 0xF6,
0x31, 0xC9, 0xFF, 0x15, 0xE2, 0x0D, 0x12, 0x00, 0xE9, 0xC4, 0x22, 0x00, 0x00, 0x81,
0xFE, 0x10, 0x03, 0x00, 0x00, 0x7F, 0x6B, 0x81, 0xFE, 0xE0, 0x02, 0x00, 0x00, 0x0F,
0x84, 0xF5, 0x01, 0x00, 0x00, 0x81, 0xFE, 0x07, 0x03, 0x00, 0x00, 0x0F, 0x84, 0xC1,
0x01, 0x00, 0x00, 0x81, 0xFE, 0x0F, 0x03, 0x00, 0x00, 0x0F, 0x85, 0xCB, 0x0A, 0x00,
0x00, 0x48, 0x83, 0x3D, 0x09, 0x77, 0x12, 0x00, 0x00, 0x0F, 0x84, 0xC7, 0x1C, 0x00,
0x00, 0xE8, 0x3E, 0x89,
];
let result = detect_mime_type(file_data);
println!("Test case 3: putty.exe: {:?}", result);
assert!(result.is_ok());
assert_eq!(
result.unwrap(),
"application/vnd.microsoft.portable-executable"
);
}
}

View File

@ -55,7 +55,7 @@ pub async fn serve_file(req: &mut Request, res: &mut Response) {
let status_code = StatusCode::NOT_FOUND;
render_template(res, &headers, template_filename, template, status_code).await;
} else {
res.set_status_code(StatusCode::NOT_FOUND);
res.status_code(StatusCode::NOT_FOUND);
res.render(Text::Json(r#"{"error": "FileNotFound"}"#));
}
@ -81,7 +81,7 @@ pub async fn serve_file(req: &mut Request, res: &mut Response) {
let status_code = StatusCode::INTERNAL_SERVER_ERROR;
render_template(res, &headers, template_filename, template, status_code).await;
} else {
res.set_status_code(StatusCode::UNAUTHORIZED);
res.status_code(StatusCode::UNAUTHORIZED);
res.render(Text::Json(r#"{"error": "InternalServerError"}"#));
}
}
@ -91,9 +91,7 @@ pub async fn serve_file(req: &mut Request, res: &mut Response) {
tracing::info!("New File View: {:?}", &filename.to_string());
// Read the list of unsafe mimetypes from the config
let mime_unsafe = CONFIG
.get_array("operations.unsafe_mimetype")
.expect("Couldn't find 'unsafe_mimetype' in config. :(");
let mime_unsafe = &CONFIG.operations.unsafe_mimetype;
// Get the mimetype of the file.
let mut mimetype: String = db::get_mimetype(sqlconn, &filename).await.unwrap();
@ -103,7 +101,7 @@ pub async fn serve_file(req: &mut Request, res: &mut Response) {
// The predicate in this case is a closure that checks if the current mime type matches the unsafe mime type.
if mime_unsafe
.iter() // Iterate over the collection
.any(|mime| mime.clone().into_string().unwrap() == mimetype.clone())
.any(|mime| mime.clone() == mimetype.clone())
{
// Check if any of the unsafe mime types match the given mimetype
tracing::info!("Unsafe Extension Filtered: {:?}", mimetype);
@ -137,9 +135,7 @@ pub async fn serve_file(req: &mut Request, res: &mut Response) {
engine::calculate_expiry(sqlconn, filename.clone(), filesize).await;
// Check if nginx sendfile is enabled, because we can skip this if it is.
let nginxsendfile = CONFIG
.get_bool("server.nginx_sendfile")
.expect("Couldn't find 'nginx_sendfile' in config. :(");
let nginxsendfile = CONFIG.server.nginx_sendfile;
if nginxsendfile {
// Add the X-Accel-Redirect header, allowing for faster file serving.
@ -154,7 +150,7 @@ pub async fn serve_file(req: &mut Request, res: &mut Response) {
tracing::debug!("response headers: {:?}", res.headers());
// https://github.com/salvo-rs/salvo/issues/233
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
} else {
// If nginx sendfile is disabled, we need to render the file directly
@ -170,7 +166,7 @@ pub async fn serve_file(req: &mut Request, res: &mut Response) {
// Go through all the headers and print them out, just to check for now!
tracing::debug!("response headers: {:?}", res.headers());
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
}
}
};

View File

@ -16,7 +16,7 @@ use crate::{db, handlers::TemplateStruct, SQLITE};
#[handler]
pub async fn serve_static(req: &mut Request, res: &mut Response) {
let headers = &req.headers().clone();
let remote_addr = &req.remote_addr().unwrap().clone();
let remote_addr = &req.remote_addr().clone();
let host = headers[HOST].to_str().unwrap_or("localhost:8282");
let template_host_path = PathBuf::from("./templates/").join(host);
let template_host_path_default = PathBuf::from("./templates/localhost:8282");
@ -38,7 +38,7 @@ pub async fn serve_static(req: &mut Request, res: &mut Response) {
..Default::default()
};
let rendered = tpls.get("services.html").unwrap().render(&template);
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Html(rendered));
}
"/about" => {
@ -52,7 +52,7 @@ pub async fn serve_static(req: &mut Request, res: &mut Response) {
..Default::default()
};
let rendered = tpls.get("about.html").unwrap().render(&template);
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Html(rendered));
}
"/faq" => {
@ -66,7 +66,7 @@ pub async fn serve_static(req: &mut Request, res: &mut Response) {
..Default::default()
};
let rendered = tpls.get("faq.html").unwrap().render(&template);
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Html(rendered));
}
"/dmca" => {
@ -81,7 +81,7 @@ pub async fn serve_static(req: &mut Request, res: &mut Response) {
..Default::default()
};
let rendered = tpls.get("dmca.html").unwrap().render(&template);
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Html(rendered));
}
"/welcome" => {
@ -96,7 +96,7 @@ pub async fn serve_static(req: &mut Request, res: &mut Response) {
..Default::default()
};
let rendered = tpls.get("welcome.html").unwrap().render(&template);
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Html(rendered));
}
"/czb" => {
@ -111,7 +111,7 @@ pub async fn serve_static(req: &mut Request, res: &mut Response) {
..Default::default()
};
let rendered = tpls.get("czb.html").unwrap().render(&template);
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Html(rendered));
}
"/qr" => {
@ -138,7 +138,7 @@ pub async fn serve_static(req: &mut Request, res: &mut Response) {
..Default::default()
};
let rendered = tpls.get("qr.html").unwrap().render(&template);
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Html(rendered));
}
_ => {

View File

@ -1,7 +1,8 @@
use chrono::{TimeZone, Utc};
use salvo::{handler, hyper::header::HOST, prelude::StatusCode, writer::Text, Request, Response};
use std::{
fs,
fs::{self, File},
io::Read,
path::{Path, PathBuf},
time::SystemTime,
};
@ -9,8 +10,8 @@ use std::{
use super::guess_ip;
use crate::{
db, engine,
handlers::{is_mimetype_banned, render_template, TemplateStruct},
CONFIG, SQLITE, MAGIC,
handlers::{detect_mime_type, is_mimetype_banned, render_template, TemplateStruct},
CONFIG, SQLITE,
};
/// This file handles (heh) uploading files to the server.
@ -20,7 +21,7 @@ pub async fn upload(req: &mut Request, res: &mut Response) {
let sqlconn = SQLITE.get().unwrap();
// Clone out the header and remote_addr so we can guess the ip later
let headers = &req.headers().clone();
let remote_addr = &req.remote_addr().unwrap().clone();
let remote_addr = &req.remote_addr().clone();
// Get the host header for nicely setting up the response.
let host = headers[HOST].to_str().unwrap_or("localhost:8282");
tracing::debug!("upload(req): {:?}", req);
@ -45,16 +46,21 @@ pub async fn upload(req: &mut Request, res: &mut Response) {
if let Some(file) = req.file("file").await {
// Generate new filename.
// Set up the filename length from the config
let length = CONFIG
.get_int("operations.filename_size")
.expect("Couldn't find 'filename_size' in config. :(") as usize;
let length = CONFIG.operations.filename_size as usize;
let filename =
engine::generate_filename(length, file.name().unwrap_or("file").to_string()).await;
// Guess the mimetype from the file.
let file_path = file.path();
let mimetype = tree_magic_mini::from_filepath(&file_path)
.unwrap_or("text/plain");
// Get the temporary file
let mut mime_file = File::open(file.path()).expect("Error opening file");
let mut buffer = vec![0; 2048];
// Load up 2048 bytes (There wasn't a decent answer on how much or little I should read)
mime_file
.read_exact(&mut buffer)
.expect("Error reading file");
// Guess the mimetype
let mimetype: &str = &detect_mime_type(&buffer).unwrap_or("text/plain".to_string());
tracing::debug!("upload(mimetype): {:?}", mimetype);
@ -75,7 +81,7 @@ pub async fn upload(req: &mut Request, res: &mut Response) {
render_template(res, headers, template_filename, template, status_code).await;
} else {
// Otherwise, render the error in json for the API.
res.set_status_code(StatusCode::FORBIDDEN);
res.status_code(StatusCode::FORBIDDEN);
res.render(Text::Json(r#"{"error": "BlockedFiletype"}"#));
}
} else {
@ -111,7 +117,7 @@ pub async fn upload(req: &mut Request, res: &mut Response) {
let status_code = StatusCode::INTERNAL_SERVER_ERROR;
render_template(res, headers, template_filename, template, status_code).await;
} else {
res.set_status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Json(r#"{"error": "InternalServerError"}"#));
}
} else {
@ -189,7 +195,7 @@ pub async fn upload(req: &mut Request, res: &mut Response) {
let status_code = StatusCode::OK;
render_template(res, headers, template_filename, template, status_code).await;
} else {
res.set_status_code(StatusCode::OK);
res.status_code(StatusCode::OK);
res.render(Text::Json(format!(
r#"{{"file": "{}", "url": "{}", "adminurl": "{}", "expiry": {}}}"#,
@ -214,7 +220,7 @@ pub async fn upload(req: &mut Request, res: &mut Response) {
let status_code = StatusCode::BAD_REQUEST;
render_template(res, headers, template_filename, template, status_code).await;
} else {
res.set_status_code(StatusCode::BAD_REQUEST);
res.status_code(StatusCode::BAD_REQUEST);
res.render(Text::Json(r#"{"error": "BadRequest"}"#));
};
}

View File

@ -1,20 +1,23 @@
use figment::providers::{Format, Toml};
use figment::Figment;
use magic::Cookie;
use once_cell::sync::OnceCell;
use salvo::prelude::*;
use salvo::serve_static::{StaticDir, StaticFile};
use sqlx::SqlitePool;
use config::Config;
use lazy_static::lazy_static;
use std::fs::create_dir_all;
use std::time::Duration;
use tokio::{task, time};
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::fmt;
use tracing_subscriber::prelude::*;
use crate::config::AppConfig;
use crate::db::DatabaseType;
use crate::handlers::cleaner_thread;
// Import sub-modules.
mod config;
mod db;
mod engine;
@ -30,21 +33,40 @@ mod handlers;
// Setup the global sqlite db
static SQLITE: OnceCell<SqlitePool> = OnceCell::new();
// Initialise the magic db
// This is thread-local, so each thread will have it's own database loaded.
// It's probably not the best way of doing it
// and we could probably have a dedicated thread for mimetypes but idk i'm not that smart
thread_local! {
static COOKIE: Cookie = {
let cookie = Cookie::open(magic::CookieFlags::MIME_TYPE).unwrap();
// Load the default database
cookie.load::<&str>(&[]).unwrap();
cookie
};
}
// Setup the config globally because I can't figure out how to pass it to functions I don't call directly.
// This is evaluated at runtime, and not compilation. \o/
lazy_static! {
pub static ref CONFIG: Config = Config::builder()
.add_source(config::File::with_name("config.toml"))
.build()
.unwrap();
pub static ref CONFIG: AppConfig = {
let config = Figment::new()
.merge(Toml::file("config.toml"))
.extract()
.unwrap_or_else(|error| {
eprintln!("Error loading configuration: {}", error);
std::process::exit(1);
});
tracing::debug!("full config: {:?}", config);
config
};
}
#[tokio::main]
async fn main() {
// Read log level from config
let log_config = CONFIG
.get_string("logging.level")
.expect("Couldn't find 'sql_backend' in config. :(");
let log_config = &CONFIG.logging.level;
// Construct tracing_subscriber with the filter from config.
tracing_subscriber::registry()
@ -53,13 +75,9 @@ async fn main() {
.init();
// Determine what SQL engine we're running.
let sql_backend = CONFIG
.get_string("database.sql_backend")
.expect("Couldn't find 'sql_backend' in config. :(");
let sql_backend = &CONFIG.database.sql_backend;
let db_url = CONFIG
.get_string("database.url")
.expect("Couldn't find 'url' in config. :(");
let db_url = &CONFIG.database.url;
// Match on the database type
match DatabaseType::from_str(&sql_backend) {
@ -94,15 +112,11 @@ async fn main() {
};
// Initialise the cleaner task
let interval = CONFIG
.get_int("operations.cleaner_interval")
.expect("Couldn't find 'cleaner_interval' in config. :(");
tracing::debug!("cleaner_interval: {}", interval);
cleaner_thread(
interval
.try_into()
.expect("Cleaner interval was too long to fit in a i32.... wow"),
tracing::info!(
"Starting cleaner thread every {} seconds",
CONFIG.operations.cleaner_interval
);
cleaner_thread(CONFIG.operations.cleaner_interval);
// Attempt to create the files directory
create_dir_all("files").unwrap();
@ -110,7 +124,6 @@ async fn main() {
// Internal routing
let router = Router::new()
// Main / functions
.hoop(Logger)
.get(handlers::index::index)
.post(handlers::upload::upload)
// Static Pages
@ -123,10 +136,9 @@ async fn main() {
.push(Router::with_path("/welcome").get(handlers::serve_static::serve_static))
.push(Router::with_path("/metrics").get(handlers::serve_metrics::serve_metrics))
.push(Router::with_path("/favicon.ico").get(StaticFile::new("static/favicon32.webp")))
.push(Router::with_path("/robots.txt").get(StaticFile::new("static/robots.txt")))
// Static File Serving
.push(
Router::with_path("static/<**path>").get(StaticDir::new("static/").with_listing(true)),
)
.push(Router::with_path("static/<**path>").get(StaticDir::new("static/").listing(true)))
// Deletion API
.push(
Router::with_path("/delete/<adminkey>")
@ -144,62 +156,21 @@ async fn main() {
.push(Router::with_path("<file>").get(handlers::serve_file::serve_file));
// Read environment variables for host and port
let host = CONFIG
.get_string("server.host")
.expect("Couldn't find 'host' in config. :(");
let port = CONFIG
.get_int("server.port")
.expect("Couldn't find 'port' in config. :(");
let host = &CONFIG.server.host;
let port = CONFIG.server.port;
let server_url = format!("{}:{}", host, port);
tracing::info!("Listening on http://{}", server_url);
Server::new(TcpListener::bind(&server_url))
.serve(router)
.await;
// TODO: Allow us to listen on a socket too?
// let acceptor = UnixListener::new("/tmp/salvo.sock").bind().await;
let acceptor = TcpListener::new(&server_url).bind().await;
Server::new(acceptor).serve(router).await;
// Close SQLite before closing
tracing::info!("Attempting to safely shut down Ephemeral.");
SQLITE.get().expect("Problem while safely closing the database :(").close().await;
}
// This spawns a tokio task to run a interval timer forever.
// the interval timer runs every 'period' seconds.
pub fn cleaner_thread(period: i32) {
let _forever = task::spawn(async move {
let mut interval = time::interval(Duration::from_secs(period.try_into().unwrap()));
let sqlconn = SQLITE.get().unwrap();
loop {
// Wait for the next interval
interval.tick().await;
// Get a vec of files that will expire this loop.
// Also I'm sorta just realising how annoying it is to pass sqlconn through like 4 functions deep just to do something.
let old_files_result = db::get_old_files(sqlconn).await;
match old_files_result {
Ok(file_list) => {
tracing::info!("Running Cleaner");
for file in file_list {
// Delete the file from the database
db::delete_file(sqlconn, &file).await.unwrap_or_else(|err| {
tracing::error!(
"Failed to delete file from database: {}, error: {:?}",
&file,
err
);
0
});
// Delete the file from the filesystem
engine::delete_file(&file).await.unwrap_or_else(|err| {
tracing::error!("Failed to delete file from database: {:?}", err);
});
}
tracing::info!("Cleaner finished");
}
Err(err) => {
tracing::error!("Error getting files to expire: {}", err);
// Return a empty Vec so it doesn't delete anything
return Vec::<String>::new();
}
}
}
});
SQLITE
.get()
.expect("Problem while safely closing the database :(")
.close()
.await;
}

0
static/robots.txt Normal file
View File