xref: /aosp_15_r20/external/boringssl/src/crypto/conf/conf_test.cc (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
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_SECTION_num_items(conf->sections), model.size());
98   EXPECT_EQ(lh_CONF_VALUE_num_items(conf->values), total_values);
99 }
100 
TEST(ConfTest,Parse)101 TEST(ConfTest, Parse) {
102   const struct {
103     std::string in;
104     ConfModel model;
105   } kTests[] = {
106       // Test basic parsing.
107       {
108           R"(# Comment
109 
110 key=value
111 
112 [section_name]
113 key=value2
114 )",
115           {
116               {"default", {{"key", "value"}}},
117               {"section_name", {{"key", "value2"}}},
118           },
119       },
120 
121       // If a section is listed multiple times, keys add to the existing one.
122       {
123           R"(key1 = value1
124 
125 [section1]
126 key2 = value2
127 
128 [section2]
129 key3 = value3
130 
131 [default]
132 key4 = value4
133 
134 [section1]
135 key5 = value5
136 )",
137           {
138               {"default", {{"key1", "value1"}, {"key4", "value4"}}},
139               {"section1", {{"key2", "value2"}, {"key5", "value5"}}},
140               {"section2", {{"key3", "value3"}}},
141           },
142       },
143 
144       // Although the CONF parser internally uses a buffer size of 512 bytes to
145       // read one line, it detects truncation and is able to parse long lines.
146       {
147           std::string(1000, 'a') + " = " + std::string(1000, 'b') + "\n",
148           {
149               {"default", {{std::string(1000, 'a'), std::string(1000, 'b')}}},
150           },
151       },
152 
153       // Trailing backslashes are line continations.
154       {
155           "key=\\\nvalue\nkey2=foo\\\nbar=baz",
156           {
157               {"default", {{"key", "value"}, {"key2", "foobar=baz"}}},
158           },
159       },
160 
161       // To be a line continuation, it must be at the end of the line.
162       {
163           "key=\\\nvalue\nkey2=foo\\ \nbar=baz",
164           {
165               {"default", {{"key", "value"}, {"key2", "foo"}, {"bar", "baz"}}},
166           },
167       },
168 
169       // A line continuation without any following line is ignored.
170       {
171           "key=value\\",
172           {
173               {"default", {{"key", "value"}}},
174           },
175       },
176 
177       // Values may have embedded whitespace, but leading and trailing
178       // whitespace is dropped.
179       {
180           "key =  \t  foo   \t\t\tbar  \t  ",
181           {
182               {"default", {{"key", "foo   \t\t\tbar"}}},
183           },
184       },
185 
186       // Empty sections still end up in the file.
187       {
188           "[section1]\n[section2]\n[section3]\n",
189           {
190               {"default", {}},
191               {"section1", {}},
192               {"section2", {}},
193               {"section3", {}},
194           },
195       },
196 
197       // Section names can contain spaces and punctuation.
198       {
199           "[This! Is. A? Section;]\nkey = value",
200           {
201               {"default", {}},
202               {"This! Is. A? Section;", {{"key", "value"}}},
203           },
204       },
205 
206       // Trailing data after a section line is ignored.
207       {
208           "[section] key = value\nkey2 = value2\n",
209           {
210               {"default", {}},
211               {"section", {{"key2", "value2"}}},
212           },
213       },
214 
215       // Comments may appear within a line. Escapes and quotes, however,
216       // suppress the comment character.
217       {
218           R"(
219 key1 = # comment
220 key2 = "# not a comment"
221 key3 = '# not a comment'
222 key4 = `# not a comment`
223 key5 = \# not a comment
224 )",
225           {
226               {"default",
227                {
228                    {"key1", ""},
229                    {"key2", "# not a comment"},
230                    {"key3", "# not a comment"},
231                    {"key4", "# not a comment"},
232                    {"key5", "# not a comment"},
233                }},
234           },
235       },
236 
237       // Quotes may appear in the middle of a string. Inside quotes, escape
238       // sequences like \n are not evaluated. \X always evaluates to X.
239       {
240           R"(
241 key1 = mix "of" 'different' `quotes`
242 key2 = "`'"
243 key3 = "\r\n\b\t\""
244 key4 = '\r\n\b\t\''
245 key5 = `\r\n\b\t\``
246 )",
247           {
248               {"default",
249                {
250                    {"key1", "mix of different quotes"},
251                    {"key2", "`'"},
252                    {"key3", "rnbt\""},
253                    {"key4", "rnbt'"},
254                    {"key5", "rnbt`"},
255                }},
256           },
257       },
258 
259       // Outside quotes, escape sequences like \n are evaluated. Unknown escapes
260       // turn into the character.
261       {
262           R"(
263 key = \r\n\b\t\"\'\`\z
264 )",
265           {
266               {"default",
267                {
268                    {"key", "\r\n\b\t\"'`z"},
269                }},
270           },
271       },
272 
273       // Escapes (but not quoting) work inside section names.
274       {
275           "[section\\ name]\nkey = value\n",
276           {
277               {"default", {}},
278               {"section name", {{"key", "value"}}},
279           },
280       },
281 
282       // Escapes (but not quoting) are skipped over in key names, but they are
283       // left unevaluated. This is probably a bug.
284       {
285           "key\\ name = value\n",
286           {
287               {"default", {{"key\\ name", "value"}}},
288           },
289       },
290 
291       // Keys can specify sections explicitly with ::.
292       {
293           R"(
294 [section1]
295 default::key1 = value1
296 section1::key2 = value2
297 section2::key3 = value3
298 section1::key4 = value4
299 section2::key5 = value5
300 default::key6 = value6
301 key7 = value7  # section1
302 )",
303           {
304               {"default", {{"key1", "value1"}, {"key6", "value6"}}},
305               {"section1",
306                {{"key2", "value2"}, {"key4", "value4"}, {"key7", "value7"}}},
307               {"section2", {{"key3", "value3"}, {"key5", "value5"}}},
308           },
309       },
310 
311       // Punctuation is allowed in key names.
312       {
313           "key!%&*+,-./;?@^_|~1 = value\n",
314           {
315               {"default", {{"key!%&*+,-./;?@^_|~1", "value"}}},
316           },
317       },
318 
319       // Only the first equals counts as a key/value separator.
320       {
321           "key======",
322           {
323               {"default", {{"key", "====="}}},
324           },
325       },
326 
327       // Empty keys and empty values are allowed.
328       {
329           R"(
330 [both_empty]
331 =
332 [empty_key]
333 =value
334 [empty_value]
335 key=
336 [equals]
337 ======
338 []
339 empty=section
340 )",
341           {
342               {"default", {}},
343               {"both_empty", {{"", ""}}},
344               {"empty_key", {{"", "value"}}},
345               {"empty_value", {{"key", ""}}},
346               {"equals", {{"", "====="}}},
347               {"", {{"empty", "section"}}},
348           },
349       },
350 
351       // After the first equals, the value can freely contain more equals.
352       {
353           "key1 = \\$value1\nkey2 = \"$value2\"",
354           {
355               {"default", {{"key1", "$value1"}, {"key2", "$value2"}}},
356           },
357       },
358 
359       // Non-ASCII bytes are allowed in values.
360       {
361           "key = \xe2\x98\x83",
362           {
363               {"default", {{"key", "\xe2\x98\x83"}}},
364           },
365       },
366 
367       // An escaped backslash is not a line continuation.
368       {
369           R"(
370 key1 = value1\\
371 key2 = value2
372 )",
373           {
374               {"default", {{"key1", "value1\\"}, {"key2", "value2"}}},
375           },
376       },
377 
378       // An unterminated escape sequence at the end of a line is silently
379       // ignored. Normally, this would be a line continuation, but the line
380       // continuation logic does not count backslashes and only looks at the
381       // last two characters. This is probably a bug.
382       {
383           R"(
384 key1 = value1\\\
385 key2 = value2
386 )",
387           {
388               {"default", {{"key1", "value1\\"}, {"key2", "value2"}}},
389           },
390       },
391 
392       // The above also happens inside a quoted string, even allowing the quoted
393       // string to be unterminated. This is also probably a bug.
394       {
395           R"(
396 key1 = "value1\\\
397 key2 = value2
398 )",
399           {
400               {"default", {{"key1", "value1\\"}, {"key2", "value2"}}},
401           },
402       },
403   };
404   for (const auto &t : kTests) {
405     SCOPED_TRACE(t.in);
406     bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t.in.data(), t.in.size()));
407     ASSERT_TRUE(bio);
408     bssl::UniquePtr<CONF> conf(NCONF_new(nullptr));
409     ASSERT_TRUE(conf);
410     ASSERT_TRUE(NCONF_load_bio(conf.get(), bio.get(), nullptr));
411 
412     ExpectConfEquals(conf.get(), t.model);
413   }
414 
415   const char *kInvalidTests[] = {
416       // Missing equals sign.
417       "key",
418       // Unterminated section heading.
419       "[section",
420       // Section names can only contain alphanumeric characters, punctuation,
421       // and escapes. Quotes are not punctuation.
422       "[\"section\"]",
423       // Keys can only contain alphanumeric characters, punctuaion, and escapes.
424       "key name = value",
425       "\"key\" = value",
426       // Variable references have been removed.
427       "key1 = value1\nkey2 = $key1",
428   };
429   for (const auto &t : kInvalidTests) {
430     SCOPED_TRACE(t);
431     bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t, strlen(t)));
432     ASSERT_TRUE(bio);
433     bssl::UniquePtr<CONF> conf(NCONF_new(nullptr));
434     ASSERT_TRUE(conf);
435     EXPECT_FALSE(NCONF_load_bio(conf.get(), bio.get(), nullptr));
436   }
437 }
438 
439 TEST(ConfTest, ParseList) {
440   const struct {
441     const char *list;
442     char sep;
443     bool remove_whitespace;
444     std::vector<std::string> expected;
445   } kTests[] = {
446       {"", ',', /*remove_whitespace=*/0, {""}},
447       {"", ',', /*remove_whitespace=*/1, {""}},
448 
449       {" ", ',', /*remove_whitespace=*/0, {" "}},
450       {" ", ',', /*remove_whitespace=*/1, {""}},
451 
452       {"hello world", ',', /*remove_whitespace=*/0, {"hello world"}},
453       {"hello world", ',', /*remove_whitespace=*/1, {"hello world"}},
454 
455       {" hello world ", ',', /*remove_whitespace=*/0, {" hello world "}},
456       {" hello world ", ',', /*remove_whitespace=*/1, {"hello world"}},
457 
458       {"hello,world", ',', /*remove_whitespace=*/0, {"hello", "world"}},
459       {"hello,world", ',', /*remove_whitespace=*/1, {"hello", "world"}},
460 
461       {"hello,,world", ',', /*remove_whitespace=*/0, {"hello", "", "world"}},
462       {"hello,,world", ',', /*remove_whitespace=*/1, {"hello", "", "world"}},
463 
464       {"\tab cd , , ef gh ",
465        ',',
466        /*remove_whitespace=*/0,
467        {"\tab cd ", " ", " ef gh "}},
468       {"\tab cd , , ef gh ",
469        ',',
470        /*remove_whitespace=*/1,
471        {"ab cd", "", "ef gh"}},
472   };
473   for (const auto& t : kTests) {
474     SCOPED_TRACE(t.list);
475     SCOPED_TRACE(t.sep);
476     SCOPED_TRACE(t.remove_whitespace);
477 
478     std::vector<std::string> result;
479     auto append_to_vector = [](const char *elem, size_t len, void *arg) -> int {
480       auto *vec = static_cast<std::vector<std::string> *>(arg);
481       vec->push_back(std::string(elem, len));
482       return 1;
483     };
484     ASSERT_TRUE(CONF_parse_list(t.list, t.sep, t.remove_whitespace,
485                                 append_to_vector, &result));
486     EXPECT_EQ(result, t.expected);
487   }
488 }
489