@@ -53,6 +53,20 @@ var FilterSubstringsMap = map[uint64]string{
5353 FilterSubstringsFinal : "Substrings Final" ,
5454}
5555
56+ const (
57+ MatchingRuleAssertionMatchingRule = 1
58+ MatchingRuleAssertionType = 2
59+ MatchingRuleAssertionMatchValue = 3
60+ MatchingRuleAssertionDNAttributes = 4
61+ )
62+
63+ var MatchingRuleAssertionMap = map [uint64 ]string {
64+ MatchingRuleAssertionMatchingRule : "Matching Rule Assertion Matching Rule" ,
65+ MatchingRuleAssertionType : "Matching Rule Assertion Type" ,
66+ MatchingRuleAssertionMatchValue : "Matching Rule Assertion Match Value" ,
67+ MatchingRuleAssertionDNAttributes : "Matching Rule Assertion DN Attributes" ,
68+ }
69+
5670func CompileFilter (filter string ) (* ber.Packet , error ) {
5771 if len (filter ) == 0 || filter [0 ] != '(' {
5872 return nil , NewError (ErrorFilterCompile , errors .New ("ldap: filter does not start with an '('" ))
@@ -111,7 +125,7 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
111125 if i == 0 && child .Tag != FilterSubstringsInitial {
112126 ret += "*"
113127 }
114- ret += ber .DecodeString (child .Data .Bytes ())
128+ ret += EscapeFilter ( ber .DecodeString (child .Data .Bytes () ))
115129 if child .Tag != FilterSubstringsFinal {
116130 ret += "*"
117131 }
@@ -135,6 +149,37 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
135149 ret += ber .DecodeString (packet .Children [0 ].Data .Bytes ())
136150 ret += "~="
137151 ret += EscapeFilter (ber .DecodeString (packet .Children [1 ].Data .Bytes ()))
152+ case FilterExtensibleMatch :
153+ attr := ""
154+ dnAttributes := false
155+ matchingRule := ""
156+ value := ""
157+
158+ for _ , child := range packet .Children {
159+ switch child .Tag {
160+ case MatchingRuleAssertionMatchingRule :
161+ matchingRule = ber .DecodeString (child .Data .Bytes ())
162+ case MatchingRuleAssertionType :
163+ attr = ber .DecodeString (child .Data .Bytes ())
164+ case MatchingRuleAssertionMatchValue :
165+ value = ber .DecodeString (child .Data .Bytes ())
166+ case MatchingRuleAssertionDNAttributes :
167+ dnAttributes = child .Value .(bool )
168+ }
169+ }
170+
171+ if len (attr ) > 0 {
172+ ret += attr
173+ }
174+ if dnAttributes {
175+ ret += ":dn"
176+ }
177+ if len (matchingRule ) > 0 {
178+ ret += ":"
179+ ret += matchingRule
180+ }
181+ ret += ":="
182+ ret += EscapeFilter (value )
138183 }
139184
140185 ret += ")"
@@ -194,38 +239,107 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
194239 packet .AppendChild (child )
195240 return packet , newPos , err
196241 default :
242+ READING_ATTR := 0
243+ READING_EXTENSIBLE_MATCHING_RULE := 1
244+ READING_CONDITION := 2
245+
246+ state := READING_ATTR
247+
197248 attribute := ""
249+ extensibleDNAttributes := false
250+ extensibleMatchingRule := ""
198251 condition := ""
252+
199253 for newPos < len (filter ) {
200- currentRune , currentWidth = utf8 .DecodeRuneInString (filter [newPos :])
254+ remainingFilter := filter [newPos :]
255+ currentRune , currentWidth = utf8 .DecodeRuneInString (remainingFilter )
201256 if currentRune == ')' {
202257 break
203258 }
204259 if currentRune == utf8 .RuneError {
205260 return packet , newPos , NewError (ErrorFilterCompile , fmt .Errorf ("ldap: error reading rune at position %d" , newPos ))
206261 }
207262
208- nextRune , nextWidth := utf8 .DecodeRuneInString (filter [newPos + currentWidth :])
263+ switch state {
264+ case READING_ATTR :
265+ switch {
266+ // Extensible rule, with only DN-matching
267+ case currentRune == ':' && strings .HasPrefix (remainingFilter , ":dn:=" ):
268+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterExtensibleMatch , nil , FilterMap [FilterExtensibleMatch ])
269+ extensibleDNAttributes = true
270+ state = READING_CONDITION
271+ newPos += 5
272+
273+ // Extensible rule, with DN-matching and a matching OID
274+ case currentRune == ':' && strings .HasPrefix (remainingFilter , ":dn:" ):
275+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterExtensibleMatch , nil , FilterMap [FilterExtensibleMatch ])
276+ extensibleDNAttributes = true
277+ state = READING_EXTENSIBLE_MATCHING_RULE
278+ newPos += 4
279+
280+ // Extensible rule, with attr only
281+ case currentRune == ':' && strings .HasPrefix (remainingFilter , ":=" ):
282+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterExtensibleMatch , nil , FilterMap [FilterExtensibleMatch ])
283+ state = READING_CONDITION
284+ newPos += 2
285+
286+ // Extensible rule, with no DN attribute matching
287+ case currentRune == ':' :
288+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterExtensibleMatch , nil , FilterMap [FilterExtensibleMatch ])
289+ state = READING_EXTENSIBLE_MATCHING_RULE
290+ newPos += 1
291+
292+ // Equality condition
293+ case currentRune == '=' :
294+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterEqualityMatch , nil , FilterMap [FilterEqualityMatch ])
295+ state = READING_CONDITION
296+ newPos += 1
297+
298+ // Greater-than or equal
299+ case currentRune == '>' && strings .HasPrefix (remainingFilter , ">=" ):
300+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterGreaterOrEqual , nil , FilterMap [FilterGreaterOrEqual ])
301+ state = READING_CONDITION
302+ newPos += 2
303+
304+ // Less-than or equal
305+ case currentRune == '<' && strings .HasPrefix (remainingFilter , "<=" ):
306+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterLessOrEqual , nil , FilterMap [FilterLessOrEqual ])
307+ state = READING_CONDITION
308+ newPos += 2
309+
310+ // Approx
311+ case currentRune == '~' && strings .HasPrefix (remainingFilter , "~=" ):
312+ packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterApproxMatch , nil , FilterMap [FilterApproxMatch ])
313+ state = READING_CONDITION
314+ newPos += 2
315+
316+ // Still reading the attribute name
317+ default :
318+ attribute += fmt .Sprintf ("%c" , currentRune )
319+ newPos += currentWidth
320+ }
321+
322+ case READING_EXTENSIBLE_MATCHING_RULE :
323+ switch {
324+
325+ // Matching rule OID is done
326+ case currentRune == ':' && strings .HasPrefix (remainingFilter , ":=" ):
327+ state = READING_CONDITION
328+ newPos += 2
209329
210- switch {
211- case packet != nil :
330+ // Still reading the matching rule oid
331+ default :
332+ extensibleMatchingRule += fmt .Sprintf ("%c" , currentRune )
333+ newPos += currentWidth
334+ }
335+
336+ case READING_CONDITION :
337+ // append to the condition
212338 condition += fmt .Sprintf ("%c" , currentRune )
213- case currentRune == '=' :
214- packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterEqualityMatch , nil , FilterMap [FilterEqualityMatch ])
215- case currentRune == '>' && nextRune == '=' :
216- packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterGreaterOrEqual , nil , FilterMap [FilterGreaterOrEqual ])
217- newPos += nextWidth // we're skipping the next character as well
218- case currentRune == '<' && nextRune == '=' :
219- packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterLessOrEqual , nil , FilterMap [FilterLessOrEqual ])
220- newPos += nextWidth // we're skipping the next character as well
221- case currentRune == '~' && nextRune == '=' :
222- packet = ber .Encode (ber .ClassContext , ber .TypeConstructed , FilterApproxMatch , nil , FilterMap [FilterLessOrEqual ])
223- newPos += nextWidth // we're skipping the next character as well
224- case packet == nil :
225- attribute += fmt .Sprintf ("%c" , currentRune )
339+ newPos += currentWidth
226340 }
227- newPos += currentWidth
228341 }
342+
229343 if newPos == len (filter ) {
230344 err = NewError (ErrorFilterCompile , errors .New ("ldap: unexpected end of filter" ))
231345 return packet , newPos , err
@@ -236,6 +350,36 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
236350 }
237351
238352 switch {
353+ case packet .Tag == FilterExtensibleMatch :
354+ // MatchingRuleAssertion ::= SEQUENCE {
355+ // matchingRule [1] MatchingRuleID OPTIONAL,
356+ // type [2] AttributeDescription OPTIONAL,
357+ // matchValue [3] AssertionValue,
358+ // dnAttributes [4] BOOLEAN DEFAULT FALSE
359+ // }
360+
361+ // Include the matching rule oid, if specified
362+ if len (extensibleMatchingRule ) > 0 {
363+ packet .AppendChild (ber .NewString (ber .ClassContext , ber .TypePrimitive , MatchingRuleAssertionMatchingRule , extensibleMatchingRule , MatchingRuleAssertionMap [MatchingRuleAssertionMatchingRule ]))
364+ }
365+
366+ // Include the attribute, if specified
367+ if len (attribute ) > 0 {
368+ packet .AppendChild (ber .NewString (ber .ClassContext , ber .TypePrimitive , MatchingRuleAssertionType , attribute , MatchingRuleAssertionMap [MatchingRuleAssertionType ]))
369+ }
370+
371+ // Add the value (only required child)
372+ encodedString , err := escapedStringToEncodedBytes (condition )
373+ if err != nil {
374+ return packet , newPos , err
375+ }
376+ packet .AppendChild (ber .NewString (ber .ClassContext , ber .TypePrimitive , MatchingRuleAssertionMatchValue , encodedString , MatchingRuleAssertionMap [MatchingRuleAssertionMatchValue ]))
377+
378+ // Defaults to false, so only include in the sequence if true
379+ if extensibleDNAttributes {
380+ packet .AppendChild (ber .NewBoolean (ber .ClassContext , ber .TypePrimitive , MatchingRuleAssertionDNAttributes , extensibleDNAttributes , MatchingRuleAssertionMap [MatchingRuleAssertionDNAttributes ]))
381+ }
382+
239383 case packet .Tag == FilterEqualityMatch && condition == "*" :
240384 packet = ber .NewString (ber .ClassContext , ber .TypePrimitive , FilterPresent , attribute , FilterMap [FilterPresent ])
241385 case packet .Tag == FilterEqualityMatch && strings .Contains (condition , "*" ):
@@ -257,38 +401,56 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
257401 default :
258402 tag = FilterSubstringsAny
259403 }
260- seq .AppendChild (ber .NewString (ber .ClassContext , ber .TypePrimitive , tag , part , FilterSubstringsMap [uint64 (tag )]))
404+ encodedString , err := escapedStringToEncodedBytes (part )
405+ if err != nil {
406+ return packet , newPos , err
407+ }
408+ seq .AppendChild (ber .NewString (ber .ClassContext , ber .TypePrimitive , tag , encodedString , FilterSubstringsMap [uint64 (tag )]))
261409 }
262410 packet .AppendChild (seq )
263411 default :
264- var buffer bytes.Buffer
265- for i := 0 ; i < len (condition ); i ++ {
266- // Check for escaped hex characters and convert them to their literal value for transport.
267- if condition [i ] == '\\' {
268- // http://tools.ietf.org/search/rfc4515
269- // \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
270- // being a member of UTF1SUBSET.
271- if i + 2 > len (condition ) {
272- err = NewError (ErrorFilterCompile , errors .New ("ldap: missing characters for escape in filter" ))
273- return packet , newPos , err
274- }
275- if escByte , decodeErr := hexpac .DecodeString (condition [i + 1 : i + 3 ]); decodeErr != nil {
276- err = NewError (ErrorFilterCompile , errors .New ("ldap: invalid characters for escape in filter" ))
277- return packet , newPos , err
278- } else {
279- buffer .WriteByte (escByte [0 ])
280- i += 2 // +1 from end of loop, so 3 total for \xx.
281- }
282- } else {
283- buffer .WriteString (string (condition [i ]))
284- }
412+ encodedString , err := escapedStringToEncodedBytes (condition )
413+ if err != nil {
414+ return packet , newPos , err
285415 }
286-
287416 packet .AppendChild (ber .NewString (ber .ClassUniversal , ber .TypePrimitive , ber .TagOctetString , attribute , "Attribute" ))
288- packet .AppendChild (ber .NewString (ber .ClassUniversal , ber .TypePrimitive , ber .TagOctetString , buffer . String () , "Condition" ))
417+ packet .AppendChild (ber .NewString (ber .ClassUniversal , ber .TypePrimitive , ber .TagOctetString , encodedString , "Condition" ))
289418 }
290419
291420 newPos += currentWidth
292421 return packet , newPos , err
293422 }
294423}
424+
425+ // Convert from "ABC\xx\xx\xx" form to literal bytes for transport
426+ func escapedStringToEncodedBytes (escapedString string ) (string , error ) {
427+ var buffer bytes.Buffer
428+ i := 0
429+ for i < len (escapedString ) {
430+ currentRune , currentWidth := utf8 .DecodeRuneInString (escapedString [i :])
431+ if currentRune == utf8 .RuneError {
432+ return "" , NewError (ErrorFilterCompile , fmt .Errorf ("ldap: error reading rune at position %d" , i ))
433+ }
434+
435+ // Check for escaped hex characters and convert them to their literal value for transport.
436+ if currentRune == '\\' {
437+ // http://tools.ietf.org/search/rfc4515
438+ // \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
439+ // being a member of UTF1SUBSET.
440+ if i + 2 > len (escapedString ) {
441+ return "" , NewError (ErrorFilterCompile , errors .New ("ldap: missing characters for escape in filter" ))
442+ }
443+ if escByte , decodeErr := hexpac .DecodeString (escapedString [i + 1 : i + 3 ]); decodeErr != nil {
444+ return "" , NewError (ErrorFilterCompile , errors .New ("ldap: invalid characters for escape in filter" ))
445+ } else {
446+ buffer .WriteByte (escByte [0 ])
447+ i += 2 // +1 from end of loop, so 3 total for \xx.
448+ }
449+ } else {
450+ buffer .WriteRune (currentRune )
451+ }
452+
453+ i += currentWidth
454+ }
455+ return buffer .String (), nil
456+ }
0 commit comments