From 4405448fa27951cf9b2a696795de8b3fce0e8c10 Mon Sep 17 00:00:00 2001 From: meeg_leeto Date: Sun, 27 Mar 2022 18:04:31 +0100 Subject: [PATCH] wip: many pieces moving, still. Working on many things, though it's starting to take shape; the configuration ouline and its loading (with figment) is well defined already; working on stabilizing the SQLite connection, and being able to insert a slug. --- Cargo.lock | 172 +++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 7 ++- src/main.rs | 177 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 327 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fba56e4..4e9e60f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[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", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -17,6 +28,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -173,13 +193,16 @@ dependencies = [ ] [[package]] -name = "envy" -version = "0.4.2" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" -dependencies = [ - "serde", -] +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" @@ -190,6 +213,20 @@ dependencies = [ "instant", ] +[[package]] +name = "figment" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "fnv" version = "1.0.7" @@ -293,6 +330,18 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] [[package]] name = "headers" @@ -407,6 +456,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "instant" version = "0.1.12" @@ -456,6 +511,16 @@ version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +[[package]] +name = "libsqlite3-sys" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb644c388dfaefa18035c12614156d285364769e818893da0dda9030c80ad2ba" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -485,7 +550,10 @@ name = "lonk" version = "0.1.0" dependencies = [ "base64", - "envy", + "either", + "figment", + "rusqlite", + "serde", "tokio", "validators", "warp", @@ -670,6 +738,29 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "pear" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -728,6 +819,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -743,6 +840,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -844,6 +954,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "rusqlite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -888,6 +1013,9 @@ name = "serde" version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -1123,6 +1251,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.1" @@ -1190,6 +1327,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "uncased" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.6.0" @@ -1283,6 +1429,12 @@ dependencies = [ "enum-ordinalize", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1405,3 +1557,9 @@ name = "windows_x86_64_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index 195176f..81f38b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,10 @@ edition = "2021" [dependencies] base64 = "0.13.0" -envy = "0.4.2" +either = "1.6.1" +figment = { version = "0.10.6", features = ["toml", "env"] } +rusqlite = "0.27.0" +serde = { version = "~1.0.136", features = ["derive"] } tokio = { version = "~1.17.0", features = ["full"] } -validators = "0.24.1" +validators = { version = "~0.24.1", features = ["url-dep"] } warp = "0.3.2" diff --git a/src/main.rs b/src/main.rs index 9977505..6d6858f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,92 @@ -use std::{collections::BTreeSet, str::FromStr}; - +use figment::{ + providers::{Format, Toml}, + Figment, +}; +use rusqlite::Connection; +use std::{collections::BTreeSet, str::FromStr, sync::Arc}; use validators::prelude::*; -use warp::{Filter, Reply}; +use warp::{hyper::StatusCode, Filter}; + +macro_rules! unwrap_or_unwrap_err { + ($x:expr) => { + match $x { + Ok(x) => x, + Err(y) => y, + } + }; +} + +mod config { + use serde::{Deserialize, Serialize}; + use std::{path::PathBuf, str::FromStr}; + use warp::{filters::BoxedFilter, Filter}; + + #[derive(Deserialize, Serialize, Debug, Clone)] + pub struct Config { + pub db_location: PathBuf, + pub slug_rules: SlugRules, + pub serve_rules: ServeRules, + } + + #[derive(Deserialize, Serialize, Debug, Clone)] + pub struct SlugRules { + pub length: usize, + pub chars: String, + } + + #[derive(Deserialize, Serialize, Debug, Clone)] + pub enum ServeRules { + File(PathBuf), + Dir(PathBuf), + } + + impl ServeRules { + pub fn to_filter(&self) -> BoxedFilter<(warp::fs::File,)> { + match self { + ServeRules::File(file) => warp::fs::file(file.clone()).boxed(), + ServeRules::Dir(dir) => warp::fs::dir(dir.clone()).boxed(), + } + } + } + + impl Default for SlugRules { + fn default() -> Self { + Self { + length: 5, + chars: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-" + .to_string(), + } + } + } + + impl Default for ServeRules { + fn default() -> Self { + ServeRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap()) + } + } + + impl Default for Config { + fn default() -> Self { + Self { + db_location: PathBuf::from_str("/etc/lonk/data.db").unwrap(), + slug_rules: Default::default(), + serve_rules: Default::default(), + } + } + } +} #[derive(Debug, Validator)] #[validator(base64_url(padding(NotAllow)))] struct Base64WithoutPaddingUrl(String); +#[derive(Validator)] +#[validator(domain(ipv4(Allow), local(Allow), at_least_two_labels(Allow), port(Allow)))] +struct Url { + domain: String, + port: Option, +} + impl FromStr for Base64WithoutPaddingUrl { type Err = ::Error; @@ -15,7 +95,22 @@ impl FromStr for Base64WithoutPaddingUrl { } } -struct SlugParser { +struct SlugDatabase(rusqlite::Connection); + +impl SlugDatabase { + fn from_connection(connection: rusqlite::Connection) -> Self { + // TODO: Check that the database has the necessary format + + SlugDatabase(connection) + } + + fn insert_slug(slug: Slug, url: Url) -> Result<(), ()> { + todo!(); + Ok(()) + } +} + +struct SlugFactory { slug_length: usize, slug_chars: BTreeSet, } @@ -27,34 +122,76 @@ enum InvalidSlug { BadChar, } -impl SlugParser { - fn slug_from_str(s: &str) -> Result { +impl SlugFactory { + fn from_rules(rules: config::SlugRules) -> Self { + let mut slug_chars = BTreeSet::::new(); + slug_chars.extend(rules.chars.chars()); + + SlugFactory { + slug_length: rules.length, + slug_chars, + } + } + + fn parse_str(&self, s: &str) -> Result { + for (i, char) in s.chars().enumerate() { + if i >= self.slug_length { + return Err(InvalidSlug::TooLong); + } + + if !self.slug_chars.contains(&char) { + return Err(InvalidSlug::BadChar); + } + } + + Ok(Slug(s.to_string())) + } + + fn generate(&self) -> Slug { todo!() } } -async fn shorten<'s>(b64url: &'s str) -> Result { - let url = base64::decode_config(b64url, base64::URL_SAFE_NO_PAD).map_err(|_| { - warp::reply::with_status(warp::reply(), warp::http::StatusCode::BAD_REQUEST) - })?; - todo!(); - Ok(warp::reply()) -} - -macro_rules! unwrap_and_err { - ($x: ident) => { - +fn shorten<'s>(slug_factory: &SlugFactory, db: SlugDatabase, b64url: &'s str) -> Result { + let url = { + let raw = base64::decode_config(b64url, base64::URL_SAFE_NO_PAD) + .map_err(|_| warp::http::StatusCode::BAD_REQUEST)?; + let url_str = std::str::from_utf8(&raw).map_err(|_| warp::http::StatusCode::BAD_REQUEST)?; + Url::parse_str(url_str).map_err(|_| warp::http::StatusCode::BAD_REQUEST)? }; + + let new_slug = slug_factory.generate(); + + Ok(warp::http::StatusCode::OK) } #[tokio::main] async fn main() { + // Read configuration + let config_file = std::env::var("LONK_CONFIG").unwrap_or("lonk.toml".to_string()); + let config: config::Config = Figment::new() + .merge(Toml::file(&config_file)) + .extract() + .expect("Could not parse configuration file."); + + // Create slug factory + let slug_factory = Arc::new(SlugFactory::from_rules(config.slug_rules)); + + // Initialize database + let db = Connection::open(config.db_location); + // GET / - let homepage = warp::path::end().and(warp::fs::file("index.html")); + let homepage = warp::path::end().and(config.serve_rules.to_filter()); // GET /shorten/:Base64WithoutPaddingUrl - let shorten = warp::path!("shorten" / Base64WithoutPaddingUrl) - .map(|link: Base64WithoutPaddingUrl| shorten(&link.0)); + let shorten = warp::path!("shorten" / Base64WithoutPaddingUrl).map({ + move |link: Base64WithoutPaddingUrl| { + warp::reply::with_status( + warp::reply(), + unwrap_or_unwrap_err!(shorten(&slug_factory, &link.0)), + ) + } + }); // GET /l/:Slug let link = warp::path("l")