]> git.scottworley.com Git - voter/blobdiff - src/main.rs
Disallow voting for eliminated candidates server-side
[voter] / src / main.rs
index 865fcb864ec5bf6497a6ac96c42d9174030345d5..92937e58ff6f9b73a313d554432f695083b99161 100644 (file)
@@ -81,8 +81,16 @@ fn tally_votes(dir: &Path) -> std::io::Result<HashMap<String, HashSet<String>>>
     }
 }
 
+fn read_elim_list(dir: &Path) -> std::io::Result<HashSet<String>> {
+    match std::fs::File::open(dir.join("eliminated")) {
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(HashSet::new()),
+        Err(e) => Err(e),
+        Ok(elimfile) => std::io::BufReader::new(elimfile).lines().collect(),
+    }
+}
+
 fn valid_id_char(c: u8) -> bool {
-    (b'A'..=b'Z').contains(&c) || (b'a'..=b'z').contains(&c) || (b'0'..=b'9').contains(&c)
+    c.is_ascii_alphanumeric()
 }
 
 fn make_random_id() -> [u8; COOKIE_LENGTH] {
@@ -121,6 +129,7 @@ const HTML_HEADER: &str = "<!DOCTYPE html>
     <style>
       th { font-size: 70%; text-align: left }
       input { transform: scale(1.5) }
+      .eliminated { text-decoration: line-through; }
       div { animation: 2s infinite linear spin }
       @keyframes spin {
         from { transform:rotate(0) }
@@ -130,6 +139,8 @@ const HTML_HEADER: &str = "<!DOCTYPE html>
     <script>
       window.onload = function() {
         for (cb of document.getElementsByTagName('input')) {
+          if (cb.parentElement.parentElement.classList.contains('eliminated'))
+            continue;
           cb.addEventListener('click', (function(cb) {
             return function() {
               cb.style.display = 'none'
@@ -207,6 +218,8 @@ fn prompt_for_vote(dir: &Path, request: &cgi::Request) -> Result<cgi::Response,
         None
     };
     let tally = tally_votes(dir).map_err(|_| cgi::text_response(503, "Couldn't tally votes"))?;
+    let elim =
+        read_elim_list(dir).map_err(|_| cgi::text_response(503, "Couldn't read eliminations"))?;
     let cfile = std::fs::File::open(dir.join("candidates"))
         .map_err(|_| cgi::text_response(503, "No candidates"))?;
     let mut response = cgi::html_response(
@@ -220,8 +233,9 @@ fn prompt_for_vote(dir: &Path, request: &cgi::Request) -> Result<cgi::Response,
                     } else {
                         ""
                     };
+                    let class = if elim.contains(&c) { "eliminated" } else { "" };
                     format!(
-                        "<tr>
+                        "<tr class=\"{class}\">
                         <td>{count}</td>
                         <td><input type=\"checkbox\" autocomplete=\"off\" {checked} disabled></td>
                         <td>{c}</td>
@@ -261,6 +275,13 @@ fn record_vote(dir: &Path, request: &cgi::Request) -> Result<cgi::Response, cgi:
     {
         return Err(cgi::text_response(415, "Invalid vote"));
     }
+    let elim =
+        read_elim_list(dir).map_err(|_| cgi::text_response(503, "Couldn't read eliminations"))?;
+    if elim.contains(
+        std::str::from_utf8(&body[2..]).map_err(|_| cgi::text_response(415, "Vote not UTF-8"))?,
+    ) {
+        return Err(cgi::text_response(403, "Candidate eliminated"));
+    }
     write_vote(dir, get_voter(request)?, body)
         .map_err(|_| cgi::text_response(503, "Couldn't record vote"))?;
     Ok(cgi::text_response(200, "Vote recorded"))