use crate::directory::entry::LdapEntry;
use regex::Regex;
#[derive(Debug, Clone, PartialEq)]
pub enum LdapFilter {
Present(String), Equality(String, String), Substring(String, SubstringFilter), GreaterOrEqual(String, String), LessOrEqual(String, String), Approximate(String, String), Extensible(ExtensibleFilter), And(Vec<LdapFilter>), Or(Vec<LdapFilter>), Not(Box<LdapFilter>), }
#[derive(Debug, Clone, PartialEq)]
pub struct SubstringFilter {
pub initial: Option<String>,
pub any: Vec<String>,
pub final_: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExtensibleFilter {
pub attribute: Option<String>, pub matching_rule: Option<String>, pub value: String, pub dn_attributes: bool, }
impl LdapFilter {
pub fn matches(&self, entry: &LdapEntry) -> bool {
match self {
LdapFilter::Present(attr) => entry.has_attribute(attr),
LdapFilter::Equality(attr, value) => {
if let Some(attribute) = entry.get_attribute(attr) {
attribute
.values
.iter()
.any(|v| v.as_string().eq_ignore_ascii_case(value))
} else {
false
}
}
LdapFilter::Substring(attr, substring) => {
if let Some(attribute) = entry.get_attribute(attr) {
attribute
.values
.iter()
.any(|v| substring.matches(&v.as_string()))
} else {
false
}
}
LdapFilter::GreaterOrEqual(attr, value) => {
if let Some(attribute) = entry.get_attribute(attr) {
attribute.values.iter().any(|v| v.as_string() >= *value)
} else {
false
}
}
LdapFilter::LessOrEqual(attr, value) => {
if let Some(attribute) = entry.get_attribute(attr) {
attribute.values.iter().any(|v| v.as_string() <= *value)
} else {
false
}
}
LdapFilter::Approximate(attr, value) => {
if let Some(attribute) = entry.get_attribute(attr) {
attribute
.values
.iter()
.any(|v| approximate_match(&v.as_string(), value))
} else {
false
}
}
LdapFilter::Extensible(extensible) => extensible.matches(entry),
LdapFilter::And(filters) => filters.iter().all(|f| f.matches(entry)),
LdapFilter::Or(filters) => filters.iter().any(|f| f.matches(entry)),
LdapFilter::Not(filter) => !filter.matches(entry),
}
}
pub fn get_referenced_attributes(&self) -> std::collections::HashSet<String> {
let mut attributes = std::collections::HashSet::new();
self.collect_attributes(&mut attributes);
attributes
}
fn collect_attributes(&self, attributes: &mut std::collections::HashSet<String>) {
match self {
LdapFilter::Present(attr)
| LdapFilter::Equality(attr, _)
| LdapFilter::Substring(attr, _)
| LdapFilter::GreaterOrEqual(attr, _)
| LdapFilter::LessOrEqual(attr, _)
| LdapFilter::Approximate(attr, _) => {
attributes.insert(attr.to_lowercase());
}
LdapFilter::Extensible(ext) => {
if let Some(attr) = &ext.attribute {
attributes.insert(attr.to_lowercase());
}
}
LdapFilter::And(filters) | LdapFilter::Or(filters) => {
for filter in filters {
filter.collect_attributes(attributes);
}
}
LdapFilter::Not(filter) => {
filter.collect_attributes(attributes);
}
}
}
}
impl SubstringFilter {
pub fn matches(&self, value: &str) -> bool {
let mut pattern = String::new();
if let Some(initial) = &self.initial {
pattern.push_str(®ex::escape(initial));
} else {
pattern.push_str(".*");
}
for any in &self.any {
pattern.push_str(".*");
pattern.push_str(®ex::escape(any));
}
if let Some(final_) = &self.final_ {
pattern.push_str(".*");
pattern.push_str(®ex::escape(final_));
} else {
pattern.push_str(".*");
}
if let Ok(re) = Regex::new(&format!("(?i)^{}$", pattern)) {
re.is_match(value)
} else {
false
}
}
}
impl ExtensibleFilter {
pub fn matches(&self, entry: &LdapEntry) -> bool {
if let Some(attr) = &self.attribute {
if let Some(attribute) = entry.get_attribute(attr) {
attribute
.values
.iter()
.any(|v| self.matches_value(&v.as_string()))
} else {
false
}
} else if self.dn_attributes {
self.matches_dn_components(&entry.dn)
} else {
false
}
}
fn matches_value(&self, value: &str) -> bool {
if let Some(rule) = &self.matching_rule {
match rule.as_str() {
"2.5.13.2" | "caseIgnoreMatch" => value.eq_ignore_ascii_case(&self.value),
"2.5.13.5" | "caseExactMatch" => value == self.value,
_ => {
value.eq_ignore_ascii_case(&self.value)
}
}
} else {
value.eq_ignore_ascii_case(&self.value)
}
}
fn matches_dn_components(&self, dn: &str) -> bool {
for component in dn.split(',') {
let component = component.trim();
if let Some(eq_pos) = component.find('=') {
let value = &component[eq_pos + 1..];
if self.matches_value(value) {
return true;
}
}
}
false
}
}
fn approximate_match(value: &str, pattern: &str) -> bool {
value.to_lowercase().contains(&pattern.to_lowercase())
}
fn unescape_filter_value(value: &str) -> String {
let mut result = String::new();
let mut chars = value.chars();
while let Some(ch) = chars.next() {
if ch == '\\' {
let hex1 = chars.next();
let hex2 = chars.next();
if let (Some(h1), Some(h2)) = (hex1, hex2) {
if let Ok(byte) = u8::from_str_radix(&format!("{}{}", h1, h2), 16) {
if let Ok(s) = std::str::from_utf8(&[byte]) {
result.push_str(s);
} else {
result.push('\\');
result.push(h1);
result.push(h2);
}
} else {
result.push('\\');
result.push(h1);
result.push(h2);
}
} else {
result.push('\\');
if let Some(h1) = hex1 {
result.push(h1);
}
}
} else {
result.push(ch);
}
}
result
}
pub fn parse_ldap_filter(filter_str: &str) -> crate::Result<LdapFilter> {
let filter_str = filter_str.trim();
if filter_str.is_empty() {
return Err(crate::YamlLdapError::Protocol(
"Empty filter string".to_string(),
));
}
if !filter_str.starts_with('(') || !filter_str.ends_with(')') {
return Err(crate::YamlLdapError::Protocol(
"Filter must be wrapped in parentheses".to_string(),
));
}
let inner = &filter_str[1..filter_str.len() - 1];
if let Some(rest) = inner.strip_prefix('&') {
let filters = parse_composite_filters(rest)?;
return Ok(LdapFilter::And(filters));
} else if let Some(rest) = inner.strip_prefix('|') {
let filters = parse_composite_filters(rest)?;
return Ok(LdapFilter::Or(filters));
} else if let Some(rest) = inner.strip_prefix('!') {
let filter = parse_ldap_filter(rest)?;
return Ok(LdapFilter::Not(Box::new(filter)));
}
if let Some(attr_part) = inner.strip_suffix("=*") {
return Ok(LdapFilter::Present(attr_part.to_string()));
}
if let Some(ext_pos) = inner.find(":=") {
return parse_extensible_filter(inner, ext_pos);
}
if let Some(approx_pos) = inner.find("~=") {
let attr = inner[..approx_pos].to_string();
let value = unescape_filter_value(&inner[approx_pos + 2..]);
return Ok(LdapFilter::Approximate(attr, value));
}
if let Some(ge_pos) = inner.find(">=") {
let attr = inner[..ge_pos].to_string();
let value = unescape_filter_value(&inner[ge_pos + 2..]);
return Ok(LdapFilter::GreaterOrEqual(attr, value));
} else if let Some(le_pos) = inner.find("<=") {
let attr = inner[..le_pos].to_string();
let value = unescape_filter_value(&inner[le_pos + 2..]);
return Ok(LdapFilter::LessOrEqual(attr, value));
} else if let Some(eq_pos) = inner.find('=') {
let attr = inner[..eq_pos].to_string();
let value = inner[eq_pos + 1..].to_string();
if value.contains('*') {
let parts: Vec<&str> = value.split('*').collect();
let substring = SubstringFilter {
initial: if parts[0].is_empty() {
None
} else {
Some(unescape_filter_value(parts[0]))
},
any: parts[1..parts.len() - 1]
.iter()
.map(|s| unescape_filter_value(s))
.collect(),
final_: if parts[parts.len() - 1].is_empty() {
None
} else {
Some(unescape_filter_value(parts[parts.len() - 1]))
},
};
return Ok(LdapFilter::Substring(attr, substring));
}
return Ok(LdapFilter::Equality(attr, unescape_filter_value(&value)));
}
Err(crate::YamlLdapError::Protocol(format!(
"Invalid filter format: {}",
filter_str
)))
}
fn parse_extensible_filter(filter_str: &str, ext_pos: usize) -> crate::Result<LdapFilter> {
let left_part = &filter_str[..ext_pos];
let value = unescape_filter_value(&filter_str[ext_pos + 2..]);
let parts: Vec<&str> = left_part.split(':').collect();
let mut attribute = None;
let mut matching_rule = None;
let mut dn_attributes = false;
for (i, part) in parts.iter().enumerate() {
if part.is_empty() {
continue;
}
if *part == "dn" {
dn_attributes = true;
} else if i == 0 && !part.starts_with(|c: char| c.is_numeric()) {
attribute = Some(part.to_string());
} else {
matching_rule = Some(part.to_string());
}
}
if attribute.is_none() && !dn_attributes {
return Err(crate::YamlLdapError::Protocol(
"Extensible filter must specify either an attribute or :dn".to_string(),
));
}
Ok(LdapFilter::Extensible(ExtensibleFilter {
attribute,
matching_rule,
value,
dn_attributes,
}))
}
fn parse_composite_filters(s: &str) -> crate::Result<Vec<LdapFilter>> {
let mut filters = Vec::new();
let mut depth = 0;
let mut start = 0;
for (i, ch) in s.char_indices() {
match ch {
'(' => {
if depth == 0 {
start = i;
}
depth += 1;
}
')' => {
depth -= 1;
if depth == 0 {
let end = i + ')'.len_utf8();
let filter_str = &s[start..end];
filters.push(parse_ldap_filter(filter_str)?);
}
}
_ => {}
}
}
if depth != 0 {
return Err(crate::YamlLdapError::Protocol(
"Unbalanced parentheses in filter".to_string(),
));
}
Ok(filters)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_substring_filter() {
let filter = SubstringFilter {
initial: Some("john".to_string()),
any: vec![],
final_: None,
};
assert!(filter.matches("john"));
assert!(filter.matches("johnny"));
assert!(!filter.matches("ajohn"));
let filter = SubstringFilter {
initial: None,
any: vec!["smith".to_string()],
final_: None,
};
assert!(filter.matches("smith"));
assert!(filter.matches("john smith"));
assert!(filter.matches("smithsonian"));
}
#[test]
fn test_parse_filter() {
let filter = parse_ldap_filter("(cn=*)").unwrap();
assert_eq!(filter, LdapFilter::Present("cn".to_string()));
let filter = parse_ldap_filter("(uid=john)").unwrap();
assert_eq!(
filter,
LdapFilter::Equality("uid".to_string(), "john".to_string())
);
}
#[test]
fn test_parse_and_filter() {
let filter = parse_ldap_filter("(&(objectClass=person)(uid=admin))").unwrap();
match filter {
LdapFilter::And(filters) => {
assert_eq!(filters.len(), 2);
assert_eq!(
filters[0],
LdapFilter::Equality("objectClass".to_string(), "person".to_string())
);
assert_eq!(
filters[1],
LdapFilter::Equality("uid".to_string(), "admin".to_string())
);
}
_ => panic!("Expected AND filter"),
}
}
#[test]
fn test_parse_or_filter() {
let filter = parse_ldap_filter("(|(uid=user1)(uid=user2))").unwrap();
match filter {
LdapFilter::Or(filters) => {
assert_eq!(filters.len(), 2);
assert_eq!(
filters[0],
LdapFilter::Equality("uid".to_string(), "user1".to_string())
);
assert_eq!(
filters[1],
LdapFilter::Equality("uid".to_string(), "user2".to_string())
);
}
_ => panic!("Expected OR filter"),
}
}
#[test]
fn test_parse_not_filter() {
let filter = parse_ldap_filter("(!(uid=admin))").unwrap();
match filter {
LdapFilter::Not(inner) => {
assert_eq!(
*inner,
LdapFilter::Equality("uid".to_string(), "admin".to_string())
);
}
_ => panic!("Expected NOT filter"),
}
}
#[test]
fn test_parse_nested_filters() {
let filter = parse_ldap_filter("(&(objectClass=person)(|(uid=user1)(uid=user2)))").unwrap();
match filter {
LdapFilter::And(filters) => {
assert_eq!(filters.len(), 2);
assert_eq!(
filters[0],
LdapFilter::Equality("objectClass".to_string(), "person".to_string())
);
match &filters[1] {
LdapFilter::Or(or_filters) => {
assert_eq!(or_filters.len(), 2);
}
_ => panic!("Expected nested OR filter"),
}
}
_ => panic!("Expected AND filter"),
}
}
#[test]
fn test_filter_evaluation() {
use crate::directory::entry::{AttributeSyntax, AttributeValue, LdapEntry};
let mut entry = LdapEntry::new("cn=test,dc=example,dc=com".to_string());
entry.add_attribute(
"uid".to_string(),
vec![AttributeValue::String("testuser".to_string())],
AttributeSyntax::String,
);
entry.add_attribute(
"objectClass".to_string(),
vec![AttributeValue::String("person".to_string())],
AttributeSyntax::String,
);
let filter = parse_ldap_filter("(&(objectClass=person)(uid=testuser))").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(&(objectClass=person)(uid=wronguser))").unwrap();
assert!(!filter.matches(&entry));
let filter = parse_ldap_filter("(|(uid=testuser)(uid=otheruser))").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(|(uid=wronguser)(uid=otheruser))").unwrap();
assert!(!filter.matches(&entry));
let filter = parse_ldap_filter("(!(uid=wronguser))").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(!(uid=testuser))").unwrap();
assert!(!filter.matches(&entry));
}
#[test]
fn test_parse_approximate_filter() {
let filter = parse_ldap_filter("(cn~=john)").unwrap();
assert_eq!(
filter,
LdapFilter::Approximate("cn".to_string(), "john".to_string())
);
}
#[test]
fn test_approximate_match() {
use crate::directory::entry::{AttributeSyntax, AttributeValue, LdapEntry};
let mut entry = LdapEntry::new("cn=John Smith,dc=example,dc=com".to_string());
entry.add_attribute(
"cn".to_string(),
vec![AttributeValue::String("John Smith".to_string())],
AttributeSyntax::String,
);
let filter = parse_ldap_filter("(cn~=john)").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(cn~=smith)").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(cn~=jane)").unwrap();
assert!(!filter.matches(&entry));
}
#[test]
fn test_parse_extensible_filter() {
let filter = parse_ldap_filter("(cn:=john)").unwrap();
match filter {
LdapFilter::Extensible(ext) => {
assert_eq!(ext.attribute, Some("cn".to_string()));
assert_eq!(ext.value, "john");
assert!(!ext.dn_attributes);
assert!(ext.matching_rule.is_none());
}
_ => panic!("Expected extensible filter"),
}
let filter = parse_ldap_filter("(cn:dn:=admin)").unwrap();
match filter {
LdapFilter::Extensible(ext) => {
assert_eq!(ext.attribute, Some("cn".to_string()));
assert_eq!(ext.value, "admin");
assert!(ext.dn_attributes);
assert!(ext.matching_rule.is_none());
}
_ => panic!("Expected extensible filter"),
}
let filter = parse_ldap_filter("(cn:caseExactMatch:=John)").unwrap();
match filter {
LdapFilter::Extensible(ext) => {
assert_eq!(ext.attribute, Some("cn".to_string()));
assert_eq!(ext.value, "John");
assert!(!ext.dn_attributes);
assert_eq!(ext.matching_rule, Some("caseExactMatch".to_string()));
}
_ => panic!("Expected extensible filter"),
}
let filter = parse_ldap_filter("(cn:2.5.13.5:=John)").unwrap();
match filter {
LdapFilter::Extensible(ext) => {
assert_eq!(ext.attribute, Some("cn".to_string()));
assert_eq!(ext.value, "John");
assert!(!ext.dn_attributes);
assert_eq!(ext.matching_rule, Some("2.5.13.5".to_string()));
}
_ => panic!("Expected extensible filter"),
}
let filter = parse_ldap_filter("(:dn:=example)").unwrap();
match filter {
LdapFilter::Extensible(ext) => {
assert!(ext.attribute.is_none());
assert_eq!(ext.value, "example");
assert!(ext.dn_attributes);
}
_ => panic!("Expected extensible filter"),
}
}
#[test]
fn test_extensible_filter_matching() {
use crate::directory::entry::{AttributeSyntax, AttributeValue, LdapEntry};
let mut entry = LdapEntry::new("cn=John Smith,ou=users,dc=example,dc=com".to_string());
entry.add_attribute(
"cn".to_string(),
vec![AttributeValue::String("John Smith".to_string())],
AttributeSyntax::String,
);
let filter = parse_ldap_filter("(cn:=john smith)").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(cn:caseExactMatch:=John Smith)").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(cn:caseExactMatch:=john smith)").unwrap();
assert!(!filter.matches(&entry));
let filter = parse_ldap_filter("(:dn:=john smith)").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(:dn:=users)").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(:dn:=example)").unwrap();
assert!(filter.matches(&entry));
}
#[test]
fn test_escape_sequences() {
let filter = parse_ldap_filter("(cn=John\\20Smith)").unwrap();
assert_eq!(
filter,
LdapFilter::Equality("cn".to_string(), "John Smith".to_string())
);
let filter = parse_ldap_filter("(cn=\\28test\\29)").unwrap();
assert_eq!(
filter,
LdapFilter::Equality("cn".to_string(), "(test)".to_string())
);
let filter = parse_ldap_filter("(cn=\\2a)").unwrap();
assert_eq!(
filter,
LdapFilter::Equality("cn".to_string(), "*".to_string())
);
}
#[test]
fn test_unescape_filter_value() {
assert_eq!(unescape_filter_value("test"), "test");
assert_eq!(unescape_filter_value("test\\20value"), "test value");
assert_eq!(unescape_filter_value("\\28test\\29"), "(test)");
assert_eq!(unescape_filter_value("\\2a"), "*");
assert_eq!(unescape_filter_value("\\5c"), "\\");
assert_eq!(unescape_filter_value("\\00"), "\0");
assert_eq!(unescape_filter_value("\\"), "\\");
assert_eq!(unescape_filter_value("\\2"), "\\2");
assert_eq!(unescape_filter_value("\\zz"), "\\zz");
}
#[test]
fn test_parse_filter_edge_cases() {
assert!(parse_ldap_filter("").is_err());
assert!(parse_ldap_filter("(cn=test").is_err());
assert!(parse_ldap_filter("cn=test)").is_err());
assert!(parse_ldap_filter("((cn=test))").is_ok());
assert!(parse_ldap_filter("()").is_err());
assert!(parse_ldap_filter("(cn#=test)").is_ok());
let long_attr = "a".repeat(1000);
let filter = format!("({}=test)", long_attr);
assert!(parse_ldap_filter(&filter).is_ok());
let long_value = "v".repeat(10000);
let filter = format!("(cn={})", long_value);
assert!(parse_ldap_filter(&filter).is_ok());
}
#[test]
fn test_parse_complex_nested_filters() {
let filter = "(&(|(cn=a)(cn=b))(|(sn=c)(sn=d))(!(uid=e)))";
let parsed = parse_ldap_filter(filter).unwrap();
match parsed {
LdapFilter::And(filters) => {
assert_eq!(filters.len(), 3);
assert!(matches!(&filters[0], LdapFilter::Or(_)));
assert!(matches!(&filters[1], LdapFilter::Or(_)));
assert!(matches!(&filters[2], LdapFilter::Not(_)));
}
_ => panic!("Expected AND filter"),
}
let mut nested = String::from("(cn=test)");
for _ in 0..50 {
nested = format!("(!{})", nested);
}
assert!(parse_ldap_filter(&nested).is_ok());
}
#[test]
fn test_extensible_filter_edge_cases() {
let filter = ExtensibleFilter {
attribute: Some("cn".to_string()),
matching_rule: Some(String::new()),
value: "test".to_string(),
dn_attributes: false,
};
assert!(filter.matches_value("test"));
let filter = ExtensibleFilter {
attribute: None,
matching_rule: Some("caseIgnoreMatch".to_string()),
value: "admin".to_string(),
dn_attributes: true,
};
assert!(filter.matches_dn_components("cn=admin,dc=example,dc=com"));
let filter = ExtensibleFilter {
attribute: Some("cn".to_string()),
matching_rule: None,
value: "test".to_string(),
dn_attributes: true,
};
assert!(filter.matches_value("test"));
}
#[test]
fn test_filter_special_characters_in_values() {
let filter = parse_ldap_filter("(cn=用户)").unwrap();
match filter {
LdapFilter::Equality(attr, val) => {
assert_eq!(attr, "cn");
assert_eq!(val, "用户");
}
_ => panic!("Expected equality filter"),
}
let filter = parse_ldap_filter("(description=Hello 😀 World)").unwrap();
match filter {
LdapFilter::Equality(attr, val) => {
assert_eq!(attr, "description");
assert_eq!(val, "Hello 😀 World");
}
_ => panic!("Expected equality filter"),
}
let filter = parse_ldap_filter("(cn=test\\28用户\\29)").unwrap();
match filter {
LdapFilter::Equality(attr, val) => {
assert_eq!(attr, "cn");
assert_eq!(val, "test(用户)");
}
_ => panic!("Expected equality filter"),
}
}
#[test]
fn test_filter_whitespace_handling() {
let filter = parse_ldap_filter("(cn= test )").unwrap();
match filter {
LdapFilter::Equality(attr, val) => {
assert_eq!(attr, "cn");
assert_eq!(val, " test ");
}
_ => panic!("Expected equality filter"),
}
assert!(parse_ldap_filter("(cn = test)").is_ok());
assert!(parse_ldap_filter("(cn =test)").is_ok());
let filter = parse_ldap_filter("(description=line1\nline2\ttab)").unwrap();
match filter {
LdapFilter::Equality(attr, val) => {
assert_eq!(attr, "description");
assert_eq!(val, "line1\nline2\ttab");
}
_ => panic!("Expected equality filter"),
}
}
#[test]
fn test_substring_filter_complex_patterns() {
let filter = parse_ldap_filter("(cn=*a*b*c*)").unwrap();
match filter {
LdapFilter::Substring(attr, sub) => {
assert_eq!(attr, "cn");
assert!(sub.initial.is_none());
assert!(sub.final_.is_none());
assert_eq!(sub.any, vec!["a", "b", "c"]);
}
_ => panic!("Expected substring filter"),
}
let filter = parse_ldap_filter("(cn=**test**)").unwrap();
match filter {
LdapFilter::Substring(attr, sub) => {
assert_eq!(attr, "cn");
assert!(sub.initial.is_none());
assert!(sub.final_.is_none());
let non_empty: Vec<_> = sub.any.iter().filter(|s| !s.is_empty()).collect();
assert_eq!(non_empty, vec!["test"]);
}
_ => panic!("Expected substring filter"),
}
let filter = parse_ldap_filter("(cn=test\\2a*end)").unwrap();
match filter {
LdapFilter::Substring(attr, sub) => {
assert_eq!(attr, "cn");
assert_eq!(sub.initial, Some("test*".to_string()));
assert_eq!(sub.final_, Some("end".to_string()));
}
_ => panic!("Expected substring filter"),
}
}
#[test]
fn test_parse_filter_errors() {
assert!(parse_ldap_filter("(cn=test").is_err());
assert!(parse_ldap_filter("cn=test)").is_err());
assert!(parse_ldap_filter("").is_err());
assert!(parse_ldap_filter("()").is_err());
assert!(parse_ldap_filter("(cn)").is_err());
assert!(parse_ldap_filter("(!)").is_err());
assert!(parse_ldap_filter("(&(cn=test)(").is_err());
}
#[test]
fn test_filter_edge_cases() {
use crate::directory::{AttributeSyntax, AttributeValue};
let mut entry = LdapEntry::new("cn=test,dc=example,dc=com".to_string());
let filter = parse_ldap_filter("(mail=*)").unwrap();
assert!(!filter.matches(&entry));
let filter = parse_ldap_filter("(mail=*test*)").unwrap();
assert!(!filter.matches(&entry));
let filter = parse_ldap_filter("(mail~=test)").unwrap();
assert!(!filter.matches(&entry));
let filter = parse_ldap_filter("(mail:=test)").unwrap();
assert!(!filter.matches(&entry));
entry.add_attribute(
"cn".to_string(),
vec![AttributeValue::String("test".to_string())],
AttributeSyntax::String,
);
entry.add_attribute(
"photo".to_string(),
vec![AttributeValue::Binary(vec![0x01, 0x02, 0x03])],
AttributeSyntax::Binary,
);
let filter = parse_ldap_filter("(photo=*)").unwrap();
assert!(filter.matches(&entry));
let filter = parse_ldap_filter("(photo=*\\01*)").unwrap();
assert!(!filter.matches(&entry));
}
#[test]
fn test_parse_complex_filter_errors() {
let deep_filter = "(".repeat(100) + "cn=test" + &")".repeat(100);
assert!(parse_ldap_filter(&deep_filter).is_ok());
assert!(parse_ldap_filter("(&(cn=test))").is_ok());
}
#[test]
fn test_approximate_match_edge_cases() {
use crate::directory::{AttributeSyntax, AttributeValue};
let mut entry = LdapEntry::new("cn=test,dc=example,dc=com".to_string());
entry.add_attribute(
"description".to_string(),
vec![AttributeValue::String("".to_string())],
AttributeSyntax::String,
);
let filter = parse_ldap_filter("(description~=)").unwrap();
assert!(filter.matches(&entry));
entry.add_attribute(
"title".to_string(),
vec![AttributeValue::String("Software Engineer!".to_string())],
AttributeSyntax::String,
);
let filter = parse_ldap_filter("(title~=software engineer)").unwrap();
assert!(filter.matches(&entry));
}
#[test]
fn test_extensible_match_errors() {
use crate::directory::{AttributeSyntax, AttributeValue};
assert!(parse_ldap_filter("(cn:=test)").is_ok());
let mut entry = LdapEntry::new("cn=test,dc=example,dc=com".to_string());
entry.add_attribute(
"cn".to_string(),
vec![AttributeValue::String("test".to_string())],
AttributeSyntax::String,
);
let filter = parse_ldap_filter("(cn:unknownMatch:=test)").unwrap();
assert!(filter.matches(&entry));
}
}