aboutsummaryrefslogtreecommitdiff
path: root/src/controller.rs
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-06-23 14:42:39 +0200
committerMartin Fischer <martin@push-f.com>2021-06-23 14:42:39 +0200
commitcde4098bb72e164524d403f626dde96913d83316 (patch)
tree07ba546af32fe291ee2778941f4cb91243bdb37c /src/controller.rs
parent3ed32ef268b54965c97b13efdf24a1539c4ec1c1 (diff)
refactor: split off shares.rs from controller.rs
Diffstat (limited to 'src/controller.rs')
-rw-r--r--src/controller.rs198
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,