4343class Net ::LDAP ::Filter
4444 ##
4545 # Known filter types.
46- FilterTypes = [ :ne , :eq , :ge , :le , :and , :or , :not ]
46+ FilterTypes = [ :ne , :eq , :ge , :le , :and , :or , :not , :ex ]
4747
4848 def initialize ( op , left , right ) #:nodoc:
4949 unless FilterTypes . include? ( op )
@@ -83,6 +83,55 @@ def eq(attribute, value)
8383 new ( :eq , attribute , value )
8484 end
8585
86+ ##
87+ # Creates a Filter object indicating extensible comparison. This Filter
88+ # object is currently considered EXPERIMENTAL.
89+ #
90+ # sample_attributes = ['cn:fr', 'cn:fr.eq',
91+ # 'cn:1.3.6.1.4.1.42.2.27.9.4.49.1.3', 'cn:dn:fr', 'cn:dn:fr.eq']
92+ # attr = sample_attributes.first # Pick an extensible attribute
93+ # value = 'roberts'
94+ #
95+ # filter = "#{attr}:=#{value}" # Basic String Filter
96+ # filter = Net::LDAP::Filter.ex(attr, value) # Net::LDAP::Filter
97+ #
98+ # # Perform a search with the Extensible Match Filter
99+ # Net::LDAP.search(:filter => filter)
100+ #--
101+ # The LDIF required to support the above examples on the OpenDS LDAP
102+ # server:
103+ #
104+ # version: 1
105+ #
106+ # dn: dc=example,dc=com
107+ # objectClass: domain
108+ # objectClass: top
109+ # dc: example
110+ #
111+ # dn: ou=People,dc=example,dc=com
112+ # objectClass: organizationalUnit
113+ # objectClass: top
114+ # ou: People
115+ #
116+ # dn: uid=1,ou=People,dc=example,dc=com
117+ # objectClass: person
118+ # objectClass: organizationalPerson
119+ # objectClass: inetOrgPerson
120+ # objectClass: top
121+ # cn:: csO0YsOpcnRz
122+ # sn:: YsO0YiByw7Riw6lydHM=
123+ # givenName:: YsO0Yg==
124+ # uid: 1
125+ #
126+ # =Refs:
127+ # * http://www.ietf.org/rfc/rfc2251.txt
128+ # * http://www.novell.com/documentation/edir88/edir88/?page=/documentation/edir88/edir88/data/agazepd.html
129+ # * https://docs.opends.org/2.0/page/SearchingUsingInternationalCollationRules
130+ #++
131+ def ex ( attribute , value )
132+ new ( :ex , attribute , value )
133+ end
134+
86135 ##
87136 # Creates a Filter object indicating that a particular attribute value
88137 # is either not present or does not match a particular string; see
@@ -280,29 +329,30 @@ def ~@
280329 def ==( filter )
281330 # 20100320 AZ: We need to come up with a better way of doing this. This
282331 # is just nasty.
283- str = "[@op,@left,@right]"
284- self . instance_eval ( str ) == filter . instance_eval ( str )
285- end
332+ str = "[@op,@left,@right]"
333+ self . instance_eval ( str ) == filter . instance_eval ( str )
334+ end
286335
287336 def to_raw_rfc2254
288337 case @op
289338 when :ne
290339 "!(#{ @left } =#{ @right } )"
291340 when :eq
292341 "#{ @left } =#{ @right } "
342+ when :ex
343+ "#{ @left } :=#{ @right } "
293344 when :ge
294345 "#{ @left } >=#{ @right } "
295346 when :le
296347 "#{ @left } <=#{ @right } "
297348 when :and
298- "&(#{ @left . __send__ ( : to_raw_rfc2254) } )(#{ @right . __send__ ( : to_raw_rfc2254) } )"
349+ "&(#{ @left . to_raw_rfc2254 } )(#{ @right . to_raw_rfc2254 } )"
299350 when :or
300- "|(#{ @left . __send__ ( : to_raw_rfc2254) } )(#{ @right . __send__ ( : to_raw_rfc2254) } )"
351+ "|(#{ @left . to_raw_rfc2254 } )(#{ @right . to_raw_rfc2254 } )"
301352 when :not
302- "!(#{ @left . __send__ ( : to_raw_rfc2254) } )"
353+ "!(#{ @left . to_raw_rfc2254 } )"
303354 end
304355 end
305- private :to_raw_rfc2254
306356
307357 ##
308358 # Converts the Filter object to an RFC 2254-compatible text format.
@@ -317,21 +367,21 @@ def to_s
317367 ##
318368 # Converts the filter to BER format.
319369 #--
320- # to_ber
321370 # Filter ::=
322371 # CHOICE {
323- # and [0] SET OF Filter,
324- # or [1] SET OF Filter,
325- # not [2] Filter,
326- # equalityMatch [3] AttributeValueAssertion,
327- # substrings [4] SubstringFilter,
328- # greaterOrEqual [5] AttributeValueAssertion,
329- # lessOrEqual [6] AttributeValueAssertion,
330- # present [7] AttributeType,
331- # approxMatch [8] AttributeValueAssertion
372+ # and [0] SET OF Filter,
373+ # or [1] SET OF Filter,
374+ # not [2] Filter,
375+ # equalityMatch [3] AttributeValueAssertion,
376+ # substrings [4] SubstringFilter,
377+ # greaterOrEqual [5] AttributeValueAssertion,
378+ # lessOrEqual [6] AttributeValueAssertion,
379+ # present [7] AttributeType,
380+ # approxMatch [8] AttributeValueAssertion,
381+ # extensibleMatch [9] MatchingRuleAssertion
332382 # }
333383 #
334- # SubstringFilter
384+ # SubstringFilter ::=
335385 # SEQUENCE {
336386 # type AttributeType,
337387 # SEQUENCE OF CHOICE {
@@ -340,6 +390,23 @@ def to_s
340390 # final [2] LDAPString
341391 # }
342392 # }
393+ #
394+ # MatchingRuleAssertion ::=
395+ # SEQUENCE {
396+ # matchingRule [1] MatchingRuleId OPTIONAL,
397+ # type [2] AttributeDescription OPTIONAL,
398+ # matchValue [3] AssertionValue,
399+ # dnAttributes [4] BOOLEAN DEFAULT FALSE
400+ # }
401+ #
402+ # Matching Rule Suffixes
403+ # Less than [.1] or .[lt]
404+ # Less than or equal to [.2] or [.lte]
405+ # Equality [.3] or [.eq] (default)
406+ # Greater than or equal to [.4] or [.gte]
407+ # Greater than [.5] or [.gt]
408+ # Substring [.6] or [.sub]
409+ #
343410 #++
344411 def to_ber
345412 case @op
@@ -381,6 +448,20 @@ def to_ber
381448 else # equality
382449 [ @left . to_s . to_ber , unescape ( @right ) . to_ber ] . to_ber_contextspecific ( 3 )
383450 end
451+ when :ex
452+ seq = [ ]
453+
454+ unless @left =~ /^([-;\d \w ]*)(:dn)?(:(\w +|[.\d \w ]+))?$/
455+ raise Net ::LDAP ::LdapError , "Bad attribute #{ @left } "
456+ end
457+ type , dn , rule = $1, $2, $4
458+
459+ seq << rule . to_ber_contextspecific ( 1 ) unless rule . to_s . empty? # matchingRule
460+ seq << type . to_ber_contextspecific ( 2 ) unless type . to_s . empty? # type
461+ seq << unescape ( @right ) . to_ber_contextspecific ( 3 ) # matchingValue
462+ seq << "1" . to_ber_contextspecific ( 4 ) unless dn . to_s . empty? # dnAttributes
463+
464+ seq . to_ber_contextspecific ( 9 )
384465 when :ge
385466 [ @left . to_s . to_ber , unescape ( @right ) . to_ber ] . to_ber_contextspecific ( 5 )
386467 when :le
@@ -407,10 +488,10 @@ def to_ber
407488 # some desired application-defined processing, and may return a
408489 # locally-meaningful object that will appear as a parameter in the :and,
409490 # :or and :not operations detailed below.
410- #
411- # A typical object to return from the user-supplied block is an array of
412- # Net::LDAP::Filter objects.
413- #
491+ #
492+ # A typical object to return from the user-supplied block is an array of
493+ # Net::LDAP::Filter objects.
494+ #
414495 # These are the possible values that may be passed to the user-supplied
415496 # block:
416497 # * :equalityMatch (the arguments will be an attribute name and a value
@@ -428,26 +509,26 @@ def to_ber
428509 # a recursive call to #execute, with the same block; and
429510 # * :not (one argument, which is an object returned from a recursive
430511 # call to #execute with the the same block.
431- def execute ( &block )
432- case @op
433- when :eq
434- if @right == "*"
435- yield :present , @left
436- elsif @right . index '*'
437- yield :substrings , @left , @right
438- else
439- yield :equalityMatch , @left , @right
440- end
441- when :ge
442- yield :greaterOrEqual , @left , @right
443- when :le
444- yield :lessOrEqual , @left , @right
445- when :or , :and
446- yield @op , ( @left . execute ( &block ) ) , ( @right . execute ( &block ) )
447- when :not
448- yield @op , ( @left . execute ( &block ) )
449- end || [ ]
450- end
512+ def execute ( &block )
513+ case @op
514+ when :eq
515+ if @right == "*"
516+ yield :present , @left
517+ elsif @right . index '*'
518+ yield :substrings , @left , @right
519+ else
520+ yield :equalityMatch , @left , @right
521+ end
522+ when :ge
523+ yield :greaterOrEqual , @left , @right
524+ when :le
525+ yield :lessOrEqual , @left , @right
526+ when :or , :and
527+ yield @op , ( @left . execute ( &block ) ) , ( @right . execute ( &block ) )
528+ when :not
529+ yield @op , ( @left . execute ( &block ) )
530+ end || [ ]
531+ end
451532
452533 ##
453534 # This is a private helper method for dealing with chains of ANDs and ORs
@@ -588,9 +669,9 @@ def parse_paren_expression(scanner)
588669 # This parses a given expression inside of parentheses.
589670 def parse_filter_branch ( scanner )
590671 scanner . scan ( /\s */ )
591- if token = scanner . scan ( /[-\w _]+ / )
672+ if token = scanner . scan ( /[-\w \d _:.]*[ \d \w ] / )
592673 scanner . scan ( /\s */ )
593- if op = scanner . scan ( /<=|>=|!=|=/ )
674+ if op = scanner . scan ( /<=|>=|!=|:=| =/ )
594675 scanner . scan ( /\s */ )
595676 if value = scanner . scan ( /(?:[-\w *.+@=,#\$ %&!\s ]|\\ [a-fA-F\d ]{2})+/ )
596677 # 20100313 AZ: Assumes that "(uid=george*)" is the same as
@@ -606,6 +687,8 @@ def parse_filter_branch(scanner)
606687 Net ::LDAP ::Filter . le ( token , value )
607688 when ">="
608689 Net ::LDAP ::Filter . ge ( token , value )
690+ when ":="
691+ Net ::LDAP ::Filter . ex ( token , value )
609692 end
610693 end
611694 end
0 commit comments