aboutsummaryrefslogtreecommitdiff
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
parent3ed32ef268b54965c97b13efdf24a1539c4ec1c1 (diff)
refactor: split off shares.rs from controller.rs
-rw-r--r--src/controller.rs198
-rw-r--r--src/main.rs1
-rw-r--r--src/shares.rs199
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,
+ })
+}