@@ -14,6 +14,29 @@ class LDAP::Request::Filter
14
14
Extensible
15
15
end
16
16
17
+ ESCAPES = {
18
+ " \0 " => " \\ 00" , # NUL = %x00 ; null character
19
+ " *" => " \\ 2A" , # ASTERISK = %x2A ; asterisk ("*")
20
+ " (" => " \\ 28" , # LPARENS = %x28 ; left parenthesis ("(")
21
+ " )" => " \\ 29" , # RPARENS = %x29 ; right parenthesis (")")
22
+ " \\ " => " \\ 5C" , # ESC = %x5C ; esc (or backslash) ("\")
23
+ }
24
+ # Compiled character class regexp using the keys from the above hash.
25
+ ESCAPE_RE = Regex .new(String .build { |str |
26
+ str << " ["
27
+ ESCAPES .keys.each { |e | str << Regex .escape(e) }
28
+ str << " ]"
29
+ })
30
+
31
+ # Escape a string for use in an LDAP filter
32
+ def self.escape (string : String ) : String
33
+ string.gsub(ESCAPE_RE ) { |char | ESCAPES [char] }
34
+ end
35
+
36
+ def self.unescape (value : String , escaped : Bool = false ) : String
37
+ escaped ? value.gsub(/\\ ([a-fA-F\d ] {2} ) / ) { String .new($1 .hexbytes) } : value
38
+ end
39
+
17
40
def initialize (@operation : Type , @filter : BER )
18
41
end
19
42
@@ -27,7 +50,7 @@ class LDAP::Request::Filter
27
50
to_ber.to_slice
28
51
end
29
52
30
- def self.equal (object : String , value)
53
+ def self.equal (object : String , value, escaped : Bool = false )
31
54
value = value.to_s
32
55
if value == " *"
33
56
self .new(Type ::Equal , BER .new.set_string(object, 7 , TagClass ::ContextSpecific ))
@@ -38,17 +61,17 @@ class LDAP::Request::Filter
38
61
first = nil
39
62
ary.shift
40
63
else
41
- first = BER .new.set_string(ary.shift, 0 , TagClass ::ContextSpecific )
64
+ first = BER .new.set_string(unescape( ary.shift, escaped) , 0 , TagClass ::ContextSpecific )
42
65
end
43
66
44
67
if ary.last.empty?
45
68
last = nil
46
69
ary.pop
47
70
else
48
- last = BER .new.set_string(ary.pop, 2 , TagClass ::ContextSpecific )
71
+ last = BER .new.set_string(unescape( ary.pop, escaped) , 2 , TagClass ::ContextSpecific )
49
72
end
50
73
51
- seq = ary.map { |e | BER .new.set_string(e , 1 , TagClass ::ContextSpecific ) }
74
+ seq = ary.map { |e | BER .new.set_string(unescape(e, escaped) , 1 , TagClass ::ContextSpecific ) }
52
75
seq.unshift first if first
53
76
seq.push last if last
54
77
seq = LDAP .sequence(seq)
@@ -57,7 +80,7 @@ class LDAP::Request::Filter
57
80
self .new(Type ::Equal , LDAP .context_sequence({left, seq}, 4 ))
58
81
else
59
82
left = BER .new.set_string(object, UniversalTags ::OctetString )
60
- right = BER .new.set_string(value, UniversalTags ::OctetString )
83
+ right = BER .new.set_string(unescape( value, escaped) , UniversalTags ::OctetString )
61
84
self .new(Type ::Equal , LDAP .context_sequence({left, right}, 3 ))
62
85
end
63
86
end
@@ -74,10 +97,10 @@ class LDAP::Request::Filter
74
97
self .new(Type ::LessThanOrEqual , LDAP .context_sequence({left, right}, 6 ))
75
98
end
76
99
77
- def self.not_equal (object : String , value)
100
+ def self.not_equal (object : String , value, escaped : Bool = false )
78
101
self .new(
79
102
Type ::NotEqual ,
80
- LDAP .context_sequence({equal(object, value).to_ber}, 2 )
103
+ LDAP .context_sequence({equal(object, value, escaped ).to_ber}, 2 )
81
104
)
82
105
end
83
106
@@ -105,6 +128,22 @@ class LDAP::Request::Filter
105
128
self .new(Type ::Or , LDAP .context_sequence({left.to_ber, right.to_ber}, 1 ))
106
129
end
107
130
131
+ def self.begins (attribute : String , value)
132
+ equal(attribute, " #{ escape(value.to_s) } *" , escaped: true )
133
+ end
134
+
135
+ def self.ends (attribute : String , value)
136
+ equal(attribute, " *#{ escape(value.to_s) } " , escaped: true )
137
+ end
138
+
139
+ def self.contains (attribute : String , value)
140
+ equal(attribute, " *#{ escape(value.to_s) } *" , escaped: true )
141
+ end
142
+
143
+ def self.present ?(attribute : String )
144
+ equal(attribute, " *" )
145
+ end
146
+
108
147
# Joins two or more filters so that all conditions must be true.
109
148
def & (filter )
110
149
self .class.join(self , filter)
0 commit comments