net-ldap 0.3.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/Contributors.rdoc +4 -0
  3. data/Hacking.rdoc +3 -8
  4. data/History.rdoc +181 -0
  5. data/README.rdoc +44 -12
  6. data/lib/net-ldap.rb +1 -1
  7. data/lib/net/ber.rb +41 -7
  8. data/lib/net/ber/ber_parser.rb +21 -7
  9. data/lib/net/ber/core_ext.rb +11 -18
  10. data/lib/net/ber/core_ext/array.rb +14 -0
  11. data/lib/net/ber/core_ext/integer.rb +74 -0
  12. data/lib/net/ber/core_ext/string.rb +24 -4
  13. data/lib/net/ber/core_ext/true_class.rb +2 -3
  14. data/lib/net/ldap.rb +441 -639
  15. data/lib/net/ldap/auth_adapter.rb +29 -0
  16. data/lib/net/ldap/auth_adapter/gss_spnego.rb +41 -0
  17. data/lib/net/ldap/auth_adapter/sasl.rb +62 -0
  18. data/lib/net/ldap/auth_adapter/simple.rb +34 -0
  19. data/lib/net/ldap/connection.rb +716 -0
  20. data/lib/net/ldap/dataset.rb +23 -9
  21. data/lib/net/ldap/dn.rb +13 -14
  22. data/lib/net/ldap/entry.rb +27 -9
  23. data/lib/net/ldap/error.rb +49 -0
  24. data/lib/net/ldap/filter.rb +58 -32
  25. data/lib/net/ldap/instrumentation.rb +23 -0
  26. data/lib/net/ldap/password.rb +23 -14
  27. data/lib/net/ldap/pdu.rb +70 -6
  28. data/lib/net/ldap/version.rb +5 -0
  29. data/lib/net/snmp.rb +237 -241
  30. metadata +71 -116
  31. data/.autotest +0 -11
  32. data/.gemtest +0 -0
  33. data/.rspec +0 -2
  34. data/Manifest.txt +0 -49
  35. data/Rakefile +0 -74
  36. data/autotest/discover.rb +0 -1
  37. data/lib/net/ber/core_ext/bignum.rb +0 -22
  38. data/lib/net/ber/core_ext/fixnum.rb +0 -66
  39. data/net-ldap.gemspec +0 -58
  40. data/spec/integration/ssl_ber_spec.rb +0 -36
  41. data/spec/spec.opts +0 -2
  42. data/spec/spec_helper.rb +0 -5
  43. data/spec/unit/ber/ber_spec.rb +0 -109
  44. data/spec/unit/ber/core_ext/string_spec.rb +0 -51
  45. data/spec/unit/ldap/dn_spec.rb +0 -80
  46. data/spec/unit/ldap/entry_spec.rb +0 -51
  47. data/spec/unit/ldap/filter_spec.rb +0 -84
  48. data/spec/unit/ldap_spec.rb +0 -48
  49. data/test/common.rb +0 -3
  50. data/test/test_entry.rb +0 -59
  51. data/test/test_filter.rb +0 -122
  52. data/test/test_ldap_connection.rb +0 -24
  53. data/test/test_ldif.rb +0 -79
  54. data/test/test_password.rb +0 -17
  55. data/test/test_rename.rb +0 -77
  56. data/test/test_snmp.rb +0 -114
  57. data/test/testdata.ldif +0 -101
  58. data/testserver/ldapserver.rb +0 -210
  59. data/testserver/testdata.ldif +0 -101
@@ -1,14 +1,18 @@
1
+ require_relative 'entry'
2
+
1
3
  # -*- ruby encoding: utf-8 -*-
2
4
  ##
3
5
  # An LDAP Dataset. Used primarily as an intermediate format for converting
4
6
  # to and from LDIF strings and Net::LDAP::Entry objects.
5
7
  class Net::LDAP::Dataset < Hash
6
8
  ##
7
- # Dataset object comments.
8
- attr_reader :comments
9
+ # Dataset object version, comments.
10
+ attr_accessor :version
11
+ attr_reader :comments
9
12
 
10
13
  def initialize(*args, &block) # :nodoc:
11
14
  super
15
+ @version = nil
12
16
  @comments = []
13
17
  end
14
18
 
@@ -17,11 +21,17 @@ class Net::LDAP::Dataset < Hash
17
21
  # entries.
18
22
  def to_ldif
19
23
  ary = []
