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_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