Add in file serving!

main
Volkor 2022-10-02 04:51:32 +11:00
parent 95e27073f4
commit ade6c0ad67
Signed by: Volkor
GPG Key ID: BAD7CA8A81CC2DA5
16 changed files with 423 additions and 97 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=postgres://ephemeral:ephemeral@localhost/ephemeral

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
files.db
files.db
/files/

265
Cargo.lock generated
View File

@ -37,17 +37,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check 0.9.4",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -314,6 +303,36 @@ dependencies = [
"syn 1.0.100",
]
[[package]]
name = "diesel"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01e2adfd0a7a81070ed7beec0c62636458926326c16fedb77796d41e447b282d"
dependencies = [
"bitflags",
"byteorder",
"diesel_derives",
"itoa",
"libsqlite3-sys",
"mysqlclient-sys",
"percent-encoding 2.2.0",
"pq-sys",
"r2d2",
"url 2.3.1",
]
[[package]]
name = "diesel_derives"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a7ab9d7967e6a1a247ea38aedf88ab808b4ac0c159576bc71866ab8f9f9250"
dependencies = [
"proc-macro-error",
"proc-macro2 1.0.44",
"quote 1.0.21",
"syn 1.0.100",
]
[[package]]
name = "digest"
version = "0.10.5"
@ -325,6 +344,35 @@ dependencies = [
"subtle",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi 0.3.9",
]
[[package]]
name = "dotenvy"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9155c8f4dc55c7470ae9da3f63c6785245093b3f6aeb0f5bf2e968efbba314"
dependencies = [
"dirs",
]
[[package]]
name = "either"
version = "1.8.0"
@ -344,25 +392,17 @@ dependencies = [
name = "ephemeral"
version = "0.1.0"
dependencies = [
"diesel",
"dotenvy",
"nanoid",
"r2d2",
"rocket 0.5.0-rc.2",
"rocket-multipart-form-data",
"rocket_contrib",
"rusqlite",
"serde",
"serde_json",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "1.8.0"
@ -404,6 +444,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding 2.2.0",
]
[[package]]
name = "fsevent"
version = "0.4.0"
@ -578,18 +627,6 @@ name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
dependencies = [
"hashbrown",
]
[[package]]
name = "hermit-abi"
@ -668,7 +705,7 @@ dependencies = [
"traitobject",
"typeable",
"unicase",
"url",
"url 1.7.2",
]
[[package]]
@ -706,6 +743,16 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.1"
@ -816,7 +863,6 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
@ -975,6 +1021,25 @@ dependencies = [
"version_check 0.9.4",
]
[[package]]
name = "mysqlclient-sys"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61b381528ba293005c42a409dd73d034508e273bf90481f17ec2e964a6e969b"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "nanoid"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8"
dependencies = [
"rand",
]
[[package]]
name = "net2"
version = "0.2.37"
@ -1151,6 +1216,39 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "pq-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b845d6d8ec554f972a2c5298aad68953fd64e7441e846075450b44656a016d1"
dependencies = [
"vcpkg",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.44",
"quote 1.0.21",
"syn 1.0.100",
"version_check 0.9.4",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2 1.0.44",
"quote 1.0.21",
"version_check 0.9.4",
]
[[package]]
name = "proc-macro2"
version = "0.4.30"
@ -1200,6 +1298,17 @@ dependencies = [
"proc-macro2 1.0.44",
]
[[package]]
name = "r2d2"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
dependencies = [
"log 0.4.17",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -1239,6 +1348,17 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "ref-cast"
version = "1.0.9"
@ -1340,6 +1460,7 @@ dependencies = [
"rocket_codegen 0.5.0-rc.2",
"rocket_http 0.5.0-rc.2",
"serde",
"serde_json",
"state 0.5.3",
"tempfile",
"time 0.3.14",
@ -1351,6 +1472,18 @@ dependencies = [
"yansi",
]
[[package]]
name = "rocket-multipart-form-data"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a4b5ad1a62bfa8a250204c41cf02c94cf9ef135a680669cdb39dbbeb8b0132e"
dependencies = [
"mime 0.3.16",
"multer",
"rocket 0.5.0-rc.2",
"tokio-util",
]
[[package]]
name = "rocket_codegen"
version = "0.4.11"
@ -1439,20 +1572,6 @@ dependencies = [
"uncased",
]
[[package]]
name = "rusqlite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustversion"
version = "1.0.9"
@ -1480,6 +1599,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf"
dependencies = [
"parking_lot",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
@ -1649,6 +1777,26 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "thiserror"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [
"proc-macro2 1.0.44",
"quote 1.0.21",
"syn 1.0.100",
]
[[package]]
name = "thread_local"
version = "1.1.4"
@ -1945,11 +2093,22 @@ version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
dependencies = [
"idna",
"idna 0.1.5",
"matches",
"percent-encoding 1.0.1",
]
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna 0.3.0",
"percent-encoding 2.2.0",
]
[[package]]
name = "valuable"
version = "0.1.0"

View File

@ -7,8 +7,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rocket = "0.5.0-rc.2"
rocket = { version = "0.5.0-rc.2", features = ["json"] }
rocket_contrib = "0.4.11"
rusqlite = { version = "0.28.0", features = ["bundled"] }
diesel = { version = "2.0.0", features = ["postgres", "sqlite", "mysql", "r2d2"] }
dotenvy = "0.15"
serde = "1.0.145"
serde_json = "1.0.85"
r2d2 = "0.8.10"
rocket-multipart-form-data = "0.10.3"
nanoid = "0.4.0"

8
diesel.toml Normal file
View File

@ -0,0 +1,8 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"
[migrations_directory]
dir = "migrations"

View File

@ -23,6 +23,10 @@ Returns 401 if the server requires a key to upload files.
GET - Returns the webpage, so be careful.
## File Serving - `/<file>.[ext]`
GET - Retrieves the file with the given ID, performing syntax highlighting and rendering a template if matching known plain-text file. (otherwise just plaintext, OR rendered by browser)
## File information - `/edit/<key>`
Returns information about the file with the corresponding key.
@ -34,7 +38,7 @@ Returns information about the file with the corresponding key.
If the key is invalid returns a 404, same with all children API Endpoints
## File Deletion - `/edit/<key>/delete` (also `/delete/<key>` for backwards compatibility.)
## File Deletion - `/edit/<key>` (also `/delete/<key>` for backwards compatibility.)
Accepts GET and DELETE - as long as it has the keys.

View File

@ -7,6 +7,7 @@ Database should store everything in 1 db, multiple 2 or 3, because easy.
### files table
- **file** as text - contains the filename
- **filetype** as text - contains the file extension.
- **expiry** as int - contains unix time of when the file will be deleted - AS COMPUTED BY ENGINE MODE. (this is kept as is, never changed after initial upload)
- **expiry_override** - _can_ contain unixtime of when the file will actaully be deleted, if it's set, this is used for overriding files.
- **views** - counts up for each view to the file.
@ -14,6 +15,7 @@ Database should store everything in 1 db, multiple 2 or 3, because easy.
- **adminkey** - key for deletion and setting expiry_override
- **accessed** - last time the file was accessed
- **filesize** - size of the file, in kb or something idk
- **ip** - IP Address of the uploader, for legal and statistics purposes!
### qrscan table
@ -24,3 +26,10 @@ Database should store everything in 1 db, multiple 2 or 3, because easy.
### stats table
Hopefully we can move away from having a separate stats table, and use metrics generated from the files table instead.
## PostgreSQL installation
1. `psql -U <admin>`
2. `CREATE DATABASE ephemeral;`
3. `CREATE USER ephemeral WITH ENCRYPTED PASSWORD 'yourpass';`
4. `GRANT ALL PRIVILEGES ON DATABASE ephemeral TO ephemeral;`

0
migrations/.keep Normal file
View File

View File

@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

View File

@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE files;

View File

@ -0,0 +1,13 @@
-- Your SQL goes here
CREATE TABLE files (
file TEXT PRIMARY KEY,
filetype TEXT,
expiry INT NOT NULL,
expiry_override INT,
views INT NOT NULL,
is_deleted INT NOT NULL, -- Treated as a bool
adminkey TEXT NOT NULL,
accessed INT NOT NULL,
filesize INT NOT NULL,
ip TEXT NOT NULL -- IP Addr that uploaded the file.
);

View File

@ -1,28 +1,2 @@
// Ephemeral Database Library
// only for DB Stuff, should probably change this to SQLite or change libraries or something hey
pub fn create_files_db() {
// Initalise DB (this is bad, it'll crash entire program if db is bork'd)
let db_connection = rusqlite::Connection::open("files.db").unwrap();
// Create Table if it doesn't already exist.
db_connection.execute(
"create table if not exists files (
file text primary key,
expiry int,
expiry_override int,
views int,
isDeleted BOOLEAN NOT NULL CHECK (
isDeleted IN (0, 1)
),
adminkey text,
accessed int,
filesize int
);",
rusqlite::NO_PARAMS,
)
.unwrap();
}
//
// using Diesel.rs for Postgres, SQLite and MySQL! (Postgres is primary focus)

40
src/lib.rs Normal file
View File

@ -0,0 +1,40 @@
// 'Logic' of the web server
use std::borrow::Cow;
use std::fs::File;
use std::path::{Path, PathBuf};
use nanoid::nanoid;
// Generate a random name for the uploaded file.
pub struct FileName<'a>(Cow<'a, str>);
impl FileName<'_> {
// Uses https://github.com/nikolay-govorov/nanoid to generate a 6 (default) character long filename.
// Uses the characters A-Z, a-z, 0-9, and _- (VERY urlsafe :) )
// Code shamelessly stolen from https://rocket.rs/v0.5-rc/guide/pastebin-tutorial
pub async fn generate_filename(size: usize) -> FileName<'static> {
// This is how nanoid's documentation says to do it.
let filename = nanoid!(size);
FileName(Cow::Owned(filename))
}
}
use rocket::request::FromParam;
// Returns an instance of `FileName` if the path segment is a valid file.
// Otherwise returns the invalid filename as the `Err` value.
// Since the ONLY files that are inside the ./files/ directory, this is more
// than enough to protect against path disclosure attacks.
impl<'a> FromParam<'a> for FileName<'a> {
type Error = &'a str;
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
param.chars().all(|c| c.is_ascii_alphanumeric())
.then(|| FileName(param.into()))
.ok_or(param)
}
}

