diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d401f9..f9bfcd38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,16 @@ ### API changes on existing features (you may need to change your code) +* RESTCONF strict namespace validation of data in POST and PUT. + * Accepted: + ``` + curl -X PUT http://localhost/restconf/data/mod:a -d {"mod:a":"x"} + ``` + * Not accepted (must prefix "a" with module): + ``` + curl -X PUT http://localhost/restconf/data/mod:a -d {"a":"x"} + ``` + * Undefine `RESTCONF_NS_DATA_CHECK` in include/clixon_custom.h to disable strict check. * Many validation functions have changed error parameter from cbuf to xml tree. * XML trees are more flexible for utility tools * If you use these(mostly internal), you need to change the error function: `generic_validate, from_validate_common, xml_yang_validate_all_top, xml_yang_validate_all, xml_yang_validate_add, xml_yang_validate_rpc, xml_yang_validate_list_key_only` diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index b09da045..79571982 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -437,13 +437,17 @@ api_data_post(clicon_handle h, { int retval = -1; enum operation_type op = OP_CREATE; + cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */ + cxobj *xdata; /* -d data (without top symbol)*/ int i; - cxobj *xdata = NULL; cbuf *cbx = NULL; - cxobj *xtop = NULL; /* xpath root */ - cxobj *xbot = NULL; - cxobj *x; - yang_stmt *y = NULL; + cxobj *xtop = NULL; /* top of api-path */ + cxobj *xbot = NULL; /* bottom of api-path */ + yang_stmt *ybot = NULL; /* yang of xbot */ +#ifdef RESTCONF_NS_DATA_CHECK + yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */ + yang_stmt *ymoddata = NULL; /* yang module of data (-d) */ +#endif yang_stmt *yspec; cxobj *xa; cxobj *xret = NULL; @@ -469,8 +473,12 @@ api_data_post(clicon_handle h, /* Translate api_path to xtop/xbot */ xbot = xtop; if (api_path){ - if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y)) < 0) + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0) goto done; +#ifdef RESTCONF_NS_DATA_CHECK + if (ybot) + ymodapi=ys_module(ybot); +#endif if (ret == 0){ /* validation failed */ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; @@ -486,7 +494,7 @@ api_data_post(clicon_handle h, } /* Parse input data as json or xml into xml */ if (parse_xml){ - if (xml_parse_string(data, NULL, &xdata) < 0){ + if (xml_parse_string(data, NULL, &xdata0) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ @@ -499,7 +507,7 @@ api_data_post(clicon_handle h, } } else { - if ((ret = json_parse_str(data, yspec, &xdata, &xerr)) < 0){ + if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ @@ -523,7 +531,7 @@ api_data_post(clicon_handle h, /* 4.4.1: The message-body MUST contain exactly one instance of the * expected data resource. */ - if (xml_child_nr(xdata) != 1){ + if (xml_child_nr(xdata0) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ @@ -534,15 +542,46 @@ api_data_post(clicon_handle h, goto done; goto ok; } - x = xml_child_i(xdata,0); + xdata = xml_child_i(xdata0,0); +#ifdef RESTCONF_NS_DATA_CHECK + /* If the api-path (above) defines a module, then xdata must have a prefix + * and it match the module defined in api-path. + * In a POST, maybe there are cornercases where xdata (which is a child) and + * xbot (which is the parent) may have non-matching namespaces? + * This does not apply if api-path is / (no module) + */ + if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0) + goto done; + if (ymoddata && ymodapi){ + if (ymoddata != ymodapi){ + if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xe, 0, 0) < 0) + goto done; + clicon_debug(1, "%s XE:%s", __FUNCTION__, cbuf_get(ccc)); + } + + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } +#endif /* RESTCONF_NS_DATA_CHECK */ + /* Add operation (create/replace) as attribute */ - if ((xa = xml_new("operation", x, NULL)) == NULL) + if ((xa = xml_new("operation", xdata, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; /* Replace xbot with x, ie bottom of api-path with data */ - if (xml_addsub(xbot, x) < 0) + if (xml_addsub(xbot, xdata) < 0) goto done; /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) @@ -626,8 +665,8 @@ api_data_post(clicon_handle h, xml_free(xretdis); if (xtop) xml_free(xtop); - if (xdata) - xml_free(xdata); + if (xdata0) + xml_free(xdata0); if (cbx) cbuf_free(cbx); return retval; @@ -745,10 +784,14 @@ api_data_put(clicon_handle h, cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */ cxobj *xdata; /* -d data (without top symbol)*/ cbuf *cbx = NULL; - cxobj *xtop = NULL; /* xpath root */ - cxobj *xbot = NULL; + cxobj *xtop = NULL; /* top of api-path */ + cxobj *xbot = NULL; /* bottom of api-path */ + yang_stmt *ybot = NULL; /* yang of xbot */ +#ifdef RESTCONF_NS_DATA_CHECK + yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */ + yang_stmt *ymoddata = NULL; /* yang module of data (-d) */ +#endif cxobj *xparent; - yang_stmt *y = NULL; yang_stmt *yp; /* yang parent */ yang_stmt *yspec; cxobj *xa; @@ -778,8 +821,12 @@ api_data_put(clicon_handle h, /* Translate api_path to xtop/xbot */ xbot = xtop; if (api_path){ - if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y)) < 0) + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0) goto done; +#ifdef RESTCONF_NS_DATA_CHECK + if (ybot) + ymodapi=ys_module(ybot); +#endif if (ret == 0){ /* validation failed */ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; @@ -793,9 +840,10 @@ api_data_put(clicon_handle h, goto ok; } } + /* Parse input data as json or xml into xml */ if (parse_xml){ - if (xml_parse_string(data, NULL, &xdata0) < 0){ + if (xml_parse_string(data, yspec, &xdata0) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ @@ -844,20 +892,35 @@ api_data_put(clicon_handle h, goto ok; } xdata = xml_child_i(xdata0,0); +#ifdef RESTCONF_NS_DATA_CHECK + /* If the api-path (above) defines a module, then xdata must have a prefix + * and it match the module defined in api-path + * This does not apply if api-path is / (no module) + */ + if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0) + goto done; + if (ymoddata && ymodapi){ + if (ymoddata != ymodapi){ + if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } +#endif /* RESTCONF_NS_DATA_CHECK */ + /* Add operation (create/replace) as attribute */ if ((xa = xml_new("operation", xdata, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; -#if 0 - if (debug){ - cbuf *ccc=cbuf_new(); - if (clicon_xml2cbuf(ccc, xdata0, 0, 0) < 0) - goto done; - clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); - } -#endif + /* Top-of tree, no api-path * Replace xparent with x, ie bottom of api-path with data */ @@ -875,6 +938,7 @@ api_data_put(clicon_handle h, * Not top-of-tree. */ clicon_debug(1, "%s x:%s xbot:%s",__FUNCTION__, dname, xml_name(xbot)); + /* Check same symbol in api-path as data */ if (strcmp(dname, xml_name(xbot))){ if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) @@ -895,13 +959,13 @@ api_data_put(clicon_handle h, * That is why the conditional is somewhat hairy */ xparent = xml_parent(xbot); - if (y){ + if (ybot){ /* Ensure list keys match between uri and data. That is: * If data is on the form: -d {"a":{"k":1}} where a is list or leaf-list * then uri-path must be ../a=1 * match_list_key() checks if this is true */ - if (match_list_keys(y, xdata, xbot) < 0){ + if (match_list_keys(ybot, xdata, xbot) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ @@ -916,7 +980,7 @@ api_data_put(clicon_handle h, * If data is on the form: -d {"k":1} and its parent is a list "a" * then the uri-path must be "../a=1 (you cannot change a's key)" */ - if ((yp = yang_parent_get(y)) != NULL && + if ((yp = yang_parent_get(ybot)) != NULL && yang_keyword_get(yp) == Y_LIST){ if ((ret = yang_key_match(yp, dname)) < 0) goto done; @@ -967,6 +1031,7 @@ api_data_put(clicon_handle h, clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; diff --git a/include/clixon_custom.h b/include/clixon_custom.h index c302d0ed..d5d46c16 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -44,3 +44,7 @@ /* Use new xml_insert code on sorted xml lists */ #define USE_XML_INSERT + +/* Make namespace check on RESTCONF PUT and POST -d data + */ +#define RESTCONF_NS_DATA_CHECK diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index cd66b01c..e2207ea8 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -183,10 +183,10 @@ new "admin edit nacm" expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" 0 "" new "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$" diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 8204b6a4..16755175 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -153,7 +153,7 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" #--------------- nacm enabled @@ -256,7 +256,7 @@ expecteof "$clixon_netconf -U guest -qf $cfg" 0 ']]>]]>" "^]]>]]>$" new "enable nacm" - expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" + expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" } #--------------- enable nacm diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 3e6089ce..c9092ac9 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -162,7 +162,7 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" #--------------- nacm enabled diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 11a4b89a..84aba720 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -95,6 +95,9 @@ expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"type":"regular"}}} new "restconf POST initial tree" expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" +new "restconf POST top without namespace" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "cont1"},"error-severity": "error","error-message": "Unassigned yang spec"}}}' + new "restconf GET datastore initial" expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' @@ -113,17 +116,19 @@ new "restconf GET if-type" expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type": "regular"}' new "restconf POST interface without mandatory type" -expectfn 'curl -s -X POST http://localhost/restconf/data/example:cont1 -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/example:cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expectfn 'curl -s -X POST http://localhost/restconf/data/example:cont1 -d {"example:interface":{"name":"TEST"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf POST interface without mandatory key" -expectfn 'curl -s -X POST http://localhost/restconf/data/example:cont1 -d {"interface":{"type":"regular"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "name"},"error-severity": "error","error-message": "Mandatory key"}}} ' +expectfn 'curl -s -X POST http://localhost/restconf/data/example:cont1 -d {"example:interface":{"type":"regular"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "name"},"error-severity": "error","error-message": "Mandatory key"}}} ' new "restconf POST interface" expectfn 'curl -s -X POST -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 "" -# XXX should it be example:interface? +new "restconf POST interface without namespace" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST2","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "Data is not prefixed with matching namespace"}}}' + new "restconf POST again" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf POST from top" expecteq "$(curl -s -X POST -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' @@ -162,16 +167,16 @@ new "restconf PUT initial datastore again" expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf PUT change interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/example:cont1/interface=local0' 0 "" +expectfn 'curl -s -X PUT -d {"example:interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/example:cont1/interface=local0' 0 "" new "restconf GET datastore atm" expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' new "restconf PUT add interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 "" +expectfn 'curl -s -X PUT -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' +expectfn 'curl -is -X PUT -d {"example:interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' new "restconf PUT change type to eth0 (non-key sub-element to list)" expectfn 'curl -s -X PUT -d {"example:type":"eth0"} http://localhost/restconf/data/example:cont1/interface=local0/type' 0 "" diff --git a/test/test_restconf_listkey.sh b/test/test_restconf_listkey.sh index 4a33b21a..2426e4b9 100755 --- a/test/test_restconf_listkey.sh +++ b/test/test_restconf_listkey.sh @@ -93,6 +93,9 @@ expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a new "restconf PUT change whole list entry (same keys)" expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"x","c":"y","nonkey":"z"}}' 0 '' +new "restconf PUT change whole list entry (no namespace)(expect fail)" +expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"a":{"b":"x","c":"y","nonkey":"z"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "Data is not prefixed with matching namespace"}}}' + new "restconf PUT change list entry (wrong keys)(expect fail)" expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"y","c":"x"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' @@ -127,10 +130,10 @@ new "restconf PUT list-list single first key" expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x/e=z/f -d {"f":"z"}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "List key a length mismatch"}}}' new "restconf PUT list-list just key ok" -expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"f":"z"}' 0 '' +expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"list:f":"z"}' 0 '' new "restconf PUT list-list just key just key wrong value (should fail)" -expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"f":"wrong"}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' +expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/e=z/f -d {"list:f":"wrong"}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' new "restconf PUT add list+leaf-list entry" expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/f=u -d {"list:f":"u"}' 0 ''