24
+
25
+ if version
26
+ ary << "version: #{version}"
27
+ ary << ""
28
+ end
29
+
20
30
  ary += @comments unless @comments.empty?
21
31
  keys.sort.each do |dn|
22
32
  ary << "dn: #{dn}"
23
33
 
24
- attributes = self[dn].keys.map { |attr| attr.to_s }.sort
34
+ attributes = self[dn].keys.map(&:to_s).sort
25
35
  attributes.each do |attr|
26
36
  self[dn][attr.to_sym].each do |value|
27
37
  if attr == "userpassword" or value_is_binary?(value)
@@ -95,7 +105,7 @@ class Net::LDAP::Dataset < Hash
95
105
  # with the conversion of
96
106
  def from_entry(entry)
97
107
  dataset = Net::LDAP::Dataset.new
98
- hash = { }
108
+ hash = {}
99
109
  entry.each_attribute do |attribute, value|
100
110
  next if attribute == :dn
101
111
  hash[attribute] = value
@@ -125,9 +135,15 @@ class Net::LDAP::Dataset < Hash
125
135
  if line =~ /^#/
126
136
  ds.comments << line
127
137
  yield :comment, line if block_given?
128
- elsif line =~ /^dn:[\s]*/i
129
- dn = $'
130
- ds[dn] = Hash.new { |k,v| k[v] = [] }
138
+ elsif line =~ /^version:[\s]*([0-9]+)$/i
139
+ ds.version = $1
140
+ yield :version, line if block_given?
141
+ elsif line =~ /^dn:([\:]?)[\s]*/i
142
+ # $1 is a colon if the dn-value is base-64 encoded
143
+ # $' is the dn-value
144
+ # Avoid the Base64 class because not all Ruby versions have it.
145
+ dn = ($1 == ":") ? $'.unpack('m').shift : $'
146
+ ds[dn] = Hash.new { |k, v| k[v] = [] }
131
147
  yield :dn, dn if block_given?
132
148
  elsif line.empty?
133
149
  dn = nil
@@ -150,5 +166,3 @@ class Net::LDAP::Dataset < Hash
150
166
  end
151
167
  end
152
168
  end
153
-
154
- require 'net/ldap/entry' unless defined? Net::LDAP::Entry
data/lib/net/ldap/dn.rb CHANGED
@@ -57,19 +57,19 @@ class Net::LDAP::DN
57
57
  state = :key_oid
58
58
  key << char
59
59
  when ' ' then state = :key
60
- else raise "DN badly formed"
60
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
61
61
  end
62
62
  when :key_normal then
63
63
  case char
64
64
  when '=' then state = :value
65
65
  when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
66
- else raise "DN badly formed"
66
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
67
67
  end
68
68
  when :key_oid then
69
69
  case char
70
70
  when '=' then state = :value
71
71
  when '0'..'9', '.', ' ' then key << char
72
- else raise "DN badly formed"
72
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
73
73
  end
74
74
  when :value then
75
75
  case char
@@ -110,7 +110,7 @@ class Net::LDAP::DN
110
110
  when '0'..'9', 'a'..'f', 'A'..'F' then
111
111
  state = :value_normal
112
112
  value << "#{hex_buffer}#{char}".to_i(16).chr
113
- else raise "DN badly formed"
113
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
114
114
  end
115
115
  when :value_quoted then
116
116
  case char
@@ -132,7 +132,7 @@ class Net::LDAP::DN
132
132
  when '0'..'9', 'a'..'f', 'A'..'F' then
133
133
  state = :value_quoted
134
134
  value << "#{hex_buffer}#{char}".to_i(16).chr
135
- else raise "DN badly formed"
135
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
136
136
  end
137
137
  when :value_hexstring then
138
138
  case char
@@ -145,14 +145,14 @@ class Net::LDAP::DN
145
145
  yield key.string.strip, value.string.rstrip
146
146
  key = StringIO.new
147
147
  value = StringIO.new;
148
- else raise "DN badly formed"
148
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
149
149
  end
150
150
  when :value_hexstring_hex then
151
151
  case char
152
152
  when '0'..'9', 'a'..'f', 'A'..'F' then
153
153
  state = :value_hexstring
154
154
  value << char
155
- else raise "DN badly formed"
155
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
156
156
  end
157
157
  when :value_end then
158
158
  case char
@@ -162,18 +162,17 @@ class Net::LDAP::DN
162
162
  yield key.string.strip, value.string.rstrip
163
163
  key = StringIO.new
164
164
  value = StringIO.new;
