]> git.scottworley.com Git - voter/commitdiff
Record votes
authorScott Worley <scottworley@scottworley.com>
Sat, 19 Nov 2022 03:25:24 +0000 (19:25 -0800)
committerScott Worley <scottworley@scottworley.com>
Sat, 19 Nov 2022 04:42:19 +0000 (20:42 -0800)
Cargo.lock
Cargo.toml
src/main.rs

index 37716a95e63b17e2ecec0b34d23ddd6d2e38b996..5b80888aa5e1f9b1c636f1b3510f3d7e273e2b1f 100644 (file)
@@ -2,12 +2,30 @@
 # It is not intended for manual editing.
 version = 3
 
 # 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 = "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"
 [[package]]
 name = "cgi"
 version = "0.6.0"
@@ -17,6 +35,38 @@ dependencies = [
  "http",
 ]
 
  "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"
 [[package]]
 name = "fnv"
 version = "1.0.7"
@@ -34,15 +84,131 @@ dependencies = [
  "itoa",
 ]
 
  "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 = "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",
 [[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"
index c819d86c5f02b85a16c5a99f605d55c3c54d1221..37646824b331c9f6ae3a2b1877eb4b7a724a8c0a 100644 (file)
@@ -9,3 +9,4 @@ description = "A simple web page that tracks votes"
 
 [dependencies]
 cgi = "0"
 
 [dependencies]
 cgi = "0"
+fd-lock = "3"
index 15a1655de920522e2526191dc6cd47fd9dbf5c3e..700cce03f3f7f4b74ac6654ea9e22a5709335631 100644 (file)
@@ -1,6 +1,9 @@
+use std::io::prelude::*;
 use std::path::{Path, PathBuf};
 
 const DATA_PATH: &str = "/var/lib/voter";
 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<PathBuf, cgi::Response> {
     let invalid_path = || cgi::text_response(404, "Invalid path");
 
 fn validate_path(path: &str) -> Result<PathBuf, cgi::Response> {
     let invalid_path = || cgi::text_response(404, "Invalid path");
@@ -24,19 +27,64 @@ fn validate_path(path: &str) -> Result<PathBuf, cgi::Response> {
     Ok(dir)
 }
 
     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<cgi::Response, cgi::Response> {
     Err(cgi::text_response(503, "Not Implemented"))
 }
 
 fn prompt_for_vote(dir: PathBuf, request: cgi::Request) -> Result<cgi::Response, cgi::Response> {
     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<cgi::Response, cgi::Response> {
 fn record_vote(dir: PathBuf, request: cgi::Request) -> Result<cgi::Response, cgi::Response> {
-    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<cgi::Response, cgi::Response> {
     let dir = validate_path(request.uri().path())?;
     match request.method() {
 }
 
 fn respond(request: cgi::Request) -> Result<cgi::Response, cgi::Response> {
     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::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?")),
     }
 }
         _ => Err(cgi::text_response(405, "Huh?")),
     }
 }