43
43
class Net ::LDAP ::Filter
44
44
##
45
45
# Known filter types.
46
- FilterTypes = [ :ne , :eq , :ge , :le , :and , :or , :not ]
46
+ FilterTypes = [ :ne , :eq , :ge , :le , :and , :or , :not , :ex ]
47
47
48
48
def initialize ( op , left , right ) #:nodoc:
49
49
unless FilterTypes . include? ( op )
@@ -83,6 +83,55 @@ def eq(attribute, value)
83
83
new ( :eq , attribute , value )
84
84
end
85
85
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
+
86
135
##
87
136
# Creates a Filter object indicating that a particular attribute value
88
137
# is either not present or does not match a particular string; see
@@ -280,29 +329,30 @@ def ~@
280
329
def ==( filter )
281
330
# 20100320 AZ: We need to come up with a better way of doing this. This
282
331
# 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
286
335
287
336
def to_raw_rfc2254
288
337
case @op
289
338
when :ne
290
339
"!(#{ @left } =#{ @right } )"
291
340
when :eq
292
341
"#{ @left } =#{ @right } "
342
+ when :ex
343
+ "#{ @left } :=#{ @right } "
293
344
when :ge
294
345
"#{ @left } >=#{ @right } "
295
346
when :le
296
347
"#{ @left } <=#{ @right } "
297
348
when :and
298
- "&(#{ @left . __send__ ( : to_raw_rfc2254) } )(#{ @right . __send__ ( : to_raw_rfc2254) } )"
349
+ "&(#{ @left . to_raw_rfc2254 } )(#{ @right . to_raw_rfc2254 } )"
299
350
when :or
300
- "|(#{ @left . __send__ ( : to_raw_rfc2254) } )(#{ @right . __send__ ( : to_raw_rfc2254) } )"
351
+ "|(#{ @left . to_raw_rfc2254 } )(#{ @right . to_raw_rfc2254 } )"
301
352
when :not
302
- "!(#{ @left . __send__ ( : to_raw_rfc2254) } )"
353
+ "!(#{ @left . to_raw_rfc2254 } )"
303
354
end
304
355
end
305
- private :to_raw_rfc2254
306
356
307
357
##
308
358
# Converts the Filter object to an RFC 2254-compatible text format.
@@ -317,21 +367,21 @@ def to_s
317
367
##
318
368
# Converts the filter to BER format.
319
369
#--
320
- # to_ber
321
370
# Filter ::=
322
371
# 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
332
382
# }
333
383
#
334
- # SubstringFilter
384
+ # SubstringFilter ::=
335
385
# SEQUENCE {
336
386
# type AttributeType,
337
387
# SEQUENCE OF CHOICE {
@@ -340,6 +390,23 @@ def to_s
340
390
# final [2] LDAPString
341
391
# }
342
392
# }
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
+ #
343
410
#++
344
411
def to_ber
345
412
case @op
@@ -381,6 +448,20 @@ def to_ber
381
448
else # equality
382
449
[ @left . to_s . to_ber , unescape ( @right ) . to_ber ] . to_ber_contextspecific ( 3 )
383
450
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 )
384
465
when :ge
385
466
[ @left . to_s . to_ber , unescape ( @right ) . to_ber ] . to_ber_contextspecific ( 5 )
386
467
when :le
@@ -407,10 +488,10 @@ def to_ber
407
488
# some desired application-defined processing, and may return a
408
489
# locally-meaningful object that will appear as a parameter in the :and,
409
490
# :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
+ #
414
495
# These are the possible values that may be passed to the user-supplied
415
496
# block:
416
497
# * :equalityMatch (the arguments will be an attribute name and a value
@@ -428,26 +509,26 @@ def to_ber
428
509
# a recursive call to #execute, with the same block; and
429
510
# * :not (one argument, which is an object returned from a recursive
430
511
# 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
451
532
452
533
##
453
534
# This is a private helper method for dealing with chains of ANDs and ORs
@@ -588,9 +669,9 @@ def parse_paren_expression(scanner)
588
669
# This parses a given expression inside of parentheses.
589
670
def parse_filter_branch ( scanner )
590
671
scanner . scan ( /\s */ )
591
- if token = scanner . scan ( /[-\w _]+ / )
672
+ if token = scanner . scan ( /[-\w \d _:.]*[ \d \w ] / )
592
673
scanner . scan ( /\s */ )
593
- if op = scanner . scan ( /<=|>=|!=|=/ )
674
+ if op = scanner . scan ( /<=|>=|!=|:=| =/ )
594
675
scanner . scan ( /\s */ )
595
676
if value = scanner . scan ( /(?:[-\w *.+@=,#\$ %&!\s ]|\\ [a-fA-F\d ]{2})+/ )
596
677
# 20100313 AZ: Assumes that "(uid=george*)" is the same as
@@ -606,6 +687,8 @@ def parse_filter_branch(scanner)
606
687
Net ::LDAP ::Filter . le ( token , value )
607
688
when ">="
608
689
Net ::LDAP ::Filter . ge ( token , value )
690
+ when ":="
691
+ Net ::LDAP ::Filter . ex ( token , value )
609
692
end
610
693
end
611
694
end
0 commit comments