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