1 /*
2 * Copyright (c) 2017 Balabit
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16 *
17 * As an additional exemption you are allowed to compile & link against the
18 * OpenSSL libraries as published by the OpenSSL project. See the file
19 * COPYING for details.
20 */
21
22
23 #include <criterion/criterion.h>
24 #include <criterion/parameterized.h>
25 #include "xml.h"
26 #include "scanner/xml-scanner/xml-scanner.h"
27 #include "apphook.h"
28 #include "scratch-buffers.h"
29
30 void
setup(void)31 setup(void)
32 {
33 configuration = cfg_new_snippet();
34 app_startup();
35 }
36
37 void
teardown(void)38 teardown(void)
39 {
40 scratch_buffers_explicit_gc();
41 app_shutdown();
42 cfg_free(configuration);
43 }
44
45 typedef struct
46 {
47 gboolean forward_invalid;
48 gboolean strip_whitespaces;
49 gboolean *create_lists;
50 GList *exclude_tags;
51 const gchar *prefix;
52 } XMLParserTestOptions;
53
54 static LogParser *
_construct_xml_parser(XMLParserTestOptions options)55 _construct_xml_parser(XMLParserTestOptions options)
56 {
57 LogParser *xml_parser = xml_parser_new(configuration);
58 xml_parser_set_forward_invalid(xml_parser, options.forward_invalid);
59 if (options.create_lists != NULL)
60 xml_parser_allow_create_lists(xml_parser, *options.create_lists);
61 if (options.strip_whitespaces)
62 xml_scanner_options_set_strip_whitespaces(xml_parser_get_scanner_options(xml_parser), options.strip_whitespaces);
63 if (options.exclude_tags)
64 xml_scanner_options_set_and_compile_exclude_tags(xml_parser_get_scanner_options(xml_parser), options.exclude_tags);
65 if (options.prefix)
66 xml_parser_set_prefix(xml_parser, options.prefix);
67
68 LogPipe *cloned = xml_parser_clone(&xml_parser->super);
69 log_pipe_init(cloned);
70 log_pipe_unref(&xml_parser->super);
71 return (LogParser *)cloned;
72 }
73
74 TestSuite(xmlparser, .init = setup, .fini = teardown);
75
76 typedef struct
77 {
78 const gchar *input;
79 } XMLFailTestCase;
80
ParameterizedTestParameters(xmlparser,invalid_inputs)81 ParameterizedTestParameters(xmlparser, invalid_inputs)
82 {
83 static XMLFailTestCase test_cases[] =
84 {
85 {"simple string"},
86 {"<tag></missingtag>"},
87 {"<tag></tag></extraclosetag>"},
88 {"<tag><tag></tag>"},
89 {"<tag1><tag2>closewrongorder</tag1></tag2>"},
90 {"<tag id=\"missingquote></tag>"},
91 {"<tag id='missingquote></tag>"},
92 {"<tag id=missingquote\"></tag>"},
93 {"<tag id=missingquote'></tag>"},
94 {"<space in tag/>"},
95 {"</>"},
96 {"<tag></tag>>"},
97 };
98
99 return cr_make_param_array(XMLFailTestCase, test_cases, sizeof(test_cases) / sizeof(test_cases[0]));
100 }
101
ParameterizedTest(XMLFailTestCase * test_case,xmlparser,invalid_inputs)102 ParameterizedTest(XMLFailTestCase *test_case, xmlparser, invalid_inputs)
103 {
104 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions) {});
105
106 LogMessage *msg = log_msg_new_empty();
107 log_msg_set_value(msg, LM_V_MESSAGE, test_case->input, -1);
108
109 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
110 cr_assert_not(log_parser_process_message(xml_parser, &msg, &path_options));
111
112 log_pipe_deinit((LogPipe *)xml_parser);
113 log_pipe_unref((LogPipe *)xml_parser);
114 log_msg_unref(msg);
115 }
116
117 typedef struct
118 {
119 const gchar *input;
120 const gchar *key;
121 const gchar *value;
122 } ValidXMLTestCase;
123
ParameterizedTestParameters(xmlparser,valid_inputs)124 ParameterizedTestParameters(xmlparser, valid_inputs)
125 {
126 static ValidXMLTestCase test_cases[] =
127 {
128 {"<tag1>value1</tag1>", ".xml.tag1", "value1"},
129 {"<tag1 attr='attr_value'>value1</tag1>", ".xml.tag1._attr", "attr_value"},
130 {"<tag1><tag2>value2</tag2></tag1>", ".xml.tag1.tag2", "value2"},
131 {"<tag1>part1<tag2>value2</tag2>part2</tag1>", ".xml.tag1", "part1part2"},
132 {"<tag1><tag11></tag11><tag12><tag121>value</tag121></tag12></tag1>", ".xml.tag1.tag12.tag121", "value"},
133 {"<tag1><tag11></tag11><tag12><tag121 attr1='1' attr2='2'>value</tag121></tag12></tag1>", ".xml.tag1.tag12.tag121._attr1", "1"},
134 {"<tag1><tag11></tag11><tag12><tag121 attr1='1' attr2='2'>value</tag121></tag12></tag1>", ".xml.tag1.tag12.tag121._attr2", "2"},
135 {"<tag1><tag1>t11.1</tag1><tag1>t11.2</tag1></tag1>", ".xml.tag1.tag1", "t11.1,t11.2"},
136 };
137
138 return cr_make_param_array(ValidXMLTestCase, test_cases, sizeof(test_cases) / sizeof(test_cases[0]));
139 }
140
ParameterizedTest(ValidXMLTestCase * test_cases,xmlparser,valid_inputs)141 ParameterizedTest(ValidXMLTestCase *test_cases, xmlparser, valid_inputs)
142 {
143 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions) {});
144
145 LogMessage *msg = log_msg_new_empty();
146 log_msg_set_value(msg, LM_V_MESSAGE, test_cases->input, -1);
147
148 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
149 log_parser_process_message(xml_parser, &msg, &path_options);
150
151 const gchar *value = log_msg_get_value_by_name(msg, test_cases->key, NULL);
152
153 cr_assert_str_eq(value, test_cases->value, "key: %s | value: %s != %s (expected)", test_cases->key, value,
154 test_cases->value);
155
156 log_pipe_deinit((LogPipe *)xml_parser);
157 log_pipe_unref((LogPipe *)xml_parser);
158 log_msg_unref(msg);
159 }
160
161 typedef struct
162 {
163 const gchar *input;
164 gboolean create_lists;
165 const gchar *key;
166 const gchar *value;
167 } ListCreateTestCase;
168
ParameterizedTestParameters(xmlparser,list_quoting_array_elements)169 ParameterizedTestParameters(xmlparser, list_quoting_array_elements)
170 {
171 static ListCreateTestCase test_cases[] =
172 {
173 {
174 "<tag1><simple_namevalue> value,2 </simple_namevalue></tag1>", .create_lists = FALSE,
175 ".xml.tag1.simple_namevalue", " value,2 "
176 },
177 {
178 "<tag1><simple_namevalue> value,2 </simple_namevalue></tag1>", .create_lists = TRUE,
179 ".xml.tag1.simple_namevalue", "\" value,2 \""
180 },
181 {
182 "<events><data>1</data><data> 2 </data></events>", .create_lists = TRUE,
183 ".xml.events.data", "1,\" 2 \""
184 },
185 {
186 "<events><data>1</data><data> 2 </data><data>3,</data><data>4</data></events>", .create_lists = TRUE,
187 ".xml.events.data", "1,\" 2 \",\"3,\",4"
188 },
189 {
190 "<noquotes><data>one</data><data>two</data><data>three</data></noquotes>", .create_lists = TRUE,
191 ".xml.noquotes.data", "one,two,three"
192 },
193 {
194 "<array><data>,first element</data><data>second element</data><data>Third element</data></array>",
195 .create_lists = TRUE,
196 ".xml.array.data", "\",first element\",\"second element\",\"Third element\""
197 },
198 {
199 "<array><data>\"Quoted elements escaped with single-quote\"</data><data>unquoted with double-quotes</data></array>",
200 .create_lists = TRUE,
201 ".xml.array.data", "'\"Quoted elements escaped with single-quote\"',\"unquoted with double-quotes\""
202 },
203 {
204 "<array><data>\'Single quoted becomes quoted\'</data><data>simple</data></array>", .create_lists = TRUE,
205 ".xml.array.data", "\"'Single quoted becomes quoted'\",simple"
206 },
207 {
208 "<events><data>first</data><data>second</data></events>", .create_lists = FALSE,
209 ".xml.events.data", "firstsecond"
210 },
211 {
212 "<events><data>first</data><data>second, long entry</data></events>", .create_lists = FALSE,
213 ".xml.events.data", "firstsecond, long entry"
214 },
215 };
216
217 return cr_make_param_array(ListCreateTestCase, test_cases, sizeof(test_cases) / sizeof(test_cases[0]));
218 }
219
ParameterizedTest(ListCreateTestCase * test_cases,xmlparser,list_quoting_array_elements)220 ParameterizedTest(ListCreateTestCase *test_cases, xmlparser, list_quoting_array_elements)
221 {
222 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions)
223 {
224 .create_lists = &test_cases->create_lists
225 });
226
227 LogMessage *msg = log_msg_new_empty();
228 log_msg_set_value(msg, LM_V_MESSAGE, test_cases->input, -1);
229
230 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
231 log_parser_process_message(xml_parser, &msg, &path_options);
232
233 const gchar *value = log_msg_get_value_by_name(msg, test_cases->key, NULL);
234
235 cr_assert_str_eq(value, test_cases->value, "key: %s | value: %s != %s (expected)", test_cases->key, value,
236 test_cases->value);
237
238 log_pipe_deinit((LogPipe *)xml_parser);
239 log_pipe_unref((LogPipe *)xml_parser);
240 log_msg_unref(msg);
241 }
242
Test(xmlparser,test_drop_invalid)243 Test(xmlparser, test_drop_invalid)
244 {
245 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions) {});
246
247 LogMessage *msg = log_msg_new_empty();
248 log_msg_set_value(msg, LM_V_MESSAGE, "<tag>", -1);
249
250 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
251 xml_parser_set_forward_invalid(xml_parser, FALSE);
252 cr_assert_not(log_parser_process_message(xml_parser, &msg, &path_options));
253
254 xml_parser_set_forward_invalid(xml_parser, TRUE);
255 cr_assert(log_parser_process_message(xml_parser, &msg, &path_options));
256
257 log_pipe_deinit((LogPipe *)xml_parser);
258 log_pipe_unref((LogPipe *)xml_parser);
259 log_msg_unref(msg);
260 }
261
262 typedef struct
263 {
264 const gchar *input;
265 gchar *pattern;
266 const gchar *key;
267 const gchar *value;
268 } SingleExcludeTagTestCase;
269
ParameterizedTestParameters(xmlparser,single_exclude_tags)270 ParameterizedTestParameters(xmlparser, single_exclude_tags)
271 {
272 static SingleExcludeTagTestCase test_cases[] =
273 {
274 /* Negative */
275 {"<longtag>Text</longtag>", "longtag", ".xml.longtag", ""},
276 {"<longtag>Text</longtag>", "longt?g", ".xml.longtag", ""},
277 {"<longtag>Text</longtag>", "?ongtag", ".xml.longtag", ""},
278 {"<longtag>Text</longtag>", "longta?", ".xml.longtag", ""},
279 {"<longtag>Text</longtag>", "lon?ta?", ".xml.longtag", ""},
280 {"<longtag>Text</longtag>", "longt*", ".xml.longtag", ""},
281 {"<longtag>Text</longtag>", "*tag", ".xml.longtag", ""},
282 {"<longtag>Text</longtag>", "lo*gtag", ".xml.longtag", ""},
283 {"<longtag>Text</longtag>", "long*ag", ".xml.longtag", ""},
284 {"<longtag>Text</longtag>", "*", ".xml.longtag", ""},
285 /* Positive */
286 {"<longtag>Text</longtag>", "longtag_break", ".xml.longtag", "Text"},
287 {"<longtag>Text</longtag>", "longt?g_break", ".xml.longtag", "Text"},
288 {"<longtag>Text</longtag>", "?ongtag_break", ".xml.longtag", "Text"},
289 {"<longtag>Text</longtag>", "longta?_break", ".xml.longtag", "Text"},
290 {"<longtag>Text</longtag>", "lon?ta?_break", ".xml.longtag", "Text"},
291 {"<longtag>Text</longtag>", "break_longt*", ".xml.longtag", "Text"},
292 {"<longtag>Text</longtag>", "lo*gtag_break", ".xml.longtag", "Text"},
293 {"<longtag>Text</longtag>", "break_long*ag", ".xml.longtag", "Text"},
294 {"<longtag>Text</longtag>", "*tag_break", ".xml.longtag", "Text"},
295 /* Complex */
296 {"<longtag>Outer<inner>Inner</inner></longtag>", "inner", ".xml.longtag", "Outer"},
297 {"<longtag>Outer<inner>Inner</inner></longtag>", "inner", ".xml.longtag.inner", ""},
298 {
299 "<exclude>excude1Text</exclude><notexclude>notexcludeText<exclude>excude2Text</exclude></notexclude>",
300 "exclude", ".xml.exclude", ""
301 },
302 {
303 "<exclude>excude1Text</exclude><notexclude>notexcludeText<exclude>excude2Text</exclude></notexclude>",
304 "exclude", ".xml.exclude", ""
305 },
306 {
307 "<exclude>excude1Text</exclude><notexclude>notexcludeText<exclude>excude2Text</exclude></notexclude>",
308 "exclude", ".xml.notexclude.exclude", ""
309 },
310 {
311 "<exclude>excude1Text</exclude><notexclude>notexcludeText<exclude>excude2Text</exclude></notexclude>",
312 "exclude", ".xml.notexclude", "notexcludeText"
313 },
314 };
315
316 return cr_make_param_array(SingleExcludeTagTestCase, test_cases, sizeof(test_cases) / sizeof(test_cases[0]));
317 }
318
ParameterizedTest(SingleExcludeTagTestCase * test_cases,xmlparser,single_exclude_tags)319 ParameterizedTest(SingleExcludeTagTestCase *test_cases, xmlparser, single_exclude_tags)
320 {
321 GList *exclude_tags = NULL;
322 exclude_tags = g_list_append(exclude_tags, test_cases->pattern);
323
324 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions)
325 {
326 .exclude_tags = exclude_tags
327 });
328
329 LogMessage *msg = log_msg_new_empty();
330 log_msg_set_value(msg, LM_V_MESSAGE, test_cases->input, -1);
331
332 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
333 log_parser_process_message(xml_parser, &msg, &path_options);
334
335 const gchar *value = log_msg_get_value_by_name(msg, test_cases->key, NULL);
336 cr_assert_str_eq(value, test_cases->value, "key: %s | value: %s, should be %s", test_cases->key, value,
337 test_cases->value);
338
339 log_pipe_deinit((LogPipe *)xml_parser);
340 log_pipe_unref((LogPipe *)xml_parser);
341 log_msg_unref(msg);
342
343 g_list_free(exclude_tags);
344 }
345
Test(xmlparser,test_multiple_exclude_tags)346 Test(xmlparser, test_multiple_exclude_tags)
347 {
348 GList *exclude_tags = NULL;
349 exclude_tags = g_list_append(exclude_tags, "tag1");
350 exclude_tags = g_list_append(exclude_tags, "tag2");
351 exclude_tags = g_list_append(exclude_tags, "inner*");
352
353 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions)
354 {
355 .exclude_tags = exclude_tags
356 });
357
358 LogMessage *msg = log_msg_new_empty();
359 log_msg_set_value(msg, LM_V_MESSAGE,
360 "<tag1>Text1</tag1><tag2>Text2</tag2><tag3>Text3<innertag>TextInner</innertag></tag3>", -1);
361
362 const gchar *value;
363 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
364 log_parser_process_message(xml_parser, &msg, &path_options);
365
366 value = log_msg_get_value_by_name(msg, ".xml.tag1", NULL);
367 cr_assert_str_eq(value, "");
368 value = log_msg_get_value_by_name(msg, ".xml.tag2", NULL);
369 cr_assert_str_eq(value, "");
370 value = log_msg_get_value_by_name(msg, ".xml.tag3", NULL);
371 cr_assert_str_eq(value, "Text3");
372 value = log_msg_get_value_by_name(msg, ".xml.tag3.innertag", NULL);
373 cr_assert_str_eq(value, "");
374
375 log_pipe_deinit((LogPipe *)xml_parser);
376 log_pipe_unref((LogPipe *)xml_parser);
377 log_msg_unref(msg);
378
379 g_list_free(exclude_tags);
380 }
381
Test(xmlparser,test_strip_whitespaces)382 Test(xmlparser, test_strip_whitespaces)
383 {
384 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions)
385 {
386 .strip_whitespaces = TRUE
387 });
388
389 LogMessage *msg = log_msg_new_empty();
390 log_msg_set_value(msg, LM_V_MESSAGE,
391 "<tag> \n\t part1 <tag2/> part2 \n\n</tag>", -1);
392
393 const gchar *value;
394 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
395 log_parser_process_message(xml_parser, &msg, &path_options);
396
397 value = log_msg_get_value_by_name(msg, ".xml.tag", NULL);
398 cr_assert_str_eq(value, "part1part2");
399
400 log_pipe_deinit((LogPipe *)xml_parser);
401 log_pipe_unref((LogPipe *)xml_parser);
402 log_msg_unref(msg);
403 }
404
405 typedef struct
406 {
407 const gchar *input;
408 const gchar *prefix;
409 const gchar *key;
410 const gchar *value;
411 } PrefixTestCase;
412
ParameterizedTestParameters(xmlparser,test_prefix)413 ParameterizedTestParameters(xmlparser, test_prefix)
414 {
415 static PrefixTestCase test_cases[] =
416 {
417 {"<tag>default_prefix</tag>", NULL, ".xml.tag", "default_prefix"},
418 {"<tag>foo</tag>", "", "tag", "foo"},
419 {"<tag>foobar</tag>", ".xmlparser", ".xmlparser.tag", "foobar"},
420 {"<tag>baz</tag>", ".meta.", ".meta.tag", "baz"},
421 {"<top><t1>asd</t1><t2>jkl</t2></top>", "", "top.t2", "jkl"},
422 {"<top><t1>1</t1><t2><t3>3</t3></t2></top>", "", "top.t2.t3", "3"},
423 {"<top><t1>1</t1><t2><t3>3</t3></t2><misc>value</misc></top>", "", "top.misc", "value"},
424 };
425 return cr_make_param_array(PrefixTestCase, test_cases, sizeof(test_cases)/sizeof(test_cases[0]));
426 }
427
ParameterizedTest(PrefixTestCase * test_cases,xmlparser,test_prefix)428 ParameterizedTest(PrefixTestCase *test_cases, xmlparser, test_prefix)
429 {
430 LogParser *xml_parser = _construct_xml_parser((XMLParserTestOptions)
431 {
432 .prefix = test_cases->prefix
433 });
434
435 LogMessage *msg = log_msg_new_empty();
436 log_msg_set_value(msg, LM_V_MESSAGE, test_cases->input, -1);
437
438 LogPathOptions path_options = LOG_PATH_OPTIONS_INIT;
439 log_parser_process_message(xml_parser, &msg, &path_options);
440
441 const gchar *value = log_msg_get_value_by_name(msg, test_cases->key, NULL);
442 cr_assert_str_eq(value, test_cases->value);
443
444 log_pipe_deinit((LogPipe *)xml_parser);
445 log_pipe_unref((LogPipe *)xml_parser);
446 log_msg_unref(msg);
447 }
448