diff options
Diffstat (limited to 'src/controller.rs')
-rw-r--r-- | src/controller.rs | 198 |
1 files changed, 1 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, |