| /* 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_SECTION_num_items(conf->sections), model.size()); | 
 |   EXPECT_EQ(lh_CONF_VALUE_num_items(conf->values), total_values); | 
 | } | 
 |  | 
 | 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"}}}, | 
 |           }, | 
 |       }, | 
 |  | 
 |       // Only the first equals counts as a key/value separator. | 
 |       { | 
 |           "key======", | 
 |           { | 
 |               {"default", {{"key", "====="}}}, | 
 |           }, | 
 |       }, | 
 |  | 
 |       // Empty keys and empty values are allowed. | 
 |       { | 
 |           R"( | 
 | [both_empty] | 
 | = | 
 | [empty_key] | 
 | =value | 
 | [empty_value] | 
 | key= | 
 | [equals] | 
 | ====== | 
 | [] | 
 | empty=section | 
 | )", | 
 |           { | 
 |               {"default", {}}, | 
 |               {"both_empty", {{"", ""}}}, | 
 |               {"empty_key", {{"", "value"}}}, | 
 |               {"empty_value", {{"key", ""}}}, | 
 |               {"equals", {{"", "====="}}}, | 
 |               {"", {{"empty", "section"}}}, | 
 |           }, | 
 |       }, | 
 |  | 
 |       // After the first equals, the value can freely contain more equals. | 
 |       { | 
 |           "key1 = \\$value1\nkey2 = \"$value2\"", | 
 |           { | 
 |               {"default", {{"key1", "$value1"}, {"key2", "$value2"}}}, | 
 |           }, | 
 |       }, | 
 |  | 
 |       // Non-ASCII bytes are allowed in values. | 
 |       { | 
 |           "key = \xe2\x98\x83", | 
 |           { | 
 |               {"default", {{"key", "\xe2\x98\x83"}}}, | 
 |           }, | 
 |       }, | 
 |  | 
 |       // An escaped backslash is not a line continuation. | 
 |       { | 
 |           R"( | 
 | key1 = value1\\ | 
 | key2 = value2 | 
 | )", | 
 |           { | 
 |               {"default", {{"key1", "value1\\"}, {"key2", "value2"}}}, | 
 |           }, | 
 |       }, | 
 |  | 
 |       // An unterminated escape sequence at the end of a line is silently | 
 |       // ignored. Normally, this would be a line continuation, but the line | 
 |       // continuation logic does not count backslashes and only looks at the | 
 |       // last two characters. This is probably a bug. | 
 |       { | 
 |           R"( | 
 | key1 = value1\\\ | 
 | key2 = value2 | 
 | )", | 
 |           { | 
 |               {"default", {{"key1", "value1\\"}, {"key2", "value2"}}}, | 
 |           }, | 
 |       }, | 
 |  | 
 |       // The above also happens inside a quoted string, even allowing the quoted | 
 |       // string to be unterminated. This is also probably a bug. | 
 |       { | 
 |           R"( | 
 | key1 = "value1\\\ | 
 | key2 = value2 | 
 | )", | 
 |           { | 
 |               {"default", {{"key1", "value1\\"}, {"key2", "value2"}}}, | 
 |           }, | 
 |       }, | 
 |   }; | 
 |   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); | 
 |   } | 
 | } |