165
- else raise "DN badly formed"
165
+ else raise Net::LDAP::InvalidDNError, "DN badly formed"
166
166
  end
167
- else raise "Fell out of state machine"
167
+ else raise Net::LDAP::InvalidDNError, "Fell out of state machine"
168
168
  end
169
169
  end
170
170
 
171
171
  # Last pair
172
- if [:value, :value_normal, :value_hexstring, :value_end].include? state
173
- yield key.string.strip, value.string.rstrip
174
- else
175
- raise "DN badly formed"
176
- end
172
+ raise Net::LDAP::InvalidDNError, "DN badly formed" unless
173
+ [:value, :value_normal, :value_hexstring, :value_end].include? state
174
+
175
+ yield key.string.strip, value.string.rstrip
177
176
  end
178
177
 
179
178
  ##
@@ -1,3 +1,5 @@
1
+ require_relative 'dataset'
2
+
1
3
  # -*- ruby encoding: utf-8 -*-
2
4
  ##
3
5
  # Objects of this class represent individual entries in an LDAP directory.
@@ -71,7 +73,7 @@ class Net::LDAP::Entry
71
73
 
72
74
  return nil if ds.empty?
73
75
 
74
- raise Net::LDAP::LdapError, "Too many LDIF entries" unless ds.size == 1
76
+ raise Net::LDAP::EntryOverflowError, "Too many LDIF entries" unless ds.size == 1
75
77
 
76
78
  entry = ds.to_entries.first
77
79
 
@@ -113,6 +115,14 @@ class Net::LDAP::Entry
113
115
  @myhash[name] || []
114
116
  end
115
117
 
118
+ ##
119
+ # Read the first value for the provided attribute. The attribute name
120
+ # is canonicalized prior to reading. Returns nil if the attribute does
121
+ # not exist.
122
+ def first(name)
123
+ self[name].first
124
+ end
125
+
116
126
  ##
117
127
  # Returns the first distinguished name (dn) of the Entry as a \String.
118
128
  def dn
@@ -125,6 +135,13 @@ class Net::LDAP::Entry
125
135
  @myhash.keys
126
136
  end
127
137
 
138
+ ##
139
+ # Creates a duplicate of the internal Hash containing the attributes
140
+ # of the entry.
141
+ def to_h
142
+ @myhash.dup
143
+ end
144
+
128
145
  ##
129
146
  # Accesses each of the attributes present in the Entry.
130
147
  #
@@ -132,11 +149,10 @@ class Net::LDAP::Entry
132
149
  # arguments to the block: a Symbol giving the name of the attribute, and a
133
150
  # (possibly empty) \Array of data values.
134
151
  def each # :yields: attribute-name, data-values-array
135
- if block_given?
136
- attribute_names.each {|a|
137
- attr_name,values = a,self[a]
138
- yield attr_name, values
139
- }
152
+ return unless block_given?
153
+ attribute_names.each do|a|
154
+ attr_name, values = a, self[a]
155
+ yield attr_name, values
140
156
  end
141
157
  end
142
158
  alias_method :each_attribute, :each
@@ -147,7 +163,7 @@ class Net::LDAP::Entry
147
163
  Net::LDAP::Dataset.from_entry(self).to_ldif_string
148
164
  end
149
165
 
150
- def respond_to?(sym) #:nodoc:
166
+ def respond_to?(sym, include_all = false) #:nodoc:
151
167
  return true if valid_attribute?(self.class.attribute_name(sym))
152
168
  return super
153
169
  end
@@ -180,6 +196,8 @@ class Net::LDAP::Entry
180
196
  sym.to_s[-1] == ?=
181
197
  end
182
198
  private :setter?
183
- end # class Entry
184
199
 
