#![allow(clippy::doc_markdown)]
use super::ber::{BerError, Children, parse_tlv};
const TAG_AND: u8 = 0xa0;
const TAG_OR: u8 = 0xa1;
const TAG_NOT: u8 = 0xa2;
const TAG_EQUALITY: u8 = 0xa3;
const TAG_SUBSTRINGS: u8 = 0xa4;
const TAG_GE: u8 = 0xa5;
const TAG_LE: u8 = 0xa6;
const TAG_PRESENT: u8 = 0x87;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Filter {
And(Vec<Filter>),
Or(Vec<Filter>),
Not(Box<Filter>),
Equality {
attribute: String,
value: String,
},
Present(String),
Substrings {
attribute: String,
initial: Option<String>,
any: Vec<String>,
last: Option<String>,
},
GreaterOrEqual {
attribute: String,
value: String,
},
LessOrEqual {
attribute: String,
value: String,
},
Unsupported,
}
fn octet_str(content: &[u8]) -> String {
String::from_utf8_lossy(content).into_owned()
}
const MAX_FILTER_DEPTH: usize = 32;
const MAX_FILTER_NODES: usize = 4096;
pub fn decode_filter(tag: u8, content: &[u8]) -> Result<Filter, BerError> {
let mut budget = MAX_FILTER_NODES;
decode_filter_at(tag, content, 0, &mut budget)
}
fn decode_filter_at(
tag: u8,
content: &[u8],
depth: usize,
budget: &mut usize,
) -> Result<Filter, BerError> {
if depth > MAX_FILTER_DEPTH {
return Err(BerError("filter nesting too deep"));
}
*budget = budget
.checked_sub(1)
.ok_or(BerError("filter has too many nodes"))?;
match tag {
TAG_AND => Ok(Filter::And(decode_set(content, depth, budget)?)),
TAG_OR => Ok(Filter::Or(decode_set(content, depth, budget)?)),
TAG_NOT => {
let (t, c, _) = parse_tlv(content)?;
Ok(Filter::Not(Box::new(decode_filter_at(
t,
c,
depth + 1,
budget,
)?)))
}
TAG_EQUALITY | TAG_GE | TAG_LE => {
let (attribute, value) = decode_ava(content)?;
Ok(match tag {
TAG_EQUALITY => Filter::Equality { attribute, value },
TAG_GE => Filter::GreaterOrEqual { attribute, value },
_ => Filter::LessOrEqual { attribute, value },
})
}
TAG_PRESENT => Ok(Filter::Present(octet_str(content))),
TAG_SUBSTRINGS => decode_substrings(content),
_ => Err(BerError("unsupported filter type")),
}
}
fn decode_set(content: &[u8], depth: usize, budget: &mut usize) -> Result<Vec<Filter>, BerError> {
let mut filters = Vec::new();
for child in Children::new(content) {
let (t, c) = child?;
filters.push(decode_filter_at(t, c, depth + 1, budget)?);
}
Ok(filters)
}
fn decode_ava(content: &[u8]) -> Result<(String, String), BerError> {
let (_t1, attr, rest) = parse_tlv(content)?;
let (_t2, val, _) = parse_tlv(rest)?;
Ok((octet_str(attr), octet_str(val)))
}
fn decode_substrings(content: &[u8]) -> Result<Filter, BerError> {
let (_t, attr, rest) = parse_tlv(content)?;
let (_seq_tag, parts, _) = parse_tlv(rest)?;
let mut initial = None;
let mut any = Vec::new();
let mut last = None;
for child in Children::new(parts) {
let (t, c) = child?;
match t {
0x80 => initial = Some(octet_str(c)),
0x81 => any.push(octet_str(c)),
0x82 => last = Some(octet_str(c)),
_ => {}
}
}
Ok(Filter::Substrings {
attribute: octet_str(attr),
initial,
any,
last,
})
}
impl Filter {
#[must_use]
pub fn matches<F>(&self, get: &F) -> bool
where
F: Fn(&str) -> Vec<String>,
{
match self {
Filter::And(fs) => fs.iter().all(|f| f.matches(get)),
Filter::Or(fs) => fs.iter().any(|f| f.matches(get)),
Filter::Not(f) => !f.matches(get),
Filter::Present(attr) => !get(attr).is_empty(),
Filter::Equality { attribute, value } => {
let v = value.to_lowercase();
get(attribute).iter().any(|a| a.to_lowercase() == v)
}
Filter::GreaterOrEqual { attribute, value } => {
let v = value.to_lowercase();
get(attribute).iter().any(|a| a.to_lowercase() >= v)
}
Filter::LessOrEqual { attribute, value } => {
let v = value.to_lowercase();
get(attribute).iter().any(|a| a.to_lowercase() <= v)
}
Filter::Substrings {
attribute,
initial,
any,
last,
} => get(attribute).iter().any(|a| {
let a = a.to_lowercase();
let mut cursor = 0;
if let Some(i) = initial {
let i = i.to_lowercase();
if !a.starts_with(&i) {
return false;
}
cursor = i.len();
}
for part in any {
let part = part.to_lowercase();
match a[cursor..].find(&part) {
Some(idx) => cursor += idx + part.len(),
None => return false,
}
}
if let Some(f) = last {
if !a[cursor..].ends_with(&f.to_lowercase()) {
return false;
}
}
true
}),
Filter::Unsupported => false,
}
}
}
#[cfg(test)]
mod tests {
use super::super::ber::{encode_octet_string, tlv};
use super::*;
fn ava(tag: u8, attr: &str, value: &str) -> Vec<u8> {
let content = [
encode_octet_string(attr.as_bytes()),
encode_octet_string(value.as_bytes()),
]
.concat();
tlv(tag, &content)
}
fn getter(pairs: &'static [(&'static str, &'static str)]) -> impl Fn(&str) -> Vec<String> {
move |name: &str| {
pairs
.iter()
.filter(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| (*v).to_string())
.collect()
}
}
#[test]
fn equality_and_present() {
let eq = ava(TAG_EQUALITY, "uid", "bjensen");
let (t, c, _) = parse_tlv(&eq).unwrap();
let f = decode_filter(t, c).unwrap();
assert_eq!(
f,
Filter::Equality {
attribute: "uid".into(),
value: "bjensen".into()
}
);
let g = getter(&[("uid", "BJensen")]);
assert!(f.matches(&g)); assert!(
!Filter::Equality {
attribute: "uid".into(),
value: "nope".into()
}
.matches(&g)
);
assert!(Filter::Present("uid".into()).matches(&g));
assert!(!Filter::Present("mail".into()).matches(&g));
}
#[test]
fn and_or_not() {
let inner = [
ava(TAG_EQUALITY, "uid", "bob"),
ava(TAG_EQUALITY, "ou", "eng"),
]
.concat();
let and = tlv(TAG_AND, &inner);
let (t, c, _) = parse_tlv(&and).unwrap();
let f = decode_filter(t, c).unwrap();
let g = getter(&[("uid", "bob"), ("ou", "eng")]);
assert!(f.matches(&g));
let g2 = getter(&[("uid", "bob"), ("ou", "sales")]);
assert!(!f.matches(&g2));
}
#[test]
fn substrings() {
let parts = [tlv(0x80, b"a"), tlv(0x81, b"b"), tlv(0x82, b"c")].concat();
let content = [encode_octet_string(b"cn"), tlv(0x30, &parts)].concat();
let sub = tlv(TAG_SUBSTRINGS, &content);
let (t, c, _) = parse_tlv(&sub).unwrap();
let f = decode_filter(t, c).unwrap();
assert!(f.matches(&getter(&[("cn", "aXbYc")])));
assert!(!f.matches(&getter(&[("cn", "aXc")]))); assert!(!f.matches(&getter(&[("cn", "Xbc")]))); }
#[test]
fn substrings_non_ascii_no_panic() {
let f = Filter::Substrings {
attribute: "cn".into(),
initial: Some("İ".into()),
any: Vec::new(),
last: Some("x".into()),
};
assert!(f.matches(&getter(&[("cn", "İx")])));
assert!(!f.matches(&getter(&[("cn", "İy")])));
}
#[test]
fn rejects_over_deep_nesting() {
let mut buf = ava(TAG_EQUALITY, "uid", "x");
for _ in 0..(MAX_FILTER_DEPTH + 5) {
buf = tlv(TAG_NOT, &buf);
}
let (t, c, _) = parse_tlv(&buf).unwrap();
assert!(decode_filter(t, c).is_err());
}
#[test]
fn rejects_too_many_nodes() {
let leaves: Vec<u8> = (0..=MAX_FILTER_NODES)
.flat_map(|_| ava(TAG_EQUALITY, "uid", "x"))
.collect();
let and = tlv(TAG_AND, &leaves);
let (t, c, _) = parse_tlv(&and).unwrap();
assert!(decode_filter(t, c).is_err());
}
#[test]
fn accepts_node_count_within_limit() {
let leaves: Vec<u8> = (0..MAX_FILTER_NODES - 1)
.flat_map(|_| ava(TAG_EQUALITY, "uid", "x"))
.collect();
let and = tlv(TAG_AND, &leaves);
let (t, c, _) = parse_tlv(&and).unwrap();
assert!(decode_filter(t, c).is_ok());
}
#[test]
fn accepts_nesting_within_limit() {
let mut buf = ava(TAG_EQUALITY, "uid", "x");
for _ in 0..(MAX_FILTER_DEPTH - 1) {
buf = tlv(TAG_NOT, &buf);
}
let (t, c, _) = parse_tlv(&buf).unwrap();
assert!(decode_filter(t, c).is_ok());
}
}