From 15f0c3cd13a37deba56c9d2d96675f74ed2c5f03 Mon Sep 17 00:00:00 2001 From: Ahmed Elhassany Date: Fri, 12 Dec 2025 09:59:38 +0100 Subject: [PATCH 1/5] Add LYD_PARSE_ANYDATA_STRICT option Add an option to enable strict parsing for anydata subtrees in json and xml parsers. The option is exposed in yanglint command line as -A, --anydata-strict. --- src/parser_data.h | 5 +++++ src/parser_json.c | 6 +++++- src/parser_xml.c | 8 ++++++-- tools/lint/cmd_data.c | 35 +++++++++++++++++++++-------------- tools/lint/main_ni.c | 10 +++++++++- 5 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/parser_data.h b/src/parser_data.h index 2d0c1aa36..a54c87c82 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -197,6 +197,11 @@ struct ly_in; number type values enclosed in quotes. */ #define LYD_PARSE_OPTS_MASK 0xFFFF0000 /**< Mask for all the LYD_PARSE_ options. */ +#define LYD_PARSE_ANYDATA_STRICT 0x10000000 /**< Apply strict parsing (::LYD_PARSE_STRICT) also to anydata + content. By default, unknown elements in anydata are parsed + as opaque nodes. With this flag, an error is raised for any unknown + elements within anydata/anyxml subtrees. */ + /** @} dataparseroptions */ /** diff --git a/src/parser_json.c b/src/parser_json.c index 689609a37..39de2ae05 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -1310,7 +1310,11 @@ lydjson_parse_any(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, st /* parse any data tree with correct options, first backup the current options and then make the parser * process data as opaq nodes */ - lydctx->parse_opts &= ~LYD_PARSE_STRICT; + if (lydctx->parse_opts & LYD_PARSE_ANYDATA_STRICT) { + lydctx->parse_opts |= LYD_PARSE_STRICT; + } else { + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + } lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; lydctx->any_schema = snode; diff --git a/src/parser_xml.c b/src/parser_xml.c index dd704680d..f33cf631e 100644 --- a/src/parser_xml.c +++ b/src/parser_xml.c @@ -954,8 +954,12 @@ lydxml_subtree_any(struct lyd_xml_ctx *lydctx, const struct lysc_node *snode, co r = lyxml_ctx_next(xmlctx); LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - /* update options so that generic data can be parsed */ - lydctx->parse_opts &= ~LYD_PARSE_STRICT; + if (lydctx->parse_opts & LYD_PARSE_ANYDATA_STRICT) { + lydctx->parse_opts |= LYD_PARSE_STRICT; + } else { + /* update options so that generic data can be parsed */ + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + } lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c index 69b4dd3c9..ca8c3bc38 100644 --- a/tools/lint/cmd_data.c +++ b/tools/lint/cmd_data.c @@ -146,7 +146,9 @@ cmd_data_help(void) " is supposed to contain the source 'nc-rpc' operation of the reply.\n" " -k, --ext-inst \n" " Name of extension instance in format:\n" - " ::\n"); + " ::\n" + " -A, --anydata-strict\n" + " Enable strict parsing of anydata content\n"); cmd_data_help_format(); cmd_data_help_in_format(); printf(" -o OUTFILE, --output=OUTFILE\n" @@ -161,19 +163,20 @@ cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { - {"defaults", required_argument, NULL, 'd'}, - {"present", no_argument, NULL, 'e'}, - {"format", required_argument, NULL, 'f'}, - {"in-format", required_argument, NULL, 'F'}, - {"help", no_argument, NULL, 'h'}, - {"merge", no_argument, NULL, 'm'}, - {"output", required_argument, NULL, 'o'}, - {"operational", required_argument, NULL, 'O'}, - {"reply-rpc", required_argument, NULL, 'R'}, - {"not-strict", no_argument, NULL, 'n'}, - {"type", required_argument, NULL, 't'}, - {"xpath", required_argument, NULL, 'x'}, - {"ext-inst", required_argument, NULL, 'k'}, + {"defaults", required_argument, NULL, 'd'}, + {"present", no_argument, NULL, 'e'}, + {"format", required_argument, NULL, 'f'}, + {"in-format", required_argument, NULL, 'F'}, + {"help", no_argument, NULL, 'h'}, + {"merge", no_argument, NULL, 'm'}, + {"output", required_argument, NULL, 'o'}, + {"operational", required_argument, NULL, 'O'}, + {"reply-rpc", required_argument, NULL, 'R'}, + {"not-strict", no_argument, NULL, 'n'}, + {"anydata-strict", no_argument , NULL, 'A'}, + {"type", required_argument, NULL, 't'}, + {"xpath", required_argument, NULL, 'x'}, + {"ext-inst", required_argument, NULL, 'k'}, {NULL, 0, NULL, 0} }; @@ -242,6 +245,10 @@ cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) case 'n': /* --not-strict */ yo->data_parse_options &= ~LYD_PARSE_STRICT; break; + case 'A': /* --anydata-strict */ + yo->data_parse_options |= LYD_PARSE_ANYDATA_STRICT; + break; + case 't': /* --type */ if (data_type_set) { YLMSG_E("The data type (-t) cannot be set multiple times."); diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c index 0725455c4..74c6b3d1c 100644 --- a/tools/lint/main_ni.c +++ b/tools/lint/main_ni.c @@ -128,6 +128,9 @@ help(int shortout) " Do not require strict data parsing (silently skip unknown data),\n" " has no effect for schemas.\n\n"); + printf(" -A, --anydata-strict\n" + " Enable strict parsing of anydata content.\n\n"); + printf(" -e, --present Validate only with the schema modules whose data actually\n" " exist in the provided input data files. Takes effect only\n" " with the 'data' or 'config' TYPEs. Used to avoid requiring\n" @@ -486,6 +489,7 @@ process_args(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx) {"submodule", required_argument, NULL, 's'}, {"ext-data", required_argument, NULL, 'x'}, {"not-strict", no_argument, NULL, 'n'}, + {"anydata-strict", no_argument, NULL, 'A'}, {"present", no_argument, NULL, 'e'}, {"type", required_argument, NULL, 't'}, {"default", required_argument, NULL, 'd'}, @@ -511,7 +515,7 @@ process_args(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx) yo->line_length = 0; opterr = 0; - while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:XJx:G:k:", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neEA:t:d:lL:o:O:R:myY:XJx:G:k:", options, &opt_index)) != -1) { switch (opt) { case 'h': /* --help */ help(0); @@ -604,6 +608,10 @@ process_args(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx) yo->data_parse_options &= ~LYD_PARSE_STRICT; break; + case 'A': /* --anydata-strict */ + yo->data_parse_options |= LYD_PARSE_ANYDATA_STRICT; + break; + case 'e': /* --present */ yo->data_validate_options |= LYD_VALIDATE_PRESENT; break; From 89d756bdb10f02891762010a05bdc5b27a00fb1e Mon Sep 17 00:00:00 2001 From: Ahmed Elhassany Date: Tue, 16 Dec 2025 11:18:02 +0100 Subject: [PATCH 2/5] Formatting for the order of LYD_PARSE_ANYDATA_STRICT --- src/parser_data.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/parser_data.h b/src/parser_data.h index a54c87c82..797fdb389 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -195,12 +195,11 @@ struct ly_in; format according to RFC 7951 based on their type. Using this option the validation can be softened to accept boolean and number type values enclosed in quotes. */ -#define LYD_PARSE_OPTS_MASK 0xFFFF0000 /**< Mask for all the LYD_PARSE_ options. */ - #define LYD_PARSE_ANYDATA_STRICT 0x10000000 /**< Apply strict parsing (::LYD_PARSE_STRICT) also to anydata - content. By default, unknown elements in anydata are parsed - as opaque nodes. With this flag, an error is raised for any unknown - elements within anydata/anyxml subtrees. */ + content. By default, unknown elements in anydata are parsed + as opaque nodes. With this flag, an error is raised for any unknown + elements within anydata/anyxml subtrees. */ +#define LYD_PARSE_OPTS_MASK 0xFFFF0000 /**< Mask for all the LYD_PARSE_ options. */ /** @} dataparseroptions */ From e3f21853f30d879476c199de926e18ab6d0f0221 Mon Sep 17 00:00:00 2001 From: Ahmed Elhassany Date: Tue, 16 Dec 2025 13:33:55 +0100 Subject: [PATCH 3/5] Add unit tests for LYD_PARSE_ANYDATA_STRICT --- tests/utests/data/test_parser_json.c | 30 +++++++++++++++ tests/utests/data/test_parser_xml.c | 56 ++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/tests/utests/data/test_parser_json.c b/tests/utests/data/test_parser_json.c index bfec8f3ed..ea432c610 100644 --- a/tests/utests/data/test_parser_json.c +++ b/tests/utests/data/test_parser_json.c @@ -376,6 +376,35 @@ test_anydata(void **state) assert_null(tree); } +static void +test_anydata_strict_validation(void **state) +{ + const char *data_without_schema; + const char *data_invalid; + const char *data_valid; + struct lyd_node *tree; + + // no shcema defiend for "x" in the parsing context + data_without_schema = "{\"a:any\":{\"x:element1\":{\"element2\":\"/a:some/a:path\",\"list\":[{},{\"key\":\"a\"}]}}}"; + + PARSER_CHECK_ERROR(data_without_schema, LYD_PARSE_ANYDATA_STRICT, LYD_VALIDATE_PRESENT, tree, LY_EVALID, + "No module named \"x\" in the context.", "/a:any", 1); + + data_invalid = "{\"a:any\":{\"a:fooA\":{\"element2\":\"/a:some/a:path\",\"list\":[{},{\"key\":\"a\"}]}}}"; + + PARSER_CHECK_ERROR(data_invalid, LYD_PARSE_ANYDATA_STRICT, LYD_VALIDATE_PRESENT, tree, LY_EVALID, + "Node \"fooA\" not found in the \"a\" module.", "/a:any", 1); + + data_valid = "{\"a:any\":{\"foo\":\"default-val\"}}"; + CHECK_PARSE_LYD(data_valid, 0, LYD_VALIDATE_PRESENT, tree); + assert_non_null(tree); + tree = tree->next; + CHECK_LYSC_NODE(tree->schema, NULL, 0, LYS_STATUS_CURR | LYS_CONFIG_R | LYS_SET_CONFIG, 1, "any", + 1, LYS_ANYDATA, 0, 0, NULL, 0); + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_SIBLINGS, data_valid); + lyd_free_all(tree); +} + static void test_anyxml(void **state) { @@ -1031,6 +1060,7 @@ main(void) UTEST(test_leaf, setup), UTEST(test_leaflist, setup), UTEST(test_anydata, setup), + UTEST(test_anydata_strict_validation, setup), UTEST(test_anyxml, setup), UTEST(test_list, setup), UTEST(test_container, setup), diff --git a/tests/utests/data/test_parser_xml.c b/tests/utests/data/test_parser_xml.c index 3f40b6d0b..b6ce7edf8 100644 --- a/tests/utests/data/test_parser_xml.c +++ b/tests/utests/data/test_parser_xml.c @@ -159,6 +159,61 @@ test_anydata(void **state) lyd_free_all(tree); } +static void +test_anydata_strict_validation(void **state) +{ + const char *data_without_schema; + const char *data_invalid; + const char *data_valid; + char *str; + struct lyd_node *tree; + + // no shcema defiend for "urn:tests:no:schema" in the parsing context + data_without_schema = "\n" + " \n" + " default-val\n" + " \n" + "\n"; + + PARSER_CHECK_ERROR(data_without_schema, LYD_PARSE_ANYDATA_STRICT, LYD_VALIDATE_PRESENT, tree, LY_EVALID, + "No module with namespace \"urn:tests:no:schema\" in the context.", "/a:any", 3); + + // anydata value are based on "module a" defined in the setup function and loaded into the parsing context. + // However, the value passed in the anydata subtree is not defined in "module a". + data_invalid = "\n" + " default-val\n" + "\n"; + + PARSER_CHECK_ERROR(data_invalid, LYD_PARSE_ANYDATA_STRICT, LYD_VALIDATE_PRESENT, tree, LY_EVALID, + "Node \"element1\" not found in the \"a\" module.", "/a:any", 2); + + // anydata value are based on "module a" defined in the setup function and loaded into the parsing context + data_valid = "\n" + " default-val\n" + "\n"; + + CHECK_PARSE_LYD(data_valid, LYD_PARSE_ANYDATA_STRICT, LYD_VALIDATE_PRESENT, tree); + assert_non_null(tree); + tree = tree->next; + CHECK_LYSC_NODE(tree->schema, NULL, 0, LYS_CONFIG_R | LYS_STATUS_CURR | LYS_SET_CONFIG, 1, "any", + 1, LYS_ANYDATA, 0, 0, NULL, 0); + + const char *data_expected = + "\n" + " default-val\n" + "\n"; + + CHECK_LYD_STRING(tree, LYD_PRINT_SIBLINGS, data_expected); + + assert_int_equal(LY_SUCCESS, lyd_any_value_str(tree, &str)); + lyd_free_all(tree); + + assert_int_equal(LY_SUCCESS, lyd_new_path2(NULL, UTEST_LYCTX, "/a:any", str, strlen(str), LYD_ANYDATA_XML, 0, &tree, NULL)); + free(str); + CHECK_LYD_STRING(tree, LYD_PRINT_SIBLINGS, data_expected); + lyd_free_all(tree); +} + static void test_anyxml(void **state) { @@ -1042,6 +1097,7 @@ main(void) const struct CMUnitTest tests[] = { UTEST(test_leaf, setup), UTEST(test_anydata, setup), + UTEST(test_anydata_strict_validation, setup), UTEST(test_anyxml, setup), UTEST(test_list, setup), UTEST(test_container, setup), From d41431bfd2a3ad5855cc6c886fece428ebc65e3b Mon Sep 17 00:00:00 2001 From: Ahmed Elhassany Date: Tue, 16 Dec 2025 13:58:32 +0100 Subject: [PATCH 4/5] Remove extra space --- src/parser_json.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser_json.c b/src/parser_json.c index 39de2ae05..5f8a3e043 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -1314,7 +1314,7 @@ lydjson_parse_any(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, st lydctx->parse_opts |= LYD_PARSE_STRICT; } else { lydctx->parse_opts &= ~LYD_PARSE_STRICT; - } + } lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; lydctx->any_schema = snode; From 9a7bec11a0da40c146c6e6c20b177439136983b0 Mon Sep 17 00:00:00 2001 From: Ahmed Elhassany Date: Tue, 16 Dec 2025 14:13:53 +0100 Subject: [PATCH 5/5] Fix argument formatting --- tools/lint/cmd_data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c index ca8c3bc38..e3208fb0a 100644 --- a/tools/lint/cmd_data.c +++ b/tools/lint/cmd_data.c @@ -173,7 +173,7 @@ cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) {"operational", required_argument, NULL, 'O'}, {"reply-rpc", required_argument, NULL, 'R'}, {"not-strict", no_argument, NULL, 'n'}, - {"anydata-strict", no_argument , NULL, 'A'}, + {"anydata-strict", no_argument, NULL, 'A'}, {"type", required_argument, NULL, 't'}, {"xpath", required_argument, NULL, 'x'}, {"ext-inst", required_argument, NULL, 'k'},