use thiserror::Error;
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
pub enum ParseClausesError {
#[error("invalid character: {0:?}")]
InvalidChar(char),
#[error(transparent)]
BadClause(#[from] ParseClauseError),
}
pub fn parse_clauses(mut s: &str) -> Result<Vec<Clause>, ParseClausesError> {
let mut clauses = vec![Clause::parse(&mut s)?];
loop {
let mut chars = s.chars();
let Some(next) = chars.next() else {
return Ok(clauses);
};
if next != ',' {
return Err(ParseClausesError::InvalidChar(next));
}
s = chars.as_str();
clauses.push(Clause::parse(&mut s)?);
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Clause {
pub who: Who,
pub actions: Vec<Action>,
}
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
#[error(transparent)]
pub enum ParseClauseError {
BadAction(#[from] ParseActionError),
}
impl Clause {
pub fn parse(s: &mut &str) -> Result<Self, ParseClauseError> {
let who = Who::parse(s);
let mut actions = Vec::new();
loop {
match Action::parse(s) {
Ok(action) => actions.push(action),
Err(ParseActionError::NoOperator(_)) if !actions.is_empty() => {
return Ok(Self { who, actions });
}
Err(e) => return Err(ParseClauseError::BadAction(e)),
}
}
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct Who {
pub mask: u16,
}
impl std::fmt::Debug for Who {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Who {{ mask: {:#05o} }}", self.mask)
}
}
impl Who {
pub fn parse(s: &mut &str) -> Self {
let mut mask = 0;
loop {
let mut chars = s.chars();
match chars.next() {
Some('u') => mask |= 0o700,
Some('g') => mask |= 0o070,
Some('o') => mask |= 0o007,
Some('a') => mask |= 0o777,
_ => break,
}
*s = chars.as_str();
}
if mask == 0 {
mask = 0o777;
}
Self { mask }
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Action {
pub operator: Operator,
pub permission: Permission,
}
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
#[error(transparent)]
pub enum ParseActionError {
NoOperator(#[from] ParseOperatorError),
BadPermission(#[from] ParsePermissionError),
}
impl Action {
pub fn parse(s: &mut &str) -> Result<Self, ParseActionError> {
let operator = Operator::parse(s)?;
let permission = Permission::parse(s)?;
Ok(Self {
operator,
permission,
})
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Operator {
Add,
Remove,
Set,
}
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
pub struct ParseOperatorError;
impl std::fmt::Display for ParseOperatorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("no operator")
}
}
impl Operator {
pub fn parse(s: &mut &str) -> Result<Self, ParseOperatorError> {
let mut chars = s.chars();
let operator = match chars.next() {
Some('+') => Self::Add,
Some('-') => Self::Remove,
Some('=') => Self::Set,
_ => return Err(ParseOperatorError),
};
*s = chars.as_str();
Ok(operator)
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum Permission {
CopyUser,
CopyGroup,
CopyOther,
Literal {
mask: u16,
conditional_executable: bool,
},
}
impl std::fmt::Debug for Permission {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CopyUser => write!(f, "CopyUser"),
Self::CopyGroup => write!(f, "CopyGroup"),
Self::CopyOther => write!(f, "CopyOther"),
Self::Literal {
mask,
conditional_executable,
} => write!(
f,
"Literal {{ mask: {mask:#05o}, conditional_executable: {conditional_executable} }}",
),
}
}
}
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
pub enum ParsePermissionError {
InvalidCombination(char, char),
}
impl std::fmt::Display for ParsePermissionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidCombination(c1, c2) => {
write!(
f,
"invalid combination of permission symbols: {c1:?} and {c2:?}",
)
}
}
}
}
impl Permission {
pub fn parse(s: &mut &str) -> Result<Self, ParsePermissionError> {
let alphabets_len = s
.find(|c: char| !matches!(c, 'u' | 'g' | 'o' | 'r' | 'w' | 'x' | 'X' | 's'))
.unwrap_or(s.len());
let alphabets = &s[..alphabets_len];
if let Some(index) = alphabets.find(['u', 'g', 'o']) {
let copy = match alphabets {
"u" => Permission::CopyUser,
"g" => Permission::CopyGroup,
"o" => Permission::CopyOther,
_ => {
let mut chars = alphabets.chars();
let c1 = chars.next().unwrap();
let c2 = if index == 0 {
chars.next().unwrap()
} else {
alphabets[index..].chars().next().unwrap()
};
return Err(ParsePermissionError::InvalidCombination(c1, c2));
}
};
*s = &s[1..];
return Ok(copy);
}
let mut mask = 0;
let mut conditional_executable = false;
for c in alphabets.chars() {
match c {
'r' => mask |= 0o444,
'w' => mask |= 0o222,
'x' => mask |= 0o111,
'X' => conditional_executable = true,
's' => {} _ => unreachable!(),
}
}
*s = &s[alphabets_len..];
Ok(Permission::Literal {
mask,
conditional_executable,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parsing_single_clause() {
let result = parse_clauses("u=");
assert_eq!(
result,
Ok(vec![Clause {
who: Who { mask: 0o700 },
actions: vec![Action {
operator: Operator::Set,
permission: Permission::Literal {
mask: 0,
conditional_executable: false,
},
}],
}])
);
}
#[test]
fn parsing_multiple_clauses() {
let result = parse_clauses("u+r,g-w,o=");
assert_eq!(
result,
Ok(vec![
Clause {
who: Who { mask: 0o700 },
actions: vec![Action {
operator: Operator::Add,
permission: Permission::Literal {
mask: 0o444,
conditional_executable: false,
},
}],
},
Clause {
who: Who { mask: 0o070 },
actions: vec![Action {
operator: Operator::Remove,
permission: Permission::Literal {
mask: 0o222,
conditional_executable: false,
},
}],
},
Clause {
who: Who { mask: 0o007 },
actions: vec![Action {
operator: Operator::Set,
permission: Permission::Literal {
mask: 0,
conditional_executable: false,
},
}],
},
])
);
}
#[test]
fn parsing_invalid_clauses() {
let result = parse_clauses("u-go");
assert_eq!(
result,
Err(ParseClausesError::BadClause(ParseClauseError::BadAction(
ParseActionError::BadPermission(ParsePermissionError::InvalidCombination('g', 'o'))
)))
);
let result = parse_clauses("u+r,g-w,o");
assert_eq!(
result,
Err(ParseClausesError::BadClause(ParseClauseError::BadAction(
ParseActionError::NoOperator(ParseOperatorError)
)))
);
}
#[test]
fn parsing_ill_separated_clauses() {
let result = parse_clauses("u+r,g-w;o=");
assert_eq!(result, Err(ParseClausesError::InvalidChar(';')));
}
}
#[cfg(test)]
mod clause_tests {
use super::*;
#[test]
fn parsing_minimum_clause() {
let mut s = "=";
let result = Clause::parse(&mut s);
assert_eq!(
result,
Ok(Clause {
who: Who { mask: 0o777 },
actions: vec![Action {
operator: Operator::Set,
permission: Permission::Literal {
mask: 0,
conditional_executable: false,
},
}],
})
);
assert_eq!(s, "");
}
#[test]
fn clause_with_nonempty_who() {
let mut s = "u=";
let result = Clause::parse(&mut s);
assert_eq!(
result,
Ok(Clause {
who: Who { mask: 0o700 },
actions: vec![Action {
operator: Operator::Set,
permission: Permission::Literal {
mask: 0,
conditional_executable: false,
},
}],
})
);
assert_eq!(s, "");
}
#[test]
fn clause_with_one_action() {
let mut s = "u+w";
let result = Clause::parse(&mut s);
assert_eq!(
result,
Ok(Clause {
who: Who { mask: 0o700 },
actions: vec![Action {
operator: Operator::Add,
permission: Permission::Literal {
mask: 0o222,
conditional_executable: false,
},
}],
})
);
}
#[test]
fn clause_with_multiple_actions() {
let mut s = "u-w+r,";
let result = Clause::parse(&mut s);
assert_eq!(
result,
Ok(Clause {
who: Who { mask: 0o700 },
actions: vec![
Action {
operator: Operator::Remove,
permission: Permission::Literal {
mask: 0o222,
conditional_executable: false,
},
},
Action {
operator: Operator::Add,
permission: Permission::Literal {
mask: 0o444,
conditional_executable: false,
},
},
],
})
);
assert_eq!(s, ",");
}
#[test]
fn clause_with_no_actions() {
let mut s = "u";
let result = Clause::parse(&mut s);
assert_eq!(
result,
Err(ParseClauseError::BadAction(ParseActionError::NoOperator(
ParseOperatorError
)))
);
}
#[test]
fn clause_with_invalid_permission_combination() {
let mut s = "+ug";
let result = Clause::parse(&mut s);
assert_eq!(
result,
Err(ParseClauseError::BadAction(
ParseActionError::BadPermission(ParsePermissionError::InvalidCombination('u', 'g'))
))
);
}
}
#[cfg(test)]
mod who_tests {
use super::*;
#[test]
fn parsing_single() {
let mut s = "u";
let result = Who::parse(&mut s);
assert_eq!(result, Who { mask: 0o700 });
assert_eq!(s, "");
let mut s = "g+w";
let result = Who::parse(&mut s);
assert_eq!(result, Who { mask: 0o070 });
assert_eq!(s, "+w");
let mut s = "o";
let result = Who::parse(&mut s);
assert_eq!(result, Who { mask: 0o007 });
assert_eq!(s, "");
}
#[test]
fn parsing_all() {
let mut s = "a";
let result = Who::parse(&mut s);
assert_eq!(result, Who { mask: 0o777 });
assert_eq!(s, "");
}
#[test]
fn parsing_multiple() {
let mut s = "ug";
let result = Who::parse(&mut s);
assert_eq!(result, Who { mask: 0o770 });
assert_eq!(s, "");
let mut s = "go=";
let result = Who::parse(&mut s);
assert_eq!(result, Who { mask: 0o077 });
assert_eq!(s, "=");
}
#[test]
fn parsing_empty() {
let mut s = "";
let result = Who::parse(&mut s);
assert_eq!(result, Who { mask: 0o777 });
assert_eq!(s, "");
}
}
#[cfg(test)]
mod action_tests {
use super::*;
#[test]
fn parsing_empty() {
let mut s = "";
let result = Action::parse(&mut s);
assert_eq!(
result,
Err(ParseActionError::NoOperator(ParseOperatorError))
);
}
#[test]
fn parsing_invalid_operator() {
let mut s = "x";
let result = Action::parse(&mut s);
assert_eq!(
result,
Err(ParseActionError::NoOperator(ParseOperatorError))
);
}
#[test]
fn parsing_operator_with_empty_permission() {
let mut s = "+";
let result = Action::parse(&mut s);
assert_eq!(
result,
Ok(Action {
operator: Operator::Add,
permission: Permission::Literal {
mask: 0,
conditional_executable: false,
},
})
);
assert_eq!(s, "");
let mut s = "-+";
let result = Action::parse(&mut s);
assert_eq!(
result,
Ok(Action {
operator: Operator::Remove,
permission: Permission::Literal {
mask: 0,
conditional_executable: false,
},
})
);
assert_eq!(s, "+");
}
#[test]
fn parsing_operator_with_nonempty_permission() {
let mut s = "+r";
let result = Action::parse(&mut s);
assert_eq!(
result,
Ok(Action {
operator: Operator::Add,
permission: Permission::Literal {
mask: 0o444,
conditional_executable: false,
},
})
);
assert_eq!(s, "");
let mut s = "-rXw=x";
let result = Action::parse(&mut s);
assert_eq!(
result,
Ok(Action {
operator: Operator::Remove,
permission: Permission::Literal {
mask: 0o666,
conditional_executable: true,
},
})
);
assert_eq!(s, "=x");
}
}
#[cfg(test)]
mod operator_tests {
use super::*;
#[test]
fn parsing_plus() {
let mut s = "+";
let result = Operator::parse(&mut s);
assert_eq!(result, Ok(Operator::Add));
assert_eq!(s, "");
let mut s = "+r";
let result = Operator::parse(&mut s);
assert_eq!(result, Ok(Operator::Add));
assert_eq!(s, "r");
}
#[test]
fn parsing_minus() {
let mut s = "-";
let result = Operator::parse(&mut s);
assert_eq!(result, Ok(Operator::Remove));
assert_eq!(s, "");
let mut s = "-w";
let result = Operator::parse(&mut s);
assert_eq!(result, Ok(Operator::Remove));
assert_eq!(s, "w");
}
#[test]
fn parsing_equal() {
let mut s = "=";
let result = Operator::parse(&mut s);
assert_eq!(result, Ok(Operator::Set));
assert_eq!(s, "");
let mut s = "=x";
let result = Operator::parse(&mut s);
assert_eq!(result, Ok(Operator::Set));
assert_eq!(s, "x");
}
#[test]
fn parsing_non_operator() {
let mut s = "";
let result = Operator::parse(&mut s);
assert_eq!(result, Err(ParseOperatorError));
let mut s = "x";
let result = Operator::parse(&mut s);
assert_eq!(result, Err(ParseOperatorError));
}
}
#[cfg(test)]
mod permission_tests {
use super::*;
#[test]
fn parsing_empty() {
let mut s = "";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0,
conditional_executable: false,
})
);
assert_eq!(s, "");
let mut s = ",";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0,
conditional_executable: false,
})
);
assert_eq!(s, ",");
}
#[test]
fn parsing_copy_user() {
let mut s = "u";
let result = Permission::parse(&mut s);
assert_eq!(result, Ok(Permission::CopyUser));
assert_eq!(s, "");
let mut s = "u+g";
let result = Permission::parse(&mut s);
assert_eq!(result, Ok(Permission::CopyUser));
assert_eq!(s, "+g");
}
#[test]
fn parsing_copy_group() {
let mut s = "g";
let result = Permission::parse(&mut s);
assert_eq!(result, Ok(Permission::CopyGroup));
assert_eq!(s, "");
let mut s = "g+o";
let result = Permission::parse(&mut s);
assert_eq!(result, Ok(Permission::CopyGroup));
assert_eq!(s, "+o");
}
#[test]
fn parsing_copy_other() {
let mut s = "o";
let result = Permission::parse(&mut s);
assert_eq!(result, Ok(Permission::CopyOther));
assert_eq!(s, "");
let mut s = "o+u";
let result = Permission::parse(&mut s);
assert_eq!(result, Ok(Permission::CopyOther));
assert_eq!(s, "+u");
}
#[test]
fn parsing_literal_r() {
let mut s = "r";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0o444,
conditional_executable: false,
})
);
assert_eq!(s, "");
}
#[test]
fn parsing_literal_w() {
let mut s = "w";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0o222,
conditional_executable: false,
})
);
assert_eq!(s, "");
}
#[test]
fn parsing_literal_x() {
let mut s = "x";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0o111,
conditional_executable: false,
})
);
assert_eq!(s, "");
}
#[test]
fn parsing_literal_conditional_x() {
let mut s = "X";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0,
conditional_executable: true,
})
);
assert_eq!(s, "");
}
#[test]
fn parsing_literal_of_rwx_combination() {
let mut s = "rw";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0o666,
conditional_executable: false,
})
);
assert_eq!(s, "");
let mut s = "xr";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0o555,
conditional_executable: false,
})
);
assert_eq!(s, "");
let mut s = "xwr-u";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0o777,
conditional_executable: false,
})
);
assert_eq!(s, "-u");
}
#[test]
fn parsing_literal_s() {
let mut s = "s";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Ok(Permission::Literal {
mask: 0,
conditional_executable: false,
})
);
assert_eq!(s, "");
}
#[test]
fn copy_cannot_be_combined_with_literal() {
let mut s = "ur";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Err(ParsePermissionError::InvalidCombination('u', 'r'))
);
let mut s = "ru";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Err(ParsePermissionError::InvalidCombination('r', 'u'))
);
}
#[test]
fn copy_cannot_be_combined_with_copy() {
let mut s = "ug";
let result = Permission::parse(&mut s);
assert_eq!(
result,
Err(ParsePermissionError::InvalidCombination('u', 'g'))
);
}
}