diff --git a/modules/xml/tests/CMakeLists.txt b/modules/xml/tests/CMakeLists.txt index 24dbd6b7fd..d2fc633bb6 100644 --- a/modules/xml/tests/CMakeLists.txt +++ b/modules/xml/tests/CMakeLists.txt @@ -1,2 +1,3 @@ add_unit_test(CRITERION TARGET test_xml_parser DEPENDS xml syslog-ng) add_unit_test(CRITERION TARGET test_windows_eventlog_xml_parser DEPENDS xml syslog-ng) +add_unit_test(LIBTEST CRITERION TARGET test_filterx_parse_xml DEPENDS xml syslog-ng) diff --git a/modules/xml/tests/Makefile.am b/modules/xml/tests/Makefile.am index 8efd62f2ab..5e48270fa8 100644 --- a/modules/xml/tests/Makefile.am +++ b/modules/xml/tests/Makefile.am @@ -1,6 +1,7 @@ modules_xml_tests_TESTS = \ modules/xml/tests/test_xml_parser \ - modules/xml/tests/test_windows_eventlog_xml_parser + modules/xml/tests/test_windows_eventlog_xml_parser \ + modules/xml/tests/test_filterx_parse_xml check_PROGRAMS += ${modules_xml_tests_TESTS} @@ -18,4 +19,11 @@ modules_xml_tests_test_windows_eventlog_xml_parser_LDFLAGS = \ -dlpreopen $(top_builddir)/modules/xml/libxml.la EXTRA_modules_xml_tests_test_windows_eventlog_xml_parser_DEPENDENCIES = $(top_builddir)/modules/xml/libxml.la +modules_xml_tests_test_filterx_parse_xml_CFLAGS = $(TEST_CFLAGS) -I$(top_srcdir)/modules/xml +modules_xml_tests_test_filterx_parse_xml_LDADD = $(TEST_LDADD) +modules_xml_tests_test_filterx_parse_xml_LDFLAGS = \ + $(PREOPEN_SYSLOGFORMAT) \ + -dlpreopen $(top_builddir)/modules/xml/libxml.la +EXTRA_modules_xml_tests_test_filterx_parse_xml_DEPENDENCIES = $(top_builddir)/modules/xml/libxml.la + EXTRA_DIST += modules/xml/tests/CMakeLists.txt diff --git a/modules/xml/tests/test_filterx_parse_xml.c b/modules/xml/tests/test_filterx_parse_xml.c new file mode 100644 index 0000000000..789006ff29 --- /dev/null +++ b/modules/xml/tests/test_filterx_parse_xml.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 Attila Szakacs + * Copyright (c) 2017 Balabit + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + */ + + +#include + +#include "filterx-parse-xml.h" +#include "filterx/object-string.h" +#include "filterx/object-json.h" +#include "filterx/filterx-eval.h" +#include "scratch-buffers.h" +#include "apphook.h" +#include "cfg.h" + +#include "libtest/filterx-lib.h" + +static FilterXExpr * +_create_parse_xml_expr(const gchar *raw_xml, FilterXObject *fillable) +{ + FilterXFunctionArg *input = filterx_function_arg_new(NULL, filterx_non_literal_new(filterx_string_new(raw_xml, -1))); + GList *args_list = g_list_append(NULL, input); + GError *error = NULL; + FilterXFunctionArgs *args = filterx_function_args_new(args_list, &error); + g_assert(!error); + + FilterXExpr *func = filterx_generator_function_parse_xml_new("test", args, &error); + g_assert(!error); + + FilterXExpr *fillable_expr = filterx_non_literal_new(fillable); + filterx_generator_set_fillable(func, fillable_expr); + + g_error_free(error); + return func; +} + +static void +_assert_parse_xml_fail(const gchar *raw_xml) +{ + FilterXExpr *func = _create_parse_xml_expr(raw_xml, filterx_json_object_new_empty()); + + FilterXObject *result = filterx_expr_eval(func); + cr_assert(!result); + cr_assert(filterx_eval_get_last_error()); + + filterx_eval_clear_errors(); + filterx_expr_unref(func); +} + +static void +_assert_parse_xml_with_fillable(const gchar *raw_xml, const gchar *expected_json, FilterXObject *fillable) +{ + FilterXExpr *func = _create_parse_xml_expr(raw_xml, fillable); + + FilterXObject *result = filterx_expr_eval(func); + cr_assert(result); + cr_assert(!filterx_eval_get_last_error()); + + cr_assert(filterx_object_is_type(result, &FILTERX_TYPE_NAME(json_object))); + + GString *formatted_result = g_string_new(NULL); + filterx_object_repr(result, formatted_result); + cr_assert_str_eq(formatted_result->str, expected_json); + + g_string_free(formatted_result, TRUE); + filterx_object_unref(result); + filterx_expr_unref(func); +} + +static void +_assert_parse_xml(const gchar *raw_xml, const gchar *expected_json) +{ + _assert_parse_xml_with_fillable(raw_xml, expected_json, filterx_json_object_new_empty()); +} + +Test(filterx_parse_xml, invalid_inputs) +{ + _assert_parse_xml_fail(""); + _assert_parse_xml_fail("simple string"); + _assert_parse_xml_fail(""); + _assert_parse_xml_fail(""); + _assert_parse_xml_fail(""); + _assert_parse_xml_fail("closewrongorder"); + _assert_parse_xml_fail(""); + _assert_parse_xml_fail(""); + _assert_parse_xml_fail(""); + _assert_parse_xml_fail(""); + _assert_parse_xml_fail(">"); +} + +Test(filterx_parse_xml, valid_inputs) +{ + _assert_parse_xml("", + "{\"a\":\"\"}"); + _assert_parse_xml("", + "{\"a\":{\"b\":\"\"}}"); + _assert_parse_xml("foo", + "{\"a\":{\"b\":\"foo\"}}"); + _assert_parse_xml("foobar", + "{\"a\":{\"b\":\"foo\",\"c\":\"bar\"}}"); + _assert_parse_xml("foo", + "{\"a\":{\"@attr\":\"attr_val\",\"#text\":\"foo\"}}"); + _assert_parse_xml("", + "{\"a\":{\"@attr\":\"attr_val\"}}"); + _assert_parse_xml("cd", + "{\"a\":{\"b\":[\"c\",\"d\"]}}"); + _assert_parse_xml("cde", + "{\"a\":{\"b\":[\"c\",\"d\",\"e\"]}}"); + _assert_parse_xml("ce", + "{\"a\":{\"b\":[{\"@attr\":\"attr_val\",\"#text\":\"c\"},\"e\"]}}"); + _assert_parse_xml("ce", + "{\"a\":{\"b\":[\"c\",{\"@attr\":\"attr_val\",\"#text\":\"e\"}]}}"); + _assert_parse_xml("cdf", + "{\"a\":{\"b\":[\"c\",\"d\",{\"e\":\"f\"}]}}"); + _assert_parse_xml("df", + "{\"a\":{\"b\":[{\"c\":\"d\"},{\"e\":\"f\"}]}}"); + _assert_parse_xml("dfh", + "{\"a\":{\"b\":[{\"c\":\"d\"},{\"g\":\"h\"}],\"e\":\"f\"}}"); + _assert_parse_xml("bd", + "{\"a\":{\"#text\":\"b\",\"c\":\"d\"}}"); + /* + * This is not a valid XML, as a valid XML must have exactly one root element, + * but supporting this could be useful for parsing sub-XMLs, also dropping these + * XMLs would take additional processing/validating. + */ + _assert_parse_xml("bc", + "{\"a\":[\"b\",\"c\"]}"); + +} + +Test(filterx_parse_xml, overwrite_existing_invalid_value) +{ + FilterXObject *fillable = filterx_json_object_new_from_repr("{\"a\":42}", -1); + _assert_parse_xml_with_fillable("foo", "{\"a\":{\"b\":\"foo\"}}", fillable); +} + +static void +setup(void) +{ + configuration = cfg_new_snippet(); + app_startup(); + init_libtest_filterx(); +} + +static void +teardown(void) +{ + scratch_buffers_explicit_gc(); + deinit_libtest_filterx(); + app_shutdown(); + cfg_free(configuration); +} + +TestSuite(filterx_parse_xml, .init = setup, .fini = teardown); diff --git a/tests/copyright/policy b/tests/copyright/policy index 1829275a4c..7fdb7dc050 100644 --- a/tests/copyright/policy +++ b/tests/copyright/policy @@ -275,6 +275,7 @@ modules/correlation/group-lines.h modules/xml/windows-eventlog-xml-parser\.h modules/xml/filterx-parse-xml\.[ch]$ modules/xml/tests/test_windows_eventlog_xml_parser\.c +modules/xml/tests/test_filterx_parse_xml\.c modules/examples/filterx/example-filterx-func/example-filterx-func-plugin\.[ch] modules/grpc/otel/filterx modules/kvformat/filterx-func-parse-kv\.[ch] diff --git a/tests/light/functional_tests/filterx/test_filterx.py b/tests/light/functional_tests/filterx/test_filterx.py index a26c94cefa..fa5c089a98 100644 --- a/tests/light/functional_tests/filterx/test_filterx.py +++ b/tests/light/functional_tests/filterx/test_filterx.py @@ -1948,3 +1948,17 @@ def test_plus_equal_grammar_rules(config, syslog_ng): r""""attr_gen_var":["some","basic","foo","bar","and","add","to","plus"]}""" + "\n" ) assert file_true.read_log() == exp + + +def test_parse_xml(config, syslog_ng): + (file_true, file_false) = create_config( + config, r""" + custom_message = "ce"; + $MSG = json(parse_xml(custom_message)); + """, + ) + syslog_ng.start(config) + + assert file_true.get_stats()["processed"] == 1 + assert "processed" not in file_false.get_stats() + assert file_true.read_log() == "{\"a\":{\"b\":[{\"@attr\":\"attr_val\",\"#text\":\"c\"},\"e\"]}}\n"