///|
pub enum AclParseKind {
EmptyLine
UnknownDecision
UnknownTarget
MissingField
InvalidAddress
InvalidNetwork
InvalidRange
} derive(Eq, Debug)
///|
pub struct AclParseError {
line : Int
input : String
kind : AclParseKind
} derive(Eq, Debug)
///|
pub struct AclBatchReport {
allowed : Int
denied : Int
lines : Array[String]
} derive(Debug)
///|
pub fn AclParseError::to_string(self : AclParseError) -> String {
"line \{self.line}: \{self.kind.to_string()} in '\{self.input}'"
}
///|
pub fn AclParseKind::to_string(self : AclParseKind) -> String {
match self {
EmptyLine => "empty line"
UnknownDecision => "unknown decision"
UnknownTarget => "unknown target"
MissingField => "missing field"
InvalidAddress => "invalid address"
InvalidNetwork => "invalid network"
InvalidRange => "invalid range"
}
}
///|
pub fn AclRule::parse_line(line : String) -> Result[AclRule, AclParseError] {
parse_acl_line_at(line, 1)
}
///|
pub fn AclPolicy::parse_lines(
lines : Array[String],
default_decision : AclDecision,
) -> Result[AclPolicy, AclParseError] {
let mut policy = AclPolicy::new(default_decision)
let mut line_no = 1
for line in lines {
let trimmed = trim_ascii(line)
if trimmed.length() > 0 && !starts_with(trimmed, "#") {
match parse_acl_line_at(trimmed, line_no) {
Err(error) => return Err(error)
Ok(rule) => policy = policy.push(rule)
}
}
line_no = line_no + 1
}
Ok(policy)
}
///|
pub fn AclPolicy::evaluate_many(
self : AclPolicy,
addresses : Array[Ipv4Address],
) -> AclBatchReport {
let lines = Array::new()
let mut allowed = 0
let mut denied = 0
for address in addresses {
let evaluation = self.evaluate(address)
if evaluation.decision() == Allow {
allowed = allowed + 1
} else {
denied = denied + 1
}
lines.push(evaluation.to_string())
}
{ allowed, denied, lines }
}
///|
pub fn AclEvaluation::decision(self : AclEvaluation) -> AclDecision {
self.decision
}
///|
pub fn AclEvaluation::matched_rule(self : AclEvaluation) -> String? {
self.matched_rule
}
///|
pub fn AclBatchReport::allowed(self : AclBatchReport) -> Int {
self.allowed
}
///|
pub fn AclBatchReport::denied(self : AclBatchReport) -> Int {
self.denied
}
///|
pub fn AclBatchReport::lines(self : AclBatchReport) -> Array[String] {
self.lines.copy()
}
///|
pub fn AclBatchReport::to_string(self : AclBatchReport) -> String {
let mut text = "allowed=\{self.allowed}, denied=\{self.denied}"
for line in self.lines {
text = text + "\n" + line
}
text
}
///|
fn parse_acl_line_at(
line : String,
line_no : Int,
) -> Result[AclRule, AclParseError] {
let parts = split_words(line)
if parts.length() == 0 {
return Err({ line: line_no, input: line, kind: EmptyLine })
}
if parts.length() < 3 {
return Err({ line: line_no, input: line, kind: MissingField })
}
let decision = match parse_acl_decision(parts[0]) {
None => return Err({ line: line_no, input: line, kind: UnknownDecision })
Some(value) => value
}
let target_kind = parts[1]
let target_value = parts[2]
let name = if parts.length() >= 4 {
join_words(parts, 3)
} else {
"\{parts[0]}-\{parts[1]}-\{parts[2]}"
}
match target_kind {
"net" | "network" | "cidr" =>
match Ipv4Network::parse(target_value) {
Err(_) => Err({ line: line_no, input: line, kind: InvalidNetwork })
Ok(network) => Ok(AclRule::network(name, decision, network))
}
"addr" | "address" | "host" =>
match Ipv4Address::parse(target_value) {
Err(_) => Err({ line: line_no, input: line, kind: InvalidAddress })
Ok(address) => Ok(AclRule::address(name, decision, address))
}
"range" =>
if parts.length() < 4 {
Err({ line: line_no, input: line, kind: MissingField })
} else {
let range_name = if parts.length() >= 5 {
join_words(parts, 4)
} else {
"\{parts[0]}-range"
}
match Ipv4Range::parse(parts[2], parts[3]) {
Err(_) => Err({ line: line_no, input: line, kind: InvalidRange })
Ok(range) => Ok(AclRule::range(range_name, decision, range))
}
}
_ => Err({ line: line_no, input: line, kind: UnknownTarget })
}
}
///|
fn parse_acl_decision(text : String) -> AclDecision? {
match text {
"allow" | "permit" | "accept" => Some(Allow)
"deny" | "reject" | "block" => Some(Deny)
_ => None
}
}
///|
fn split_words(input : String) -> Array[String] {
let result = Array::new()
let mut current = ""
for ch in input {
if ch == ' ' || ch == '\t' {
if current.length() > 0 {
result.push(current)
current = ""
}
} else {
current = current + ch.to_string()
}
}
if current.length() > 0 {
result.push(current)
}
result
}
///|
fn join_words(parts : Array[String], start : Int) -> String {
let mut text = ""
let mut index = start
while index < parts.length() {
if index > start {
text = text + " "
}
text = text + parts[index]
index = index + 1
}
text
}
///|
fn trim_ascii(input : String) -> String {
trim_right_ascii(trim_left_ascii(input))
}
///|
fn trim_left_ascii(input : String) -> String {
let mut seen = false
let mut text = ""
for ch in input {
if !seen && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
()
} else {
seen = true
text = text + ch.to_string()
}
}
text
}
///|
fn trim_right_ascii(input : String) -> String {
let chars = Array::new()
for ch in input {
chars.push(ch)
}
let mut end = chars.length() - 1
while end >= 0 &&
(
chars[end] == ' ' ||
chars[end] == '\t' ||
chars[end] == '\r' ||
chars[end] == '\n'
) {
end = end - 1
}
let mut text = ""
let mut index = 0
while index <= end {
text = text + chars[index].to_string()
index = index + 1
}
text
}
///|
fn starts_with(input : String, prefix : String) -> Bool {
input.has_prefix(prefix)
}