diff options
author | Martin Fischer <martin@push-f.com> | 2021-06-23 14:42:39 +0200 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2021-06-23 14:42:39 +0200 |
commit | cde4098bb72e164524d403f626dde96913d83316 (patch) | |
tree | 07ba546af32fe291ee2778941f4cb91243bdb37c | |
parent | 3ed32ef268b54965c97b13efdf24a1539c4ec1c1 (diff) |
refactor: split off shares.rs from controller.rs
-rw-r--r-- | src/controller.rs | 198 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/shares.rs | 199 |
3 files changed, 201 insertions, 197 deletions
diff --git a/src/controller.rs b/src/controller.rs index 2eab998..f1bda33 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,7 +1,5 @@ use std::collections::HashMap; -use std::path::Component; use std::path::Path; -use std::path::PathBuf; use std::str::from_utf8; use std::sync::RwLock; @@ -13,6 +11,7 @@ use hyper::http::request::Parts; use serde::Deserialize; use sputnik::html_escape; +use crate::shares::{parse_shares_txt, AccessMode, AccessRule, Shares}; use crate::Branch; use crate::Context; use crate::Error; @@ -115,31 +114,6 @@ pub struct MultiUserController { shares_cache: RwLock<HashMap<Branch, Shares>>, } -/// Maps paths to access rules. -#[derive(Default, Debug)] -pub struct Shares { - exact_rules: HashMap<String, AccessRuleset>, - prefix_rules: HashMap<String, AccessRuleset>, -} - -/// Maps usernames to access modes and .shares.txt source line. -#[derive(Default, Debug)] -struct AccessRuleset(HashMap<String, AccessRule>); - -#[derive(Debug)] -struct AccessRule { - mode: AccessMode, - line: usize, - start: usize, - end: usize, -} - -#[derive(PartialEq, Debug)] -enum AccessMode { - ReadAndWrite, - ReadOnly, -} - // using our own struct because git2::Signature isn't thread-safe #[derive(Deserialize)] struct Identity { @@ -165,176 +139,6 @@ impl MultiUserController { } } -#[derive(Debug)] -enum RulesIterState { - Exact, - Prefix(usize), - Done, -} - -#[derive(Debug)] -struct RulesIter<'a> { - shares: &'a Shares, - path: &'a str, - state: RulesIterState, -} - -impl<'a> Iterator for RulesIter<'a> { - type Item = &'a AccessRuleset; - - fn next(&mut self) -> Option<Self::Item> { - match self.state { - RulesIterState::Exact => { - self.state = RulesIterState::Prefix(self.path.len()); - self.shares - .exact_rules - .get(self.path) - .or_else(|| self.next()) - } - RulesIterState::Prefix(idx) => { - let path = &self.path[..idx]; - if let Some(new_idx) = path.rfind('/') { - self.state = RulesIterState::Prefix(new_idx); - } else { - self.state = RulesIterState::Done; - } - self.shares.prefix_rules.get(path).or_else(|| self.next()) - } - RulesIterState::Done => None, - } - } -} - -impl Shares { - fn rules_iter<'a>(&'a self, path: &'a str) -> RulesIter { - RulesIter { - shares: self, - path, - state: RulesIterState::Exact, - } - } - - fn find_mode<'a>(&'a self, path: &'a str, username: &str) -> Option<&AccessMode> { - for ruleset in self.rules_iter(path) { - if let Some(rule) = ruleset.0.get(username) { - return Some(&rule.mode); - } - } - None - } -} - -fn parse_shares_txt(text: &str) -> Result<Shares, String> { - let mut exact_rules: HashMap<String, AccessRuleset> = HashMap::new(); - let mut prefix_rules: HashMap<String, AccessRuleset> = HashMap::new(); - - let mut start; - let mut next = 0; - - for (idx, line) in text.lines().enumerate() { - start = next; - next = start + line.chars().count() + 1; - - if line.starts_with('#') || line.trim_end().is_empty() { - continue; - } - - let mut iter = line.split('#').next().unwrap().split_ascii_whitespace(); - let permissions = iter.next().unwrap(); - let path = iter - .next() - .ok_or_else(|| format!("line #{} expected three values", idx + 1))?; - let username = iter - .next() - .ok_or_else(|| format!("line #{} expected three values", idx + 1))? - .trim_end(); - if iter.next().is_some() { - return Err(format!( - "line #{} unexpected fourth argument (paths with spaces are unsupported)", - idx + 1 - )); - } - - if permissions != "r" && permissions != "w" { - return Err(format!("line #{} must start with r or w", idx + 1)); - } - if username.is_empty() { - return Err(format!("line #{} empty username", idx + 1)); - } - if path.is_empty() { - return Err(format!("line #{} empty path", idx + 1)); - } - if let (Some(first_ast), Some(last_ast)) = (path.find('*'), path.rfind('*')) { - if first_ast != last_ast || !path.ends_with("/*") { - return Err(format!( - "line #{} wildcards in paths may only occur at the very end as /*", - idx + 1 - )); - } - } - let rule = AccessRule { - mode: if permissions == "w" { - AccessMode::ReadAndWrite - } else { - AccessMode::ReadOnly - }, - line: idx + 1, - start, - end: next, - }; - - // normalize path - let mut comps = Vec::new(); - - for comp in Path::new(&*path).components() { - match comp { - Component::Normal(name) => comps.push(name), - Component::ParentDir => { - return Err(format!("line #{}: path may not contain ../", idx + 1)) - } - _ => {} - } - } - - let path: PathBuf = comps.iter().collect(); - let path = path.to_str().unwrap(); - - if let Some(stripped) = path.strip_suffix("/*") { - let pr = prefix_rules.entry(stripped.to_owned()).or_default(); - if let Some(prevrule) = pr.0.get(username) { - if rule.mode != prevrule.mode { - return Err(format!( - "line #{} conflicts with line #{} ({} {})", - idx + 1, - line, - username, - path - )); - } - } - pr.0.insert(username.to_string(), rule); - } else { - let ac = exact_rules.entry(path.to_owned()).or_default(); - if let Some(prevrule) = ac.0.get(username) { - if rule.mode != prevrule.mode { - return Err(format!( - "line #{} conflicts with line #{} ({} {})", - idx + 1, - line, - username, - path - )); - } - } - ac.0.insert(username.to_string(), rule); - } - } - Ok(Shares { - exact_rules, - prefix_rules, - }) -} - impl MultiUserController { fn with_shares_cache<F: FnMut(&Shares)>( &self, diff --git a/src/main.rs b/src/main.rs index f247ba7..fb6b544 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,7 @@ mod diff; mod forms; mod get_routes; mod post_routes; +mod shares; pub(crate) type Response = hyper::Response<hyper::Body>; pub(crate) type Request = hyper::Request<hyper::Body>; diff --git a/src/shares.rs b/src/shares.rs new file mode 100644 index 0000000..9cfe156 --- /dev/null +++ b/src/shares.rs @@ -0,0 +1,199 @@ +use std::{ + collections::HashMap, + path::{Component, Path, PathBuf}, +}; + +/// Maps paths to access rules. +#[derive(Default, Debug)] +pub struct Shares { + pub exact_rules: HashMap<String, AccessRuleset>, + pub prefix_rules: HashMap<String, AccessRuleset>, +} + +impl Shares { + pub fn rules_iter<'a>(&'a self, path: &'a str) -> RulesIter { + RulesIter { + shares: self, + path, + state: RulesIterState::Exact, + } + } + + pub fn find_mode<'a>(&'a self, path: &'a str, username: &str) -> Option<&AccessMode> { + for ruleset in self.rules_iter(path) { + if let Some(rule) = ruleset.0.get(username) { + return Some(&rule.mode); + } + } + None + } +} + +/// Maps usernames to access modes and .shares.txt source line. +#[derive(Default, Debug)] +pub struct AccessRuleset(pub HashMap<String, AccessRule>); + +#[derive(Debug)] +pub struct AccessRule { + pub mode: AccessMode, + pub line: usize, + pub start: usize, + pub end: usize, +} + +#[derive(PartialEq, Debug)] +pub enum AccessMode { + ReadAndWrite, + ReadOnly, +} + +#[derive(Debug)] +enum RulesIterState { + Exact, + Prefix(usize), + Done, +} + +#[derive(Debug)] +pub struct RulesIter<'a> { + shares: &'a Shares, + path: &'a str, + state: RulesIterState, +} + +impl<'a> Iterator for RulesIter<'a> { + type Item = &'a AccessRuleset; + + fn next(&mut self) -> Option<Self::Item> { + match self.state { + RulesIterState::Exact => { + self.state = RulesIterState::Prefix(self.path.len()); + self.shares + .exact_rules + .get(self.path) + .or_else(|| self.next()) + } + RulesIterState::Prefix(idx) => { + let path = &self.path[..idx]; + if let Some(new_idx) = path.rfind('/') { + self.state = RulesIterState::Prefix(new_idx); + } else { + self.state = RulesIterState::Done; + } + self.shares.prefix_rules.get(path).or_else(|| self.next()) + } + RulesIterState::Done => None, + } + } +} + +pub fn parse_shares_txt(text: &str) -> Result<Shares, String> { + let mut exact_rules: HashMap<String, AccessRuleset> = HashMap::new(); + let mut prefix_rules: HashMap<String, AccessRuleset> = HashMap::new(); + + let mut start; + let mut next = 0; + + for (idx, line) in text.lines().enumerate() { + start = next; + next = start + line.chars().count() + 1; + + if line.starts_with('#') || line.trim_end().is_empty() { + continue; + } + + let mut iter = line.split('#').next().unwrap().split_ascii_whitespace(); + let permissions = iter.next().unwrap(); + let path = iter + .next() + .ok_or_else(|| format!("line #{} expected three values", idx + 1))?; + let username = iter + .next() + .ok_or_else(|| format!("line #{} expected three values", idx + 1))? + .trim_end(); + if iter.next().is_some() { + return Err(format!( + "line #{} unexpected fourth argument (paths with spaces are unsupported)", + idx + 1 + )); + } + + if permissions != "r" && permissions != "w" { + return Err(format!("line #{} must start with r or w", idx + 1)); + } + if username.is_empty() { + return Err(format!("line #{} empty username", idx + 1)); + } + if path.is_empty() { + return Err(format!("line #{} empty path", idx + 1)); + } + if let (Some(first_ast), Some(last_ast)) = (path.find('*'), path.rfind('*')) { + if first_ast != last_ast || !path.ends_with("/*") { + return Err(format!( + "line #{} wildcards in paths may only occur at the very end as /*", + idx + 1 + )); + } + } + let rule = AccessRule { + mode: if permissions == "w" { + AccessMode::ReadAndWrite + } else { + AccessMode::ReadOnly + }, + line: idx + 1, + start, + end: next, + }; + + // normalize path + let mut comps = Vec::new(); + + for comp in Path::new(&*path).components() { + match comp { + Component::Normal(name) => comps.push(name), + Component::ParentDir => { + return Err(format!("line #{}: path may not contain ../", idx + 1)) + } + _ => {} + } + } + + let path: PathBuf = comps.iter().collect(); + let path = path.to_str().unwrap(); + + if let Some(stripped) = path.strip_suffix("/*") { + let pr = prefix_rules.entry(stripped.to_owned()).or_default(); + if let Some(prevrule) = pr.0.get(username) { + if rule.mode != prevrule.mode { + return Err(format!( + "line #{} conflicts with line #{} ({} {})", + idx + 1, + line, + username, + path + )); + } + } + pr.0.insert(username.to_string(), rule); + } else { + let ac = exact_rules.entry(path.to_owned()).or_default(); + if let Some(prevrule) = ac.0.get(username) { + if rule.mode != prevrule.mode { + return Err(format!( + "line #{} conflicts with line #{} ({} {})", + idx + 1, + line, + username, + path + )); + } + } + ac.0.insert(username.to_string(), rule); + } + } + Ok(Shares { + exact_rules, + prefix_rules, + }) +} |