From: Scott Worley Date: Sat, 19 Nov 2022 03:25:24 +0000 (-0800) Subject: Record votes X-Git-Tag: v0.2.0~17 X-Git-Url: http://git.scottworley.com/voter/commitdiff_plain/fbcdf3ed7708b5b153a7f562f933d446ffc2d951 Record votes --- diff --git a/Cargo.lock b/Cargo.lock index 37716a9..5b80888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bytes" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "cc" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "cgi" version = "0.6.0" @@ -17,6 +35,38 @@ dependencies = [ "http", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fd-lock" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb21c69b9fea5e15dbc1049e4b77145dd0ba1c84019c488102de0dc4ea4b0a27" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys", +] + [[package]] name = "fnv" version = "1.0.7" @@ -34,15 +84,131 @@ dependencies = [ "itoa", ] +[[package]] +name = "io-lifetimes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "linux-raw-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb68f22743a3fb35785f1e7f844ca5a3de2dde5bd0c0ef5b372065814699b121" + +[[package]] +name = "rustix" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203974af07ea769452490ee8de3e5947971efc3a090dca8a779dd432d3fa46a7" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "voter" version = "0.1.0" dependencies = [ "cgi", + "fd-lock", +] + +[[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" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index c819d86..3764682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ description = "A simple web page that tracks votes" [dependencies] cgi = "0" +fd-lock = "3" diff --git a/src/main.rs b/src/main.rs index 15a1655..700cce0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ +use std::io::prelude::*; use std::path::{Path, PathBuf}; const DATA_PATH: &str = "/var/lib/voter"; +const COOKIE_NAME: &[u8] = b"__Secure-id"; +const COOKIE_LENGTH: usize = 32; fn validate_path(path: &str) -> Result { let invalid_path = || cgi::text_response(404, "Invalid path"); @@ -24,19 +27,64 @@ fn validate_path(path: &str) -> Result { Ok(dir) } +fn get_voter(request: &cgi::Request) -> Result<&[u8], cgi::Response> { + // Expect exactly one cookie, exactly as we generate it. + let cookie = request + .headers() + .get(cgi::http::header::COOKIE) + .map(|c| c.as_bytes()) + .and_then(|c| c.strip_prefix(COOKIE_NAME)) + .and_then(|c| c.strip_prefix(b"=")) + .ok_or_else(|| cgi::text_response(400, "Invalid cookie"))?; + if cookie.len() != COOKIE_LENGTH || cookie.contains(&b' ') || cookie.contains(&b';') { + Err(cgi::text_response(400, "Invalid cookie")) + } else { + Ok(cookie) + } +} + fn prompt_for_vote(dir: PathBuf, request: cgi::Request) -> Result { Err(cgi::text_response(503, "Not Implemented")) } +fn write_vote(dir: PathBuf, voter: &[u8], vote: &[u8]) -> std::io::Result<()> { + let datum = [voter, b" ", vote, b"\n"].concat(); + let vpath = dir.join("votes"); + let vfile = std::fs::File::options() + .append(true) + .create(true) + .open(vpath)?; + let mut vlock = fd_lock::RwLock::new(vfile); + vlock.write()?.write(&datum)?; + Ok(()) +} + fn record_vote(dir: PathBuf, request: cgi::Request) -> Result { - Err(cgi::text_response(503, "Not Implemented")) + let body = request.body(); + // Valid votes look like "0 foo" or "1 bar" + if body.len() < 3 + || (body[0] != b'0' && body[0] != b'1') + || body[1] != b' ' + || body.contains(&b'\n') + { + return Err(cgi::text_response(415, "Invalid vote")); + } + write_vote(dir, &get_voter(&request)?, body) + .map_err(|_| cgi::text_response(503, "Couldn't record vote"))?; + Ok(cgi::text_response(200, "Vote recorded")) +} + +fn strip_body(mut response: cgi::Response) -> cgi::Response { + response.body_mut().clear(); + response } fn respond(request: cgi::Request) -> Result { let dir = validate_path(request.uri().path())?; match request.method() { + &cgi::http::Method::HEAD => prompt_for_vote(dir, request).map(strip_body), &cgi::http::Method::GET => prompt_for_vote(dir, request), - &cgi::http::Method::POST => record_vote(dir, request), + &cgi::http::Method::PUT => record_vote(dir, request), _ => Err(cgi::text_response(405, "Huh?")), } }