• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright (c) 2021, Google Inc.
2  *
3  * Permission to use, copy, modify, and/or distribute this software for any
4  * purpose with or without fee is hereby granted, provided that the above
5  * copyright notice and this permission notice appear in all copies.
6  *
7  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14 
15 #include <algorithm>
16 #include <string>
17 #include <vector>
18 #include <map>
19 
20 #include <openssl/bio.h>
21 #include <openssl/conf.h>
22 
23 #include <gtest/gtest.h>
24 
25 #include "internal.h"
26 
27 
28 // A |CONF| is an unordered list of sections, where each section contains an
29 // ordered list of (name, value) pairs.
30 using ConfModel =
31     std::map<std::string, std::vector<std::pair<std::string, std::string>>>;
32 
ExpectConfEquals(const CONF * conf,const ConfModel & model)33 static void ExpectConfEquals(const CONF *conf, const ConfModel &model) {
34   // There is always a default section, even if empty. This is an easy mistake
35   // to make in test data, so test for it.
36   EXPECT_NE(model.find("default"), model.end())
37       << "Model does not have a default section";
38 
39   size_t total_values = 0;
40   for (const auto &pair : model) {
41     const std::string &section = pair.first;
42     SCOPED_TRACE(section);
43 
44     const STACK_OF(CONF_VALUE) *values =
45         NCONF_get_section(conf, section.c_str());
46     ASSERT_TRUE(values);
47     total_values += pair.second.size();
48 
49     EXPECT_EQ(sk_CONF_VALUE_num(values), pair.second.size());
50 
51     // If the lengths do not match, still compare up to the smaller of the two,
52     // to aid debugging.
53     size_t min_len = std::min(sk_CONF_VALUE_num(values), pair.second.size());
54     for (size_t i = 0; i < min_len; i++) {
55       SCOPED_TRACE(i);
56       const std::string &name = pair.second[i].first;
57       const std::string &value = pair.second[i].second;
58 
59       const CONF_VALUE *v = sk_CONF_VALUE_value(values, i);
60       EXPECT_EQ(v->section, section);
61       EXPECT_EQ(v->name, name);
62       EXPECT_EQ(v->value, value);
63 
64       const char *str = NCONF_get_string(conf, section.c_str(), name.c_str());
65       ASSERT_NE(str, nullptr);
66       EXPECT_EQ(str, value);
67 
68       if (section == "default") {
69         // nullptr is interpreted as the default section.
70         str = NCONF_get_string(conf, nullptr, name.c_str());
71         ASSERT_NE(str, nullptr);
72         EXPECT_EQ(str, value);
73       }
74     }
75   }
76 
77   // Unrecognized sections must return nullptr.
78   EXPECT_EQ(NCONF_get_section(conf, "must_not_appear_in_tests"), nullptr);
79   EXPECT_EQ(NCONF_get_string(conf, "must_not_appear_in_tests",
80                              "must_not_appear_in_tests"),
81             nullptr);
82   if (!model.empty()) {
83     // Valid section, invalid name.
84     EXPECT_EQ(NCONF_get_string(conf, model.begin()->first.c_str(),
85                                "must_not_appear_in_tests"),
86               nullptr);
87     if (!model.begin()->second.empty()) {
88       // Invalid section, valid name.
89       EXPECT_EQ(NCONF_get_string(conf, "must_not_appear_in_tests",
90                                  model.begin()->second.front().first.c_str()),
91                 nullptr);
92     }
93   }
94 
95   // There should not be any other values in |conf|. |conf| currently stores
96   // both sections and values in the same map.
97   EXPECT_EQ(lh_CONF_VALUE_num_items(conf->data), total_values + model.size());
98 }
99 
TEST(ConfTest,Parse)100 TEST(ConfTest, Parse) {
101   const struct {
102     std::string in;
103     ConfModel model;
104   } kTests[] = {
105       // Test basic parsing.
106       {
107           R"(# Comment
108 
109 key=value
110 
111 [section_name]
112 key=value2
113 )",
114           {
115               {"default", {{"key", "value"}}},
116               {"section_name", {{"key", "value2"}}},
117           },
118       },
119 
120       // If a section is listed multiple times, keys add to the existing one.
121       {
122           R"(key1 = value1
123 
124 [section1]
125 key2 = value2
126 
127 [section2]
128 key3 = value3
129 
130 [default]
131 key4 = value4
132 
133 [section1]
134 key5 = value5
135 )",
136           {
137               {"default", {{"key1", "value1"}, {"key4", "value4"}}},
138               {"section1", {{"key2", "value2"}, {"key5", "value5"}}},
139               {"section2", {{"key3", "value3"}}},
140           },
141       },
142 
143       // Although the CONF parser internally uses a buffer size of 512 bytes to
144       // read one line, it detects truncation and is able to parse long lines.
145       {
146           std::string(1000, 'a') + " = " + std::string(1000, 'b') + "\n",
147           {
148               {"default", {{std::string(1000, 'a'), std::string(1000, 'b')}}},
149           },
150       },
151 
152       // Trailing backslashes are line continations.
153       {
154           "key=\\\nvalue\nkey2=foo\\\nbar=baz",
155           {
156               {"default", {{"key", "value"}, {"key2", "foobar=baz"}}},
157           },
158       },
159 
160       // To be a line continuation, it must be at the end of the line.
161       {
162           "key=\\\nvalue\nkey2=foo\\ \nbar=baz",
163           {
164               {"default", {{"key", "value"}, {"key2", "foo"}, {"bar", "baz"}}},
165           },
166       },
167 
168       // A line continuation without any following line is ignored.
169       {
170           "key=value\\",
171           {
172               {"default", {{"key", "value"}}},
173           },
174       },
175 
176       // Values may have embedded whitespace, but leading and trailing
177       // whitespace is dropped.
178       {
179           "key =  \t  foo   \t\t\tbar  \t  ",
180           {
181               {"default", {{"key", "foo   \t\t\tbar"}}},
182           },
183       },
184 
185       // Empty sections still end up in the file.
186       {
187           "[section1]\n[section2]\n[section3]\n",
188           {
189               {"default", {}},
190               {"section1", {}},
191               {"section2", {}},
192               {"section3", {}},
193           },
194       },
195 
196       // Section names can contain spaces and punctuation.
197       {
198           "[This! Is. A? Section;]\nkey = value",
199           {
200               {"default", {}},
201               {"This! Is. A? Section;", {{"key", "value"}}},
202           },
203       },
204 
205       // Trailing data after a section line is ignored.
206       {
207           "[section] key = value\nkey2 = value2\n",
208           {
209               {"default", {}},
210               {"section", {{"key2", "value2"}}},
211           },
212       },
213 
214       // Comments may appear within a line. Escapes and quotes, however,
215       // suppress the comment character.
216       {
217           R"(
218 key1 = # comment
219 key2 = "# not a comment"
220 key3 = '# not a comment'
221 key4 = `# not a comment`
222 key5 = \# not a comment
223 )",
224           {
225               {"default",
226                {
227                    {"key1", ""},
228                    {"key2", "# not a comment"},
229                    {"key3", "# not a comment"},
230                    {"key4", "# not a comment"},
231                    {"key5", "# not a comment"},
232                }},
233           },
234       },
235 
236       // Quotes may appear in the middle of a string. Inside quotes, escape
237       // sequences like \n are not evaluated. \X always evaluates to X.
238       {
239           R"(
240 key1 = mix "of" 'different' `quotes`
241 key2 = "`'"
242 key3 = "\r\n\b\t\""
243 key4 = '\r\n\b\t\''
244 key5 = `\r\n\b\t\``
245 )",
246           {
247               {"default",
248                {
249                    {"key1", "mix of different quotes"},
250                    {"key2", "`'"},
251                    {"key3", "rnbt\""},
252                    {"key4", "rnbt'"},
253                    {"key5", "rnbt`"},
254                }},
255           },
256       },
257 
258       // Outside quotes, escape sequences like \n are evaluated. Unknown escapes
259       // turn into the character.
260       {
261           R"(
262 key = \r\n\b\t\"\'\`\z
263 )",
264           {
265               {"default",
266                {
267                    {"key", "\r\n\b\t\"'`z"},
268                }},
269           },
270       },
271 
272       // Escapes (but not quoting) work inside section names.
273       {
274           "[section\\ name]\nkey = value\n",
275           {
276               {"default", {}},
277               {"section name", {{"key", "value"}}},
278           },
279       },
280 
281       // Escapes (but not quoting) are skipped over in key names, but they are
282       // left unevaluated. This is probably a bug.
283       {
284           "key\\ name = value\n",
285           {
286               {"default", {{"key\\ name", "value"}}},
287           },
288       },
289 
290       // Keys can specify sections explicitly with ::.
291       {
292           R"(
293 [section1]
294 default::key1 = value1
295 section1::key2 = value2
296 section2::key3 = value3
297 section1::key4 = value4
298 section2::key5 = value5
299 default::key6 = value6
300 key7 = value7  # section1
301 )",
302           {
303               {"default", {{"key1", "value1"}, {"key6", "value6"}}},
304               {"section1",
305                {{"key2", "value2"}, {"key4", "value4"}, {"key7", "value7"}}},
306               {"section2", {{"key3", "value3"}, {"key5", "value5"}}},
307           },
308       },
309 
310       // Punctuation is allowed in key names.
311       {
312           "key.1 = value\n",
313           {
314               {"default", {{"key.1", "value"}}},
315           },
316       },
317   };
318   for (const auto &t : kTests) {
319     SCOPED_TRACE(t.in);
320     bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t.in.data(), t.in.size()));
321     ASSERT_TRUE(bio);
322     bssl::UniquePtr<CONF> conf(NCONF_new(nullptr));
323     ASSERT_TRUE(conf);
324     ASSERT_TRUE(NCONF_load_bio(conf.get(), bio.get(), nullptr));
325 
326     ExpectConfEquals(conf.get(), t.model);
327   }
328 
329   const char *kInvalidTests[] = {
330       // Missing equals sign.
331       "key",
332       // Unterminated section heading.
333       "[section",
334       // Section names can only contain alphanumeric characters, punctuation,
335       // and escapes. Quotes are not punctuation.
336       "[\"section\"]",
337       // Keys can only contain alphanumeric characters, punctuaion, and escapes.
338       "key name = value",
339       "\"key\" = value",
340       // Variable references have been removed.
341       "key1 = value1\nkey2 = $key1",
342   };
343   for (const auto &t : kInvalidTests) {
344     SCOPED_TRACE(t);
345     bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t, strlen(t)));
346     ASSERT_TRUE(bio);
347     bssl::UniquePtr<CONF> conf(NCONF_new(nullptr));
348     ASSERT_TRUE(conf);
349     EXPECT_FALSE(NCONF_load_bio(conf.get(), bio.get(), nullptr));
350   }
351 }
352 
TEST(ConfTest,ParseList)353 TEST(ConfTest, ParseList) {
354   const struct {
355     const char *list;
356     char sep;
357     bool remove_whitespace;
358     std::vector<std::string> expected;
359   } kTests[] = {
360       {"", ',', /*remove_whitespace=*/0, {""}},
361       {"", ',', /*remove_whitespace=*/1, {""}},
362 
363       {" ", ',', /*remove_whitespace=*/0, {" "}},
364       {" ", ',', /*remove_whitespace=*/1, {""}},
365 
366       {"hello world", ',', /*remove_whitespace=*/0, {"hello world"}},
367       {"hello world", ',', /*remove_whitespace=*/1, {"hello world"}},
368 
369       {" hello world ", ',', /*remove_whitespace=*/0, {" hello world "}},
370       {" hello world ", ',', /*remove_whitespace=*/1, {"hello world"}},
371 
372       {"hello,world", ',', /*remove_whitespace=*/0, {"hello", "world"}},
373       {"hello,world", ',', /*remove_whitespace=*/1, {"hello", "world"}},
374 
375       {"hello,,world", ',', /*remove_whitespace=*/0, {"hello", "", "world"}},
376       {"hello,,world", ',', /*remove_whitespace=*/1, {"hello", "", "world"}},
377 
378       {"\tab cd , , ef gh ",
379        ',',
380        /*remove_whitespace=*/0,
381        {"\tab cd ", " ", " ef gh "}},
382       {"\tab cd , , ef gh ",
383        ',',
384        /*remove_whitespace=*/1,
385        {"ab cd", "", "ef gh"}},
386   };
387   for (const auto& t : kTests) {
388     SCOPED_TRACE(t.list);
389     SCOPED_TRACE(t.sep);
390     SCOPED_TRACE(t.remove_whitespace);
391 
392     std::vector<std::string> result;
393     auto append_to_vector = [](const char *elem, size_t len, void *arg) -> int {
394       auto *vec = static_cast<std::vector<std::string> *>(arg);
395       vec->push_back(std::string(elem, len));
396       return 1;
397     };
398     ASSERT_TRUE(CONF_parse_list(t.list, t.sep, t.remove_whitespace,
399                                 append_to_vector, &result));
400     EXPECT_EQ(result, t.expected);
401   }
402 }
403