View File

@ -1,15 +1,49 @@
#[macro_use] extern crate rocket;
use rocket::{fs::{FileServer, relative}};
use std::path::{PathBuf, Path};
use rocket::{fs::{FileServer, relative, NamedFile}, Data, http::ContentType};
use rocket::tokio::fs::File;
use rocket::request::FromParam;
// For cleaner thread
use rocket::tokio::time::{sleep, Duration};
// Load Modules Locally
// mod conf
mod db;
mod lib;
use lib::FileName;
// Setup config stuffs
// Main Root page
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
async fn index() -> &'static str {
"
USAGE
POST /
accepts raw data in the body of the request and responds with a URL of
a page containing the body's content
GET /<filename>.[ext]
retrieves the content for the paste with id `<id>` (Code Highlighting running on supported .[ext]'s)
"
}
// Processing Upload files
// Serving Files
#[get("/<file>")]
async fn serve_file(file: &str) -> Option<File> {
// upload_dir is ./files/
let upload_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/", "files");
let filename = Path::new(upload_dir).join(file);
File::open(&filename).await.ok()
}
// About Page
@ -23,19 +57,39 @@ fn faq() -> &'static str {
"1. You're on your own until I can get templating working. ;)"
}
// Favicon
// #[get("/favicon.ico")]
// fn favicon() -> content::Custom<&'static [u8]> {
// content::Custom(ContentType::Icon, FAVICON.into())
// }
#[launch]
fn rocket() -> _ {
// Create DB if it doesn't exist already.
db::create_files_db();
// db::create_files_db();
rocket::build()
.mount("/", routes![index, about, faq])
.mount("/", routes![index, about, faq, serve_file])
.mount("/", FileServer::from(relative!("static")))
}
}
fn cleaner_thread() {
// Need to figure out the best way to get this to run every x seconds.
// Potentially a extra thread, JUST with a counter?
// ONLY RUNS if config says so!
// This thread reads the engine mode from the config, and runs the function for the right mode.
// This is a good example from rockets code.
// async fn delay(seconds: u64) -> String {
// sleep(Duration::from_secs(seconds)).await;
// format!("Waited for {} seconds", seconds)
// }
}
fn deletion_engine_1() {
// Loops through all files in db that aren't already deleted
// calls delete_file() on any files where last access is longer than expiry ago.
}
fn deletion_engine_2() {
// Loops through all files in db that aren't already deleted
// calls delete_file() on any files that match the 0x0 math stuff
}
// More engine modes later!

15
src/schema.rs Normal file
View File

@ -0,0 +1,15 @@
// @generated automatically by Diesel CLI.
diesel::table! {
files (file) {
file -> Text,
expiry -> Int4,
expiry_override -> Int4,
views -> Int4,
is_deleted -> Int4,
adminkey -> Text,
accessed -> Int4,
filesize -> Int4,
ip -> Text,
}
}