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 §ion = 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