commit 7d73f63cd4f6b0d2023939c023d027e27c6abc4c Author: Emi Simpson Date: Mon Nov 8 23:09:57 2021 -0500 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..189288c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,270 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "argh" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f023c76cd7975f9969f8e29f0e461decbdc7f51048ce43427107a3d192f1c9bf" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ad219abc0c06ca788aface2e3a1970587e3413ab70acd20e54b6ec524c1f8f" +dependencies = [ + "argh_shared", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33" + +[[package]] +name = "ascii" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "faery-ring" +version = "0.1.0" +dependencies = [ + "argh", + "tiny_http", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tiny_http" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96155b5f7149ba7a99ea5d516c538250b26eab60b4485c0f5344432573e7a450" +dependencies = [ + "ascii", + "chrono", + "chunked_transfer", + "log", + "url", +] + +[[package]] +name = "tinyvec" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fbac180 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "faery-ring" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tiny_http = "0.9.0" +argh = "0.1.6" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6804657 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,84 @@ +pub mod server; + +use std::io::Read; +use std::process::exit; +use std::fs::File; +use std::io::ErrorKind; +use std::path::PathBuf; + +use argh; + +fn main() { + let args: CommandlineArgs = argh::from_env(); + + // + // Step 1: Read the file + // + let mut config = match File::open(&args.domains) { + Ok(file) => file, + Err(e) => { + match e.kind() { + ErrorKind::NotFound => { + eprintln!("Domains file not found: {}", args.domains.display()); + exit(1001); + }, + ErrorKind::PermissionDenied => { + eprintln!( + "Permission denied to read domain file: {}", + args.domains.display() + ); + exit(1002); + }, + _ => { + eprintln!( + "Unexepected error opening domain file: {}", + args.domains.display() + ); + exit(1000); + } + } + } + }; + let mut read_into = String::with_capacity(1_000); + match config.read_to_string(&mut read_into) { + Ok(_) => {}, + Err(e) => { + match e.kind() { + ErrorKind::BrokenPipe => { + eprintln!("Broken pipe (filesystem error) while reading {}", args.domains.display()); + exit(1000); + }, + ErrorKind::InvalidData => { + eprintln!( + "Domain file {} contains non-UTF-8 data. Are you sure this is a domains file?", + args.domains.display() + ); + exit(1003); + } + _ => { + eprintln!( + "Unexepected error opening domain file: {}", + args.domains.display() + ); + exit(1000); + } + } + } + }; + let domains: Vec<_> = read_into.split('\n') + .filter(|s| !s.is_empty()) + .collect(); + + // + // Step 2: Run the server + // + server::go(&domains); +} + +#[derive(argh::FromArgs)] +/// Run a basic webring hub +struct CommandlineArgs { + /// a text file listing all domains in the ring, one per line + #[argh(positional)] + domains: PathBuf, +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..5d0c7e8 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,114 @@ +use std::borrow::Cow; +use std::io::Cursor; +use std::ops::Deref; +use std::sync::Arc; + +use tiny_http::Header; +use tiny_http::Request; +use tiny_http::Response; +use tiny_http::Server; +use tiny_http::StatusCode; + +pub fn go(domains: &[&str]) { + let server = Server::http("0.0.0.0:3243").unwrap(); + let server = Arc::new(server); + + for req in server.incoming_requests() { + let route = Route::parse_request(&req); + let resp = route.render(domains); + if let Err(e) = req.respond(resp) { + eprintln!("WARN: {}", e); + } + } +} + +pub enum Route<'a> { + Next(&'a str), + Prev(&'a str), + NotFound, +} + +pub enum RuntimeStatic { + JsonIndex, +} + +impl<'a> Route<'a> { + pub fn parse_request(r: &'a Request) -> Self { + let segments: Vec<_> = r.url() + .split('/') + .filter(|s| !s.is_empty()) + .collect(); + + // TODO automatically detect a fallback url from the request's Referer header + let referrer_domain = segments.get(1).map(|s| *s).unwrap_or("missing_referrer_domain"); + + match segments.get(0).map(Deref::deref) { + Some("next") => { + Route::Next(referrer_domain) + }, + Some("prev") => { + Route::Prev(referrer_domain) + }, + _ => Route::NotFound, + } + } + + pub fn render<'b>(&self, domains: &[&'b str]) -> Response>> { + match self { + Self::Next(this_domain) | Self::Prev(this_domain) => { + + let offset: isize = if let Self::Next(_) = self { + 1 + } else { + -1 + }; + + let destination = domains.iter() + .position(|d| this_domain.starts_with(d)) + .map(|i| (i as isize + offset).rem_euclid(domains.len() as isize)) + .map(|i| domains[i as usize]); + + if let Some(url) = destination { + let destination = format!( + "https://{}", + url + ); + Response::new( + StatusCode(303), + vec![ + Header::from_bytes( + b"Location".to_owned(), + destination.as_bytes(), + ).unwrap(), + ], + Cursor::new(Cow::Borrowed(b"")), + Some(0), + None, + ) + } else { + let bytes = format!( + "Misconfigured server. Website {} not found in webring.", + this_domain, + ); + let blen = bytes.len(); + Response::new( + StatusCode(400), + Vec::new(), + Cursor::new(Cow::Owned(bytes.into_bytes())), + Some(blen), + None, + ) + } + }, + Self::NotFound => { + Response::new( + StatusCode(404), + Vec::new(), + Cursor::new(Cow::Borrowed(b"Page not found")), + Some(14), + None, + ) + } + } + } +}