You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.7 KiB
182 lines
5.7 KiB
From f46bac1f3e8634e24c747d06b28e11b874f1e488 Mon Sep 17 00:00:00 2001 |
|
From: Kazuki Yamaguchi <k@rhe.jp> |
|
Date: Thu, 16 Aug 2018 19:40:48 +0900 |
|
Subject: [PATCH] config: support .include directive |
|
|
|
OpenSSL 1.1.1 introduces a new '.include' directive. Update our config |
|
parser to support that. |
|
|
|
As mentioned in the referenced GitHub issue, we should use the OpenSSL |
|
API instead of implementing the parsing logic ourselves, but it will |
|
need backwards-incompatible changes which we can't backport to stable |
|
versions. So continue to use the Ruby implementation for now. |
|
|
|
Reference: https://github.com/ruby/openssl/issues/208 |
|
--- |
|
ext/openssl/lib/openssl/config.rb | 54 ++++++++++++++++++++----------- |
|
test/openssl/test_config.rb | 54 +++++++++++++++++++++++++++++++ |
|
2 files changed, 90 insertions(+), 18 deletions(-) |
|
|
|
diff --git a/ext/openssl/lib/openssl/config.rb b/ext/openssl/lib/openssl/config.rb |
|
index 88225451..ba3a54c8 100644 |
|
--- a/ext/openssl/lib/openssl/config.rb |
|
+++ b/ext/openssl/lib/openssl/config.rb |
|
@@ -77,29 +77,44 @@ def get_key_string(data, section, key) # :nodoc: |
|
def parse_config_lines(io) |
|
section = 'default' |
|
data = {section => {}} |
|
- while definition = get_definition(io) |
|
+ io_stack = [io] |
|
+ while definition = get_definition(io_stack) |
|
definition = clear_comments(definition) |
|
next if definition.empty? |
|
- if definition[0] == ?[ |
|
+ case definition |
|
+ when /\A\[/ |
|
if /\[([^\]]*)\]/ =~ definition |
|
section = $1.strip |
|
data[section] ||= {} |
|
else |
|
raise ConfigError, "missing close square bracket" |
|
end |
|
- else |
|
- if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition |
|
- if $2 |
|
- section = $1 |
|
- key = $2 |
|
- else |
|
- key = $1 |
|
+ when /\A\.include (\s*=\s*)?(.+)\z/ |
|
+ path = $2 |
|
+ if File.directory?(path) |
|
+ files = Dir.glob(File.join(path, "*.{cnf,conf}"), File::FNM_EXTGLOB) |
|
+ else |
|
+ files = [path] |
|
+ end |
|
+ |
|
+ files.each do |filename| |
|
+ begin |
|
+ io_stack << StringIO.new(File.read(filename)) |
|
+ rescue |
|
+ raise ConfigError, "could not include file '%s'" % filename |
|
end |
|
- value = unescape_value(data, section, $3) |
|
- (data[section] ||= {})[key] = value.strip |
|
+ end |
|
+ when /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ |
|
+ if $2 |
|
+ section = $1 |
|
+ key = $2 |
|
else |
|
- raise ConfigError, "missing equal sign" |
|
+ key = $1 |
|
end |
|
+ value = unescape_value(data, section, $3) |
|
+ (data[section] ||= {})[key] = value.strip |
|
+ else |
|
+ raise ConfigError, "missing equal sign" |
|
end |
|
end |
|
data |
|
@@ -212,10 +227,10 @@ def clear_comments(line) |
|
scanned.join |
|
end |
|
|
|
- def get_definition(io) |
|
- if line = get_line(io) |
|
+ def get_definition(io_stack) |
|
+ if line = get_line(io_stack) |
|
while /[^\\]\\\z/ =~ line |
|
- if extra = get_line(io) |
|
+ if extra = get_line(io_stack) |
|
line += extra |
|
else |
|
break |
|
@@ -225,9 +240,12 @@ def get_definition(io) |
|
end |
|
end |
|
|
|
- def get_line(io) |
|
- if line = io.gets |
|
- line.gsub(/[\r\n]*/, '') |
|
+ def get_line(io_stack) |
|
+ while io = io_stack.last |
|
+ if line = io.gets |
|
+ return line.gsub(/[\r\n]*/, '') |
|
+ end |
|
+ io_stack.pop |
|
end |
|
end |
|
end |
|
diff --git a/test/openssl/test_config.rb b/test/openssl/test_config.rb |
|
index 99dcc497..5653b5d0 100644 |
|
--- a/test/openssl/test_config.rb |
|
+++ b/test/openssl/test_config.rb |
|
@@ -120,6 +120,49 @@ def test_s_parse_format |
|
assert_equal("error in line 7: missing close square bracket", excn.message) |
|
end |
|
|
|
+ def test_s_parse_include |
|
+ in_tmpdir("ossl-config-include-test") do |dir| |
|
+ Dir.mkdir("child") |
|
+ File.write("child/a.conf", <<~__EOC__) |
|
+ [default] |
|
+ file-a = a.conf |
|
+ [sec-a] |
|
+ a = 123 |
|
+ __EOC__ |
|
+ File.write("child/b.cnf", <<~__EOC__) |
|
+ [default] |
|
+ file-b = b.cnf |
|
+ [sec-b] |
|
+ b = 123 |
|
+ __EOC__ |
|
+ File.write("include-child.conf", <<~__EOC__) |
|
+ key_outside_section = value_a |
|
+ .include child |
|
+ __EOC__ |
|
+ |
|
+ include_file = <<~__EOC__ |
|
+ [default] |
|
+ file-main = unnamed |
|
+ [sec-main] |
|
+ main = 123 |
|
+ .include = include-child.conf |
|
+ __EOC__ |
|
+ |
|
+ # Include a file by relative path |
|
+ c1 = OpenSSL::Config.parse(include_file) |
|
+ assert_equal(["default", "sec-a", "sec-b", "sec-main"], c1.sections.sort) |
|
+ assert_equal(["file-main", "file-a", "file-b"], c1["default"].keys) |
|
+ assert_equal({"a" => "123"}, c1["sec-a"]) |
|
+ assert_equal({"b" => "123"}, c1["sec-b"]) |
|
+ assert_equal({"main" => "123", "key_outside_section" => "value_a"}, c1["sec-main"]) |
|
+ |
|
+ # Relative paths are from the working directory |
|
+ assert_raise(OpenSSL::ConfigError) do |
|
+ Dir.chdir("child") { OpenSSL::Config.parse(include_file) } |
|
+ end |
|
+ end |
|
+ end |
|
+ |
|
def test_s_load |
|
# alias of new |
|
c = OpenSSL::Config.load |
|
@@ -299,6 +342,17 @@ def test_clone |
|
@it['newsection'] = {'a' => 'b'} |
|
assert_not_equal(@it.sections.sort, c.sections.sort) |
|
end |
|
+ |
|
+ private |
|
+ |
|
+ def in_tmpdir(*args) |
|
+ Dir.mktmpdir(*args) do |dir| |
|
+ dir = File.realpath(dir) |
|
+ Dir.chdir(dir) do |
|
+ yield dir |
|
+ end |
|
+ end |
|
+ end |
|
end |
|
|
|
end
|
|
|