4 class DistinguishedName
11 def initialize(source)
13 source = source.to_s if source.is_a?(DN)
14 unless source.is_a?(String)
15 raise DistinguishedNameInputInvalid.new(source)
24 scanner = StringScanner.new(@source)
27 raise rdn_is_missing if scanner.scan(/\s*\+\s*/)
28 raise name_component_is_missing if scanner.scan(/\s*,\s*/)
32 type = scan_attribute_type(scanner)
33 skip_attribute_type_and_value_separator(scanner)
34 value = scan_attribute_value(scanner)
36 if scanner.scan(/\s*\+\s*/)
37 raise rdn_is_missing if scanner.eos?
38 elsif scanner.scan(/\s*\,\s*/)
41 raise name_component_is_missing if scanner.eos?
44 rdns << rdn if scanner.eos?
53 ATTRIBUTE_TYPE_RE = /\s*([a-zA-Z][a-zA-Z\d\-]*|\d+(?:\.\d+)*)\s*/
54 def scan_attribute_type(scanner)
55 raise attribute_type_is_missing unless scanner.scan(ATTRIBUTE_TYPE_RE)
59 def skip_attribute_type_and_value_separator(scanner)
60 raise attribute_value_is_missing unless scanner.scan(/\s*=\s*/)
63 HEX_PAIR = "(?:[\\da-fA-F]{2})"
64 STRING_CHARS_RE = /[^,=\+<>\#;\\\"]*/ #
65 PAIR_RE = /\\([,=\+<>\#;]|\\|\"|(#{HEX_PAIR}))/ #
66 HEX_STRING_RE = /\#(#{HEX_PAIR}+)/ #
67 def scan_attribute_value(scanner)
68 if scanner.scan(HEX_STRING_RE)
69 value = scanner[1].scan(/../).collect do |hex_pair|
72 elsif scanner.scan(/\"/)
73 value = scan_quoted_attribute_value(scanner)
75 value = scan_not_quoted_attribute_value(scanner)
77 raise attribute_value_is_missing if value.blank?
82 def scan_quoted_attribute_value(scanner)
84 until scanner.scan(/\"/)
85 scanner.scan(/([^\\\"]*)/)
86 quoted_strings = scanner[1]
87 pairs = collect_pairs(scanner)
89 if scanner.eos? or (quoted_strings.empty? and pairs.empty?)
90 raise found_unmatched_quotation
93 result << quoted_strings
99 def scan_not_quoted_attribute_value(scanner)
102 prev_size = result.size
103 pairs = collect_pairs(scanner)
104 strings = scanner.scan(STRING_CHARS_RE)
105 result << pairs if !pairs.nil? and !pairs.empty?
107 if scanner.peek(1) == ","
108 result << strings.rstrip
113 break if prev_size == result.size
118 def collect_pairs(scanner)
120 while scanner.scan(PAIR_RE)
122 result << [scanner[2].hex].pack("C*")
127 result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
131 def invalid_dn(reason)
132 DistinguishedNameInvalid.new(@source, reason)
135 def name_component_is_missing
136 invalid_dn(_("name component is missing"))
140 invalid_dn(_("relative distinguished name (RDN) is missing"))
143 def attribute_type_is_missing
144 invalid_dn(_("attribute type is missing"))
147 def attribute_value_is_missing
148 invalid_dn(_("attribute value is missing"))
151 def found_unmatched_quotation
152 invalid_dn(_("found unmatched quotation"))
158 Parser.new(source).parse
161 def escape_value(value)
162 if /(\A | \z)/.match(value)
163 '"' + value.gsub(/([\\\"])/, '\\\\\1') + '"'
165 value.gsub(/([,=\+<>#;\\\"])/, '\\\\\1')
171 def initialize(*rdns)
172 @rdns = rdns.collect do |rdn|
173 if rdn.is_a?(Array) and rdn.size == 2
186 self.class.new(*(@rdns + other.rdns))
191 normalized_rdns = normalize(@rdns)
192 normalize(other.rdns).reverse_each do |rdn|
193 if rdn == normalized_rdns.pop
196 raise ArgumentError, _("%s isn't sub DN of %s") % [other, self]
199 self.class.new(*rdns)
212 return nil if @rdns.size <= 1
213 self.class.new(*@rdns[1..-1])
217 other = DN.parse(other) if other.is_a?(String)
218 return nil unless other.is_a?(self.class)
219 normalize_for_comparing(@rdns) <=>
220 normalize_for_comparing(other.rdns)
226 normalize(@rdns) == normalize(other.rdns)
230 parsed_other = self.class.parse(other)
231 rescue DistinguishedNameInvalid
241 other.is_a?(self.class) and
242 normalize(@rdns).to_s.eql?(normalize(other.rdns).to_s)
246 normalize(@rdns).to_s.hash
251 @rdns.collect do |rdn|
252 rdn.sort_by do |type, value|
254 end.collect do |type, value|
255 "#{type}=#{klass.escape_value(value)}"
260 def to_str # for backward compatibility
264 def to_human_readable_format
270 rdns.collect do |rdn|
272 rdn.each do |key, value|
273 normalized_rdn[key.upcase] = value.upcase
279 def normalize_for_comparing(rdns)
280 normalize(rdns).collect do |rdn|
281 rdn.sort_by do |key, value|
284 end.collect do |key, value|
290 DN = DistinguishedName