OSDN Git Service

0a74036ac99012e51b8e1675986b07071d0d0a1d
[redminele/redminele.git] / ruby / lib / ruby / gems / 1.8 / gems / activeldap-1.2.1 / lib / active_ldap / distinguished_name.rb
1 require 'strscan'
2
3 module ActiveLdap
4   class DistinguishedName
5     include GetTextSupport
6
7     class Parser
8       include GetTextSupport
9
10       attr_reader :dn
11       def initialize(source)
12         @dn = nil
13         source = source.to_s if source.is_a?(DN)
14         unless source.is_a?(String)
15           raise DistinguishedNameInputInvalid.new(source)
16         end
17         @source = source
18       end
19
20       def parse
21         return @dn if @dn
22
23         rdns = []
24         scanner = StringScanner.new(@source)
25
26         scanner.scan(/\s*/)
27         raise rdn_is_missing if scanner.scan(/\s*\+\s*/)
28         raise name_component_is_missing if scanner.scan(/\s*,\s*/)
29
30         rdn = {}
31         until scanner.eos?
32           type = scan_attribute_type(scanner)
33           skip_attribute_type_and_value_separator(scanner)
34           value = scan_attribute_value(scanner)
35           rdn[type] = value
36           if scanner.scan(/\s*\+\s*/)
37             raise rdn_is_missing if scanner.eos?
38           elsif scanner.scan(/\s*\,\s*/)
39             rdns << rdn
40             rdn = {}
41             raise name_component_is_missing if scanner.eos?
42           else
43             scanner.scan(/\s*/)
44             rdns << rdn if scanner.eos?
45           end
46         end
47
48         @dn = DN.new(*rdns)
49         @dn
50       end
51
52       private
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)
56         scanner[1]
57       end
58
59       def skip_attribute_type_and_value_separator(scanner)
60         raise attribute_value_is_missing unless scanner.scan(/\s*=\s*/)
61       end
62
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|
70             hex_pair.hex
71           end.pack("C*")
72         elsif scanner.scan(/\"/)
73           value = scan_quoted_attribute_value(scanner)
74         else
75           value = scan_not_quoted_attribute_value(scanner)
76         end
77         raise attribute_value_is_missing if value.blank?
78
79         value
80       end
81
82       def scan_quoted_attribute_value(scanner)
83         result = ""
84         until scanner.scan(/\"/)
85           scanner.scan(/([^\\\"]*)/)
86           quoted_strings = scanner[1]
87           pairs = collect_pairs(scanner)
88
89           if scanner.eos? or (quoted_strings.empty? and pairs.empty?)
90             raise found_unmatched_quotation
91           end
92
93           result << quoted_strings
94           result << pairs
95         end
96         result
97       end
98
99       def scan_not_quoted_attribute_value(scanner)
100         result = ""
101         until scanner.eos?
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?
106           unless strings.nil?
107             if scanner.peek(1) == ","
108               result << strings.rstrip
109             else
110               result << strings
111             end
112           end
113           break if prev_size == result.size
114         end
115         result
116       end
117
118       def collect_pairs(scanner)
119         result = ""
120         while scanner.scan(PAIR_RE)
121           if scanner[2]
122             result << [scanner[2].hex].pack("C*")
123           else
124             result << scanner[1]
125           end
126         end
127         result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
128         result
129       end
130
131       def invalid_dn(reason)
132         DistinguishedNameInvalid.new(@source, reason)
133       end
134
135       def name_component_is_missing
136         invalid_dn(_("name component is missing"))
137       end
138
139       def rdn_is_missing
140         invalid_dn(_("relative distinguished name (RDN) is missing"))
141       end
142
143       def attribute_type_is_missing
144         invalid_dn(_("attribute type is missing"))
145       end
146
147       def attribute_value_is_missing
148         invalid_dn(_("attribute value is missing"))
149       end
150
151       def found_unmatched_quotation
152         invalid_dn(_("found unmatched quotation"))
153       end
154     end
155
156     class << self
157       def parse(source)
158         Parser.new(source).parse
159       end
160
161       def escape_value(value)
162         if /(\A | \z)/.match(value)
163           '"' + value.gsub(/([\\\"])/, '\\\\\1') + '"'
164         else
165           value.gsub(/([,=\+<>#;\\\"])/, '\\\\\1')
166         end
167       end
168     end
169
170     attr_reader :rdns
171     def initialize(*rdns)
172       @rdns = rdns.collect do |rdn|
173         if rdn.is_a?(Array) and rdn.size == 2
174           {rdn[0] => rdn[1]}
175         else
176           rdn
177         end
178       end
179     end
180
181     def blank?
182       @rdns.blank?
183     end
184
185     def +(other)
186       self.class.new(*(@rdns + other.rdns))
187     end
188
189     def -(other)
190       rdns = @rdns.dup
191       normalized_rdns = normalize(@rdns)
192       normalize(other.rdns).reverse_each do |rdn|
193         if rdn == normalized_rdns.pop
194           rdns.pop
195         else
196           raise ArgumentError, _("%s isn't sub DN of %s") % [other, self]
197         end
198       end
199       self.class.new(*rdns)
200     end
201
202     def <<(rdn)
203       @rdns << rdn
204       self
205     end
206
207     def unshift(rdn)
208       @rdns.unshift(rdn)
209     end
210
211     def parent
212       return nil if @rdns.size <= 1
213       self.class.new(*@rdns[1..-1])
214     end
215
216     def <=>(other)
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)
221     end
222
223     def ==(other)
224       case other
225       when self.class
226         normalize(@rdns) == normalize(other.rdns)
227       when String
228         parsed_other = nil
229         begin
230           parsed_other = self.class.parse(other)
231         rescue DistinguishedNameInvalid
232           return false
233         end
234         self == parsed_other
235       else
236         false
237       end
238     end
239
240     def eql?(other)
241       other.is_a?(self.class) and
242         normalize(@rdns).to_s.eql?(normalize(other.rdns).to_s)
243     end
244
245     def hash
246       normalize(@rdns).to_s.hash
247     end
248
249     def to_s
250       klass = self.class
251       @rdns.collect do |rdn|
252         rdn.sort_by do |type, value|
253           type.upcase
254         end.collect do |type, value|
255           "#{type}=#{klass.escape_value(value)}"
256         end.join("+")
257       end.join(",")
258     end
259
260     def to_str # for backward compatibility
261       to_s
262     end
263
264     def to_human_readable_format
265       to_s.inspect
266     end
267
268     private
269     def normalize(rdns)
270       rdns.collect do |rdn|
271         normalized_rdn = {}
272         rdn.each do |key, value|
273           normalized_rdn[key.upcase] = value.upcase
274         end
275         normalized_rdn
276       end
277     end
278
279     def normalize_for_comparing(rdns)
280       normalize(rdns).collect do |rdn|
281         rdn.sort_by do |key, value|
282           key
283         end
284       end.collect do |key, value|
285         [key, value]
286       end
287     end
288   end
289
290   DN = DistinguishedName
291 end