1=begin
2= Ruby-space definitions that completes C-space funcs for Config
3
4= Info
5  Copyright (C) 2010  Hiroshi Nakamura <nahi@ruby-lang.org>
6
7= Licence
8  This program is licenced under the same licence as Ruby.
9  (See the file 'LICENCE'.)
10
11=end
12
13require 'stringio'
14
15module OpenSSL
16  class Config
17    include Enumerable
18
19    class << self
20      def parse(str)
21        c = new()
22        parse_config(StringIO.new(str)).each do |section, hash|
23          c[section] = hash
24        end
25        c
26      end
27
28      alias load new
29
30      def parse_config(io)
31        begin
32          parse_config_lines(io)
33        rescue ConfigError => e
34          e.message.replace("error in line #{io.lineno}: " + e.message)
35          raise
36        end
37      end
38
39      def get_key_string(data, section, key) # :nodoc:
40        if v = data[section] && data[section][key]
41          return v
42        elsif section == 'ENV'
43          if v = ENV[key]
44            return v
45          end
46        end
47        if v = data['default'] && data['default'][key]
48          return v
49        end
50      end
51
52    private
53
54      def parse_config_lines(io)
55        section = 'default'
56        data = {section => {}}
57        while definition = get_definition(io)
58          definition = clear_comments(definition)
59          next if definition.empty?
60          if definition[0] == ?[
61            if /\[([^\]]*)\]/ =~ definition
62              section = $1.strip
63              data[section] ||= {}
64            else
65              raise ConfigError, "missing close square bracket"
66            end
67          else
68            if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition
69              if $2
70                section = $1
71                key = $2
72              else
73                key = $1
74              end
75              value = unescape_value(data, section, $3)
76              (data[section] ||= {})[key] = value.strip
77            else
78              raise ConfigError, "missing equal sign"
79            end
80          end
81        end
82        data
83      end
84
85      # escape with backslash
86      QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/
87      # escape with backslash and doubled dq
88      QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/
89      # escaped char map
90      ESCAPE_MAP = {
91        "r" => "\r",
92        "n" => "\n",
93        "b" => "\b",
94        "t" => "\t",
95      }
96
97      def unescape_value(data, section, value)
98        scanned = []
99        while m = value.match(/['"\\$]/)
100          scanned << m.pre_match
101          c = m[0]
102          value = m.post_match
103          case c
104          when "'"
105            if m = value.match(QUOTE_REGEXP_SQ)
106              scanned << m[1].gsub(/\\(.)/, '\\1')
107              value = m.post_match
108            else
109              break
110            end
111          when '"'
112            if m = value.match(QUOTE_REGEXP_DQ)
113              scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1')
114              value = m.post_match
115            else
116              break
117            end
118          when "\\"
119            c = value.slice!(0, 1)
120            scanned << (ESCAPE_MAP[c] || c)
121          when "$"
122            ref, value = extract_reference(value)
123            refsec = section
124            if ref.index('::')
125              refsec, ref = ref.split('::', 2)
126            end
127            if v = get_key_string(data, refsec, ref)
128              scanned << v
129            else
130              raise ConfigError, "variable has no value"
131            end
132          else
133            raise 'must not reaced'
134          end
135        end
136        scanned << value
137        scanned.join
138      end
139
140      def extract_reference(value)
141        rest = ''
142        if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/)
143          value = m[1] || m[2]
144          rest = m.post_match
145        elsif [?(, ?{].include?(value[0])
146          raise ConfigError, "no close brace"
147        end
148        if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/)
149          return m[0], m.post_match + rest
150        else
151          raise
152        end
153      end
154
155      def clear_comments(line)
156        # FCOMMENT
157        if m = line.match(/\A([\t\n\f ]*);.*\z/)
158          return m[1]
159        end
160        # COMMENT
161        scanned = []
162        while m = line.match(/[#'"\\]/)
163          scanned << m.pre_match
164          c = m[0]
165          line = m.post_match
166          case c
167          when '#'
168            line = nil
169            break
170          when "'", '"'
171            regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ
172            scanned << c
173            if m = line.match(regexp)
174              scanned << m[0]
175              line = m.post_match
176            else
177              scanned << line
178              line = nil
179              break
180            end
181          when "\\"
182            scanned << c
183            scanned << line.slice!(0, 1)
184          else
185            raise 'must not reaced'
186          end
187        end
188        scanned << line
189        scanned.join
190      end
191
192      def get_definition(io)
193        if line = get_line(io)
194          while /[^\\]\\\z/ =~ line
195            if extra = get_line(io)
196              line += extra
197            else
198              break
199            end
200          end
201          return line.strip
202        end
203      end
204
205      def get_line(io)
206        if line = io.gets
207          line.gsub(/[\r\n]*/, '')
208        end
209      end
210    end
211
212    def initialize(filename = nil)
213      @data = {}
214      if filename
215        File.open(filename.to_s) do |file|
216          Config.parse_config(file).each do |section, hash|
217            self[section] = hash
218          end
219        end
220      end
221    end
222
223    def get_value(section, key)
224      if section.nil?
225        raise TypeError.new('nil not allowed')
226      end
227      section = 'default' if section.empty?
228      get_key_string(section, key)
229    end
230
231    def value(arg1, arg2 = nil)
232      warn('Config#value is deprecated; use Config#get_value')
233      if arg2.nil?
234        section, key = 'default', arg1
235      else
236        section, key = arg1, arg2
237      end
238      section ||= 'default'
239      section = 'default' if section.empty?
240      get_key_string(section, key)
241    end
242
243    def add_value(section, key, value)
244      check_modify
245      (@data[section] ||= {})[key] = value
246    end
247
248    def [](section)
249      @data[section] || {}
250    end
251
252    def section(name)
253      warn('Config#section is deprecated; use Config#[]')
254      @data[name] || {}
255    end
256
257    def []=(section, pairs)
258      check_modify
259      @data[section] ||= {}
260      pairs.each do |key, value|
261        self.add_value(section, key, value)
262      end
263    end
264
265    def sections
266      @data.keys
267    end
268
269    def to_s
270      ary = []
271      @data.keys.sort.each do |section|
272        ary << "[ #{section} ]\n"
273        @data[section].keys.each do |key|
274          ary << "#{key}=#{@data[section][key]}\n"
275        end
276        ary << "\n"
277      end
278      ary.join
279    end
280
281    def each
282      @data.each do |section, hash|
283        hash.each do |key, value|
284          yield [section, key, value]
285        end
286      end
287    end
288
289    def inspect
290      "#<#{self.class.name} sections=#{sections.inspect}>"
291    end
292
293  protected
294
295    def data
296      @data
297    end
298
299  private
300
301    def initialize_copy(other)
302      @data = other.data.dup
303    end
304
305    def check_modify
306      raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen?
307    end
308
309    def get_key_string(section, key)
310      Config.get_key_string(@data, section, key)
311    end
312  end
313end
314