Mirror of BoringSSL (grpc依赖)
https://boringssl.googlesource.com/boringssl
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.
402 lines
12 KiB
402 lines
12 KiB
/* Copyright (c) 2021, Google Inc. |
|
* |
|
* Permission to use, copy, modify, and/or distribute this software for any |
|
* purpose with or without fee is hereby granted, provided that the above |
|
* copyright notice and this permission notice appear in all copies. |
|
* |
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
|
|
|
#include <algorithm> |
|
#include <string> |
|
#include <vector> |
|
#include <map> |
|
|
|
#include <openssl/bio.h> |
|
#include <openssl/conf.h> |
|
|
|
#include <gtest/gtest.h> |
|
|
|
#include "internal.h" |
|
|
|
|
|
// A |CONF| is an unordered list of sections, where each section contains an |
|
// ordered list of (name, value) pairs. |
|
using ConfModel = |
|
std::map<std::string, std::vector<std::pair<std::string, std::string>>>; |
|
|
|
static void ExpectConfEquals(const CONF *conf, const ConfModel &model) { |
|
// There is always a default section, even if empty. This is an easy mistake |
|
// to make in test data, so test for it. |
|
EXPECT_NE(model.find("default"), model.end()) |
|
<< "Model does not have a default section"; |
|
|
|
size_t total_values = 0; |
|
for (const auto &pair : model) { |
|
const std::string §ion = pair.first; |
|
SCOPED_TRACE(section); |
|
|
|
const STACK_OF(CONF_VALUE) *values = |
|
NCONF_get_section(conf, section.c_str()); |
|
ASSERT_TRUE(values); |
|
total_values += pair.second.size(); |
|
|
|
EXPECT_EQ(sk_CONF_VALUE_num(values), pair.second.size()); |
|
|
|
// If the lengths do not match, still compare up to the smaller of the two, |
|
// to aid debugging. |
|
size_t min_len = std::min(sk_CONF_VALUE_num(values), pair.second.size()); |
|
for (size_t i = 0; i < min_len; i++) { |
|
SCOPED_TRACE(i); |
|
const std::string &name = pair.second[i].first; |
|
const std::string &value = pair.second[i].second; |
|
|
|
const CONF_VALUE *v = sk_CONF_VALUE_value(values, i); |
|
EXPECT_EQ(v->section, section); |
|
EXPECT_EQ(v->name, name); |
|
EXPECT_EQ(v->value, value); |
|
|
|
const char *str = NCONF_get_string(conf, section.c_str(), name.c_str()); |
|
ASSERT_NE(str, nullptr); |
|
EXPECT_EQ(str, value); |
|
|
|
if (section == "default") { |
|
// nullptr is interpreted as the default section. |
|
str = NCONF_get_string(conf, nullptr, name.c_str()); |
|
ASSERT_NE(str, nullptr); |
|
EXPECT_EQ(str, value); |
|
} |
|
} |
|
} |
|
|
|
// Unrecognized sections must return nullptr. |
|
EXPECT_EQ(NCONF_get_section(conf, "must_not_appear_in_tests"), nullptr); |
|
EXPECT_EQ(NCONF_get_string(conf, "must_not_appear_in_tests", |
|
"must_not_appear_in_tests"), |
|
nullptr); |
|
if (!model.empty()) { |
|
// Valid section, invalid name. |
|
EXPECT_EQ(NCONF_get_string(conf, model.begin()->first.c_str(), |
|
"must_not_appear_in_tests"), |
|
nullptr); |
|
if (!model.begin()->second.empty()) { |
|
// Invalid section, valid name. |
|
EXPECT_EQ(NCONF_get_string(conf, "must_not_appear_in_tests", |
|
model.begin()->second.front().first.c_str()), |
|
nullptr); |
|
} |
|
} |
|
|
|
// There should not be any other values in |conf|. |conf| currently stores |
|
// both sections and values in the same map. |
|
EXPECT_EQ(lh_CONF_VALUE_num_items(conf->data), total_values + model.size()); |
|
} |
|
|
|
TEST(ConfTest, Parse) { |
|
const struct { |
|
std::string in; |
|
ConfModel model; |
|
} kTests[] = { |
|
// Test basic parsing. |
|
{ |
|
R"(# Comment |
|
|
|
key=value |
|
|
|
[section_name] |
|
key=value2 |
|
)", |
|
{ |
|
{"default", {{"key", "value"}}}, |
|
{"section_name", {{"key", "value2"}}}, |
|
}, |
|
}, |
|
|
|
// If a section is listed multiple times, keys add to the existing one. |
|
{ |
|
R"(key1 = value1 |
|
|
|
[section1] |
|
key2 = value2 |
|
|
|
[section2] |
|
key3 = value3 |
|
|
|
[default] |
|
key4 = value4 |
|
|
|
[section1] |
|
key5 = value5 |
|
)", |
|
{ |
|
{"default", {{"key1", "value1"}, {"key4", "value4"}}}, |
|
{"section1", {{"key2", "value2"}, {"key5", "value5"}}}, |
|
{"section2", {{"key3", "value3"}}}, |
|
}, |
|
}, |
|
|
|
// Although the CONF parser internally uses a buffer size of 512 bytes to |
|
// read one line, it detects truncation and is able to parse long lines. |
|
{ |
|
std::string(1000, 'a') + " = " + std::string(1000, 'b') + "\n", |
|
{ |
|
{"default", {{std::string(1000, 'a'), std::string(1000, 'b')}}}, |
|
}, |
|
}, |
|
|
|
// Trailing backslashes are line continations. |
|
{ |
|
"key=\\\nvalue\nkey2=foo\\\nbar=baz", |
|
{ |
|
{"default", {{"key", "value"}, {"key2", "foobar=baz"}}}, |
|
}, |
|
}, |
|
|
|
// To be a line continuation, it must be at the end of the line. |
|
{ |
|
"key=\\\nvalue\nkey2=foo\\ \nbar=baz", |
|
{ |
|
{"default", {{"key", "value"}, {"key2", "foo"}, {"bar", "baz"}}}, |
|
}, |
|
}, |
|
|
|
// A line continuation without any following line is ignored. |
|
{ |
|
"key=value\\", |
|
{ |
|
{"default", {{"key", "value"}}}, |
|
}, |
|
}, |
|
|
|
// Values may have embedded whitespace, but leading and trailing |
|
// whitespace is dropped. |
|
{ |
|
"key = \t foo \t\t\tbar \t ", |
|
{ |
|
{"default", {{"key", "foo \t\t\tbar"}}}, |
|
}, |
|
}, |
|
|
|
// Empty sections still end up in the file. |
|
{ |
|
"[section1]\n[section2]\n[section3]\n", |
|
{ |
|
{"default", {}}, |
|
{"section1", {}}, |
|
{"section2", {}}, |
|
{"section3", {}}, |
|
}, |
|
}, |
|
|
|
// Section names can contain spaces and punctuation. |
|
{ |
|
"[This! Is. A? Section;]\nkey = value", |
|
{ |
|
{"default", {}}, |
|
{"This! Is. A? Section;", {{"key", "value"}}}, |
|
}, |
|
}, |
|
|
|
// Trailing data after a section line is ignored. |
|
{ |
|
"[section] key = value\nkey2 = value2\n", |
|
{ |
|
{"default", {}}, |
|
{"section", {{"key2", "value2"}}}, |
|
}, |
|
}, |
|
|
|
// Comments may appear within a line. Escapes and quotes, however, |
|
// suppress the comment character. |
|
{ |
|
R"( |
|
key1 = # comment |
|
key2 = "# not a comment" |
|
key3 = '# not a comment' |
|
key4 = `# not a comment` |
|
key5 = \# not a comment |
|
)", |
|
{ |
|
{"default", |
|
{ |
|
{"key1", ""}, |
|
{"key2", "# not a comment"}, |
|
{"key3", "# not a comment"}, |
|
{"key4", "# not a comment"}, |
|
{"key5", "# not a comment"}, |
|
}}, |
|
}, |
|
}, |
|
|
|
// Quotes may appear in the middle of a string. Inside quotes, escape |
|
// sequences like \n are not evaluated. \X always evaluates to X. |
|
{ |
|
R"( |
|
key1 = mix "of" 'different' `quotes` |
|
key2 = "`'" |
|
key3 = "\r\n\b\t\"" |
|
key4 = '\r\n\b\t\'' |
|
key5 = `\r\n\b\t\`` |
|
)", |
|
{ |
|
{"default", |
|
{ |
|
{"key1", "mix of different quotes"}, |
|
{"key2", "`'"}, |
|
{"key3", "rnbt\""}, |
|
{"key4", "rnbt'"}, |
|
{"key5", "rnbt`"}, |
|
}}, |
|
}, |
|
}, |
|
|
|
// Outside quotes, escape sequences like \n are evaluated. Unknown escapes |
|
// turn into the character. |
|
{ |
|
R"( |
|
key = \r\n\b\t\"\'\`\z |
|
)", |
|
{ |
|
{"default", |
|
{ |
|
{"key", "\r\n\b\t\"'`z"}, |
|
}}, |
|
}, |
|
}, |
|
|
|
// Escapes (but not quoting) work inside section names. |
|
{ |
|
"[section\\ name]\nkey = value\n", |
|
{ |
|
{"default", {}}, |
|
{"section name", {{"key", "value"}}}, |
|
}, |
|
}, |
|
|
|
// Escapes (but not quoting) are skipped over in key names, but they are |
|
// left unevaluated. This is probably a bug. |
|
{ |
|
"key\\ name = value\n", |
|
{ |
|
{"default", {{"key\\ name", "value"}}}, |
|
}, |
|
}, |
|
|
|
// Keys can specify sections explicitly with ::. |
|
{ |
|
R"( |
|
[section1] |
|
default::key1 = value1 |
|
section1::key2 = value2 |
|
section2::key3 = value3 |
|
section1::key4 = value4 |
|
section2::key5 = value5 |
|
default::key6 = value6 |
|
key7 = value7 # section1 |
|
)", |
|
{ |
|
{"default", {{"key1", "value1"}, {"key6", "value6"}}}, |
|
{"section1", |
|
{{"key2", "value2"}, {"key4", "value4"}, {"key7", "value7"}}}, |
|
{"section2", {{"key3", "value3"}, {"key5", "value5"}}}, |
|
}, |
|
}, |
|
|
|
// Punctuation is allowed in key names. |
|
{ |
|
"key.1 = value\n", |
|
{ |
|
{"default", {{"key.1", "value"}}}, |
|
}, |
|
}, |
|
}; |
|
for (const auto &t : kTests) { |
|
SCOPED_TRACE(t.in); |
|
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t.in.data(), t.in.size())); |
|
ASSERT_TRUE(bio); |
|
bssl::UniquePtr<CONF> conf(NCONF_new(nullptr)); |
|
ASSERT_TRUE(conf); |
|
ASSERT_TRUE(NCONF_load_bio(conf.get(), bio.get(), nullptr)); |
|
|
|
ExpectConfEquals(conf.get(), t.model); |
|
} |
|
|
|
const char *kInvalidTests[] = { |
|
// Missing equals sign. |
|
"key", |
|
// Unterminated section heading. |
|
"[section", |
|
// Section names can only contain alphanumeric characters, punctuation, |
|
// and escapes. Quotes are not punctuation. |
|
"[\"section\"]", |
|
// Keys can only contain alphanumeric characters, punctuaion, and escapes. |
|
"key name = value", |
|
"\"key\" = value", |
|
// Variable references have been removed. |
|
"key1 = value1\nkey2 = $key1", |
|
}; |
|
for (const auto &t : kInvalidTests) { |
|
SCOPED_TRACE(t); |
|
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t, strlen(t))); |
|
ASSERT_TRUE(bio); |
|
bssl::UniquePtr<CONF> conf(NCONF_new(nullptr)); |
|
ASSERT_TRUE(conf); |
|
EXPECT_FALSE(NCONF_load_bio(conf.get(), bio.get(), nullptr)); |
|
} |
|
} |
|
|
|
TEST(ConfTest, ParseList) { |
|
const struct { |
|
const char *list; |
|
char sep; |
|
bool remove_whitespace; |
|
std::vector<std::string> expected; |
|
} kTests[] = { |
|
{"", ',', /*remove_whitespace=*/0, {""}}, |
|
{"", ',', /*remove_whitespace=*/1, {""}}, |
|
|
|
{" ", ',', /*remove_whitespace=*/0, {" "}}, |
|
{" ", ',', /*remove_whitespace=*/1, {""}}, |
|
|
|
{"hello world", ',', /*remove_whitespace=*/0, {"hello world"}}, |
|
{"hello world", ',', /*remove_whitespace=*/1, {"hello world"}}, |
|
|
|
{" hello world ", ',', /*remove_whitespace=*/0, {" hello world "}}, |
|
{" hello world ", ',', /*remove_whitespace=*/1, {"hello world"}}, |
|
|
|
{"hello,world", ',', /*remove_whitespace=*/0, {"hello", "world"}}, |
|
{"hello,world", ',', /*remove_whitespace=*/1, {"hello", "world"}}, |
|
|
|
{"hello,,world", ',', /*remove_whitespace=*/0, {"hello", "", "world"}}, |
|
{"hello,,world", ',', /*remove_whitespace=*/1, {"hello", "", "world"}}, |
|
|
|
{"\tab cd , , ef gh ", |
|
',', |
|
/*remove_whitespace=*/0, |
|
{"\tab cd ", " ", " ef gh "}}, |
|
{"\tab cd , , ef gh ", |
|
',', |
|
/*remove_whitespace=*/1, |
|
{"ab cd", "", "ef gh"}}, |
|
}; |
|
for (const auto& t : kTests) { |
|
SCOPED_TRACE(t.list); |
|
SCOPED_TRACE(t.sep); |
|
SCOPED_TRACE(t.remove_whitespace); |
|
|
|
std::vector<std::string> result; |
|
auto append_to_vector = [](const char *elem, size_t len, void *arg) -> int { |
|
auto *vec = static_cast<std::vector<std::string> *>(arg); |
|
vec->push_back(std::string(elem, len)); |
|
return 1; |
|
}; |
|
ASSERT_TRUE(CONF_parse_list(t.list, t.sep, t.remove_whitespace, |
|
append_to_vector, &result)); |
|
EXPECT_EQ(result, t.expected); |
|
} |
|
}
|
|
|