185
- require 'net/ldap/dataset' unless defined? Net::LDAP::Dataset
200
+ def ==(other)
201
+ other.instance_of?(self.class) && @myhash == other.to_h
202
+ end
203
+ end # class Entry
@@ -0,0 +1,49 @@
1
+ class Net::LDAP
2
+ class Error < StandardError; end
3
+
4
+ class AlreadyOpenedError < Error; end
5
+ class SocketError < Error; end
6
+ class ConnectionError < Error
7
+ def self.new(errors)
8
+ error = errors.first.first
9
+ if errors.size == 1
10
+ return error if error.is_a? Errno::ECONNREFUSED
11
+
12
+ return Net::LDAP::Error.new(error.message)
13
+ end
14
+
15
+ super
16
+ end
17
+
18
+ def initialize(errors)
19
+ message = "Unable to connect to any given server: \n #{errors.map { |e, h, p| "#{e.class}: #{e.message} (#{h}:#{p})" }.join("\n ")}"
20
+ super(message)
21
+ end
22
+ end
23
+ class NoOpenSSLError < Error; end
24
+ class NoStartTLSResultError < Error; end
25
+ class NoSearchBaseError < Error; end
26
+ class StartTLSError < Error; end
27
+ class EncryptionUnsupportedError < Error; end
28
+ class EncMethodUnsupportedError < Error; end
29
+ class AuthMethodUnsupportedError < Error; end
30
+ class BindingInformationInvalidError < Error; end
31
+ class NoBindResultError < Error; end
32
+ class SASLChallengeOverflowError < Error; end
33
+ class SearchSizeInvalidError < Error; end
34
+ class SearchScopeInvalidError < Error; end
35
+ class ResponseTypeInvalidError < Error; end
36
+ class ResponseMissingOrInvalidError < Error; end
37
+ class EmptyDNError < Error; end
38
+ class InvalidDNError < Error; end
39
+ class HashTypeUnsupportedError < Error; end
40
+ class OperatorError < Error; end
41
+ class SubstringFilterError < Error; end
42
+ class SearchFilterError < Error; end
43
+ class BERInvalidError < Error; end
44
+ class SearchFilterTypeUnknownError < Error; end
45
+ class BadAttributeError < Error; end
46
+ class FilterTypeUnknownError < Error; end
47
+ class FilterSyntaxInvalidError < Error; end
48
+ class EntryOverflowError < Error; end
49
+ end
@@ -23,11 +23,11 @@
23
23
  class Net::LDAP::Filter
24
24
  ##
25
25
  # Known filter types.
26
- FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex ]
26
+ FilterTypes = [:ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq]
27
27
 
28
28
  def initialize(op, left, right) #:nodoc:
29
29
  unless FilterTypes.include?(op)
30
- raise Net::LDAP::LdapError, "Invalid or unsupported operator #{op.inspect} in LDAP Filter."
30
+ raise Net::LDAP::OperatorError, "Invalid or unsupported operator #{op.inspect} in LDAP Filter."
31
31
  end
32
32
  @op = op
33
33
  @left = left
@@ -65,6 +65,23 @@ class Net::LDAP::Filter
65
65
  new(:eq, attribute, value)
66
66
  end
67
67
 
68
+ ##
69
+ # Creates a Filter object indicating a binary comparison.
70
+ # this prevents the search data from being forced into a UTF-8 string.
71
+ #
72
+ # This is primarily used for Microsoft Active Directory to compare
73
+ # GUID values.
74
+ #
75
+ # # for guid represented as hex charecters
76
+ # guid = "6a31b4a12aa27a41aca9603f27dd5116"
77
+ # guid_bin = [guid].pack("H*")
78
+ # f = Net::LDAP::Filter.bineq("objectGUID", guid_bin)
79
+ #
80
+ # This filter does not perform any escaping.
81
+ def bineq(attribute, value)
82
+ new(:bineq, attribute, value)
83
+ end
84
+
68
85
  ##
69
86
  # Creates a Filter object indicating extensible comparison. This Filter
70
87
  # object is currently considered EXPERIMENTAL.
@@ -225,7 +242,7 @@ class Net::LDAP::Filter
225
242
 
226
243
  # http://tools.ietf.org/html/rfc4515 lists these exceptions from UTF1
227
244
  # charset for filters. All of the following must be escaped in any normal
228
- # string using a single backslash ('\') as escape.
245
+ # string using a single backslash ('\') as escape.
229
246
  #
230
247
  ESCAPES = {
231
248
  "\0" => '00', # NUL = %x00 ; null character
@@ -234,10 +251,10 @@ class Net::LDAP::Filter
234
251
  ')' => '29', # RPARENS = %x29 ; right parenthesis (")")
235
252
  '\\' => '5C', # ESC = %x5C ; esc (or backslash) ("\")
236
253
  }
237
- # Compiled character class regexp using the keys from the above hash.
254
+ # Compiled character class regexp using the keys from the above hash.
238
255
  ESCAPE_RE = Regexp.new(
239
- "[" +
240
- ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
256
+ "[" +
257
+ ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
241
258
  "]")
242
259
 
243
260
  ##
@@ -270,18 +287,18 @@ class Net::LDAP::Filter
270
287
  when 0xa4 # context-specific constructed 4, "substring"
271
288
  str = ""
272
289
  final = false
273
- ber.last.each { |b|
290
+ ber.last.each do |b|
274
291
  case b.ber_identifier
275
292
  when 0x80 # context-specific primitive 0, SubstringFilter "initial"
276
- raise Net::LDAP::LdapError, "Unrecognized substring filter; bad initial value." if str.length > 0
277
- str += b
293
+ raise Net::LDAP::SubstringFilterError, "Unrecognized substring filter; bad initial value." if str.length > 0
294
+ str += escape(b)
278
295
  when 0x81 # context-specific primitive 0, SubstringFilter "any"
279
- str += "*#{b}"
296
+ str += "*#{escape(b)}"
280
297
  when 0x82 # context-specific primitive 0, SubstringFilter "final"
281
- str += "*#{b}"
298
+ str += "*#{escape(b)}"
282
299
  final = true
283
300
  end
284
- }
301
+ end
285
302
  str += "*" unless final
286
303
  eq(ber.first.to_s, str)
287
304
  when 0xa5 # context-specific constructed 5, "greaterOrEqual"
@@ -292,9 +309,9 @@ class Net::LDAP::Filter
292
309
  # call to_s to get rid of the BER-identifiedness of the incoming string.
293
310
  present?(ber.to_s)
294
311
  when 0xa9 # context-specific constructed 9, "extensible comparison"
295
- raise Net::LDAP::LdapError, "Invalid extensible search filter, should be at least two elements" if ber.size<2
296
-
297
- # Reassembles the extensible filter parts
312
+ raise Net::LDAP::SearchFilterError, "Invalid extensible search filter, should be at least two elements" if ber.size < 2
313
+
314
+ # Reassembles the extensible filter parts
298
315
  # (["sn", "2.4.6.8.10", "Barbara Jones", '1'])
299
316
  type = value = dn = rule = nil
300
317
  ber.each do |element|
@@ -310,10 +327,10 @@ class Net::LDAP::Filter
310
327
  attribute << type if type
311
328
  attribute << ":#{dn}" if dn
312
329
  attribute << ":#{rule}" if rule
313
-
330
+
314
331
  ex(attribute, value)
315
332
  else
316
- raise Net::LDAP::LdapError, "Invalid BER tag-value (#{ber.ber_identifier}) in search filter."
333
+ raise Net::LDAP::BERInvalidError, "Invalid BER tag-value (#{ber.ber_identifier}) in search filter."
317
334
  end
318
335
  end
319
336
 
@@ -340,7 +357,7 @@ class Net::LDAP::Filter
340
357
  when 0xa3 # equalityMatch. context-specific constructed 3.
341
358
  eq(obj[0], obj[1])
342
359
  else
343
- raise Net::LDAP::LdapError, "Unknown LDAP search-filter type: #{obj.ber_identifier}"
360
+ raise Net::LDAP::SearchFilterTypeUnknownError, "Unknown LDAP search-filter type: #{obj.ber_identifier}"
344
361
  end
345
362
  end
346
363
  end
@@ -397,7 +414,7 @@ class Net::LDAP::Filter
397
414
  case @op
398
415
  when :ne
399
416
  "!(#{@left}=#{@right})"
400
- when :eq
417
+ when :eq, :bineq
401
418
  "#{@left}=#{@right}"
402
419
  when :ex
403
420
  "#{@left}:=#{@right}"
@@ -473,7 +490,7 @@ class Net::LDAP::Filter
473
490
  when :eq
474
491
  if @right == "*" # presence test
475
492
  @left.to_s.to_ber_contextspecific(7)
476
- elsif @right =~ /[*]/ # substring
493
+ elsif @right.to_s =~ /[*]/ # substring
477
494
  # Parsing substrings is a little tricky. We use String#split to
478
495
  # break a string into substrings delimited by the * (star)
479
496
  # character. But we also need to know whether there is a star at the
@@ -490,17 +507,17 @@ class Net::LDAP::Filter
490
507
  first = nil
491
508
  ary.shift
492
509
  else
493
- first = ary.shift.to_ber_contextspecific(0)
510
+ first = unescape(ary.shift).to_ber_contextspecific(0)
494
511
  end
495
512
 
496
513
  if ary.last.empty?
497
514
  last = nil
498
515
  ary.pop
499
516
  else
500
- last = ary.pop.to_ber_contextspecific(2)
517
+ last = unescape(ary.pop).to_ber_contextspecific(2)
501
518
  end
502
519
 
503
- seq = ary.map { |e| e.to_ber_contextspecific(1) }
520
+ seq = ary.map { |e| unescape(e).to_ber_contextspecific(1) }
504
521
  seq.unshift first if first
505
522
  seq.push last if last
506
523
 
@@ -508,11 +525,14 @@ class Net::LDAP::Filter
508
525
  else # equality
509
526
  [@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(3)
510
527
  end
528
+ when :bineq
529
+ # make sure data is not forced to UTF-8
530
+ [@left.to_s.to_ber, unescape(@right).to_ber_bin].to_ber_contextspecific(3)
511
531
  when :ex
512
532
  seq = []
513
533
 
514
534
  unless @left =~ /^([-;\w]*)(:dn)?(:(\w+|[.\w]+))?$/
515
- raise Net::LDAP::LdapError, "Bad attribute #{@left}"
535
+ raise Net::LDAP::BadAttributeError, "Bad attribute #{@left}"
516
536
  end
517
537
  type, dn, rule = $1, $2, $4
518
538
 
@@ -530,10 +550,10 @@ class Net::LDAP::Filter
530
550
  [self.class.eq(@left, @right).to_ber].to_ber_contextspecific(2)
531
551
  when :and
532
552
  ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
533
- ary.map {|a| a.to_ber}.to_ber_contextspecific(0)
553
+ ary.map(&:to_ber).to_ber_contextspecific(0)
534
554
  when :or
535
555
  ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
536
- ary.map {|a| a.to_ber}.to_ber_contextspecific(1)
556
+ ary.map(&:to_ber).to_ber_contextspecific(1)
537
557
  when :not
538
558
  [@left.to_ber].to_ber_contextspecific(2)
539
559
  end
@@ -619,15 +639,21 @@ class Net::LDAP::Filter
619
639
  l = entry[@left] and l = Array(l) and l.index(@right)
620
640
  end
621
641
  else
622
- raise Net::LDAP::LdapError, "Unknown filter type in match: #{@op}"
642
+ raise Net::LDAP::FilterTypeUnknownError, "Unknown filter type in match: #{@op}"
623
643
  end
624
644
  end
625
645
 
626
646
  ##
627
647
  # Converts escaped characters (e.g., "\\28") to unescaped characters
628
- # ("(").
648
+ # @note slawson20170317: Don't attempt to unescape 16 byte binary data which we assume are objectGUIDs
649
+ # The binary form of 5936AE79-664F-44EA-BCCB-5C39399514C6 triggers a BINARY -> UTF-8 conversion error
629
650
  def unescape(right)
630
- right.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") }
651
+ right = right.to_s
652
+ if right.length == 16 && right.encoding == Encoding::BINARY
653
+ right
654
+ else
655
+ right.to_s.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") }
656
+ end
631
657
  end
632
658
  private :unescape
633
659
 
@@ -652,7 +678,7 @@ class Net::LDAP::Filter
652
678
  def initialize(str)
653
679
  require 'strscan' # Don't load strscan until we need it.
654
680
  @filter = parse(StringScanner.new(str))
655
- raise Net::LDAP::LdapError, "Invalid filter syntax." unless @filter
681
+ raise Net::LDAP::FilterSyntaxInvalidError, "Invalid filter syntax." unless @filter
656
682
  end
657
683
 
658
684
  ##
@@ -729,11 +755,11 @@ class Net::LDAP::Filter
729
755
  # This parses a given expression inside of parentheses.
730
756
  def parse_filter_branch(scanner)
731
757
  scanner.scan(/\s*/)
732
- if token = scanner.scan(/[-\w:.]*[\w]/)
758
+ if token = scanner.scan(/[-\w:.;]*[\w]/)
733
759
  scanner.scan(/\s*/)
734
760
  if op = scanner.scan(/<=|>=|!=|:=|=/)
735
761
  scanner.scan(/\s*/)
736
- if value = scanner.scan(/(?:[-\w*.+@=,#\$%&!'\s]|\\[a-fA-F\d]{2})+/)
762
+ if value = scanner.scan(/(?:[-\[\]{}\w*.+\/:@=,#\$%&!'^~\s\xC3\x80-\xCA\xAF]|[^\x00-\x7F]|\\[a-fA-F\d]{2})+/u)
737
763
  # 20100313 AZ: Assumes that "(uid=george*)" is the same as
738
764
  # "(uid=george* )". The standard doesn't specify, but I can find
739
765
  # no examples that suggest otherwise.