]> git.scottworley.com Git - voter/blobdiff - src/main.rs
Release 0.3.0
[voter] / src / main.rs
index fcb16c7e6805138c8899d706d3097878b05bba59..92937e58ff6f9b73a313d554432f695083b99161 100644 (file)
@@ -53,32 +53,44 @@ fn get_voter(request: &cgi::Request) -> Result<&[u8], cgi::Response> {
 
 fn tally_votes(dir: &Path) -> std::io::Result<HashMap<String, HashSet<String>>> {
     let mut tally: HashMap<String, HashSet<String>> = HashMap::new();
 
 fn tally_votes(dir: &Path) -> std::io::Result<HashMap<String, HashSet<String>>> {
     let mut tally: HashMap<String, HashSet<String>> = HashMap::new();
-    if let Ok(vfile) = std::fs::File::open(dir.to_owned().join("votes")) {
-        for liner in std::io::BufReader::new(vfile).lines() {
-            let line = liner?;
-            if let Some((voter, datum)) = line.split_once(' ') {
-                if voter.len() == COOKIE_LENGTH {
-                    if let Some((vote, candidate)) = datum.split_once(' ') {
-                        if vote == "0" {
-                            if let Some(entry) = tally.get_mut(candidate) {
-                                entry.remove(voter);
+    match std::fs::File::open(dir.to_owned().join("votes")) {
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(tally),
+        Err(e) => Err(e),
+        Ok(vfile) => {
+            for liner in std::io::BufReader::new(vfile).lines() {
+                let line = liner?;
+                if let Some((voter, datum)) = line.split_once(' ') {
+                    if voter.len() == COOKIE_LENGTH {
+                        if let Some((vote, candidate)) = datum.split_once(' ') {
+                            if vote == "0" {
+                                if let Some(entry) = tally.get_mut(candidate) {
+                                    entry.remove(voter);
+                                }
+                            } else if vote == "1" {
+                                tally
+                                    .entry(candidate.to_owned())
+                                    .or_default()
+                                    .insert(voter.to_owned());
                             }
                             }
-                        } else if vote == "1" {
-                            tally
-                                .entry(candidate.to_owned())
-                                .or_default()
-                                .insert(voter.to_owned());
                         }
                     }
                 }
             }
                         }
                     }
                 }
             }
+            Ok(tally)
         }
     }
         }
     }
-    Ok(tally)
+}
+
+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 {
 }
 
 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] {
 }
 
 fn make_random_id() -> [u8; COOKIE_LENGTH] {
@@ -117,6 +129,7 @@ const HTML_HEADER: &str = "<!DOCTYPE html>
     <style>
       th { font-size: 70%; text-align: left }
       input { transform: scale(1.5) }
     <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) }
       div { animation: 2s infinite linear spin }
       @keyframes spin {
         from { transform:rotate(0) }
@@ -126,6 +139,8 @@ const HTML_HEADER: &str = "<!DOCTYPE html>
     <script>
       window.onload = function() {
         for (cb of document.getElementsByTagName('input')) {
     <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'
           cb.addEventListener('click', (function(cb) {
             return function() {
               cb.style.display = 'none'
@@ -203,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"))?;
         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(
     let cfile = std::fs::File::open(dir.join("candidates"))
         .map_err(|_| cgi::text_response(503, "No candidates"))?;
     let mut response = cgi::html_response(
@@ -216,8 +233,9 @@ fn prompt_for_vote(dir: &Path, request: &cgi::Request) -> Result<cgi::Response,
                     } else {
                         ""
                     };
                     } else {
                         ""
                     };
+                    let class = if elim.contains(&c) { "eliminated" } else { "" };
                     format!(
                     format!(
-                        "<tr>
+                        "<tr class=\"{class}\">
                         <td>{count}</td>
                         <td><input type=\"checkbox\" autocomplete=\"off\" {checked} disabled></td>
                         <td>{c}</td>
                         <td>{count}</td>
                         <td><input type=\"checkbox\" autocomplete=\"off\" {checked} disabled></td>
                         <td>{c}</td>
@@ -257,6 +275,13 @@ fn record_vote(dir: &Path, request: &cgi::Request) -> Result<cgi::Response, cgi:
     {
         return Err(cgi::text_response(415, "Invalid vote"));
     }
     {
         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"))
     write_vote(dir, get_voter(request)?, body)
         .map_err(|_| cgi::text_response(503, "Couldn't record vote"))?;
     Ok(cgi::text_response(200, "Vote recorded"))