From 0e09996073c91a471d90277df3758518d22fff99 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 21 Jan 2019 14:28:44 +0100 Subject: [PATCH] verify empty body and/or empty yang input/output --- apps/restconf/restconf_methods.c | 108 +++++++++++-------------- example/clixon-example@2019-01-13.yang | 15 +++- example/example_backend.c | 22 +++-- test/test_restconf.sh | 27 +------ test/test_rpc.sh | 17 +++- 5 files changed, 91 insertions(+), 98 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 46708588..84289dd4 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1269,9 +1269,8 @@ api_operations_post_input(clicon_handle h, xml_name_set(xdata, "data"); /* Here xdata is: * ... - * Validate that exactly only tag */ -#if 0 +#if 1 if (debug){ cbuf *ccc=cbuf_new(); if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0) @@ -1279,6 +1278,7 @@ api_operations_post_input(clicon_handle h, clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); } #endif + /* Validate that exactly only tag */ if ((xinput = xml_child_i_type(xdata, 0, CX_ELMNT)) == NULL || strcmp(xml_name(xinput),"input") != 0 || xml_child_nr_type(xdata, CX_ELMNT) != 1){ @@ -1356,8 +1356,9 @@ api_operations_post_output(clicon_handle h, cxobj *x; cxobj *xok; cbuf *cbret = NULL; - int ret; - + int isempty; + + // clicon_debug(1, "%s", __FUNCTION__); if ((cbret = cbuf_new()) == NULL){ clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; @@ -1380,7 +1381,7 @@ api_operations_post_output(clicon_handle h, /* 9. Translate to restconf RPC data */ xml_name_set(xoutput, "output"); /* xoutput should now look: 0 */ -#if 0 +#if 1 if (debug){ cbuf *ccc=cbuf_new(); if (clicon_xml2cbuf(ccc, xoutput, 0, 0) < 0) @@ -1388,56 +1389,19 @@ api_operations_post_output(clicon_handle h, clicon_debug(1, "%s XOUTPUT:%s", __FUNCTION__, cbuf_get(ccc)); } #endif - /* Validate output (in case handlers are wrong) */ - if (youtput==NULL){ - /* Special case, no yang output - * RFC 7950 7.14.4 - * If the RPC operation invocation succeeded and no output parameters - * are returned, the contains a single element - * RFC 8040 3.6.2 - * If the "rpc" statement has no "output" section, the response message - * MUST NOT include a message-body and MUST send a "204 No Content" - * status-line instead. - */ - if ((xok = xml_child_i_type(xoutput, 0, CX_ELMNT)) == NULL || - strcmp(xml_name(xok),"ok") != 0 || - xml_child_nr_type(xoutput, CX_ELMNT) != 1){ - /* Internal error - invalid output from rpc handler */ - if (xok){ - if (netconf_operation_failed_xml(&xerr, "application", - "Internal error: Empty RPC reply is not ok") < 0) - goto done; - } - else - if (netconf_operation_failed_xml(&xerr, "application", - "Internal error: Empty RPC reply should have OK") < 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 fail; - } - FCGX_SetExitStatus(204, r->out); /* OK */ - FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - goto fail; - } - else{ + + /* Sanity check of outgoing XML + * For now, skip outgoing checks. + * (1) Does not handle properly + * (2) Uncertain how validation errors should be logged/handled + */ + if (youtput!=NULL){ xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (0){ - /* Sanity check of outgoing XML - * For now, skip outgoing checks. - * (1) Does not handle properly - * (2) Uncertain how validation errors should be logged/handled - */ +#if 0 if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) goto done; - if (ret == 1 && (ret = xml_yang_validate_add(xoutput, cbret)) < 0) goto done; @@ -1452,14 +1416,34 @@ api_operations_post_output(clicon_handle h, goto done; goto fail; } - } - /* Clear namespace of parameters */ - x = NULL; - while ((x = xml_child_each(xoutput, x, CX_ELMNT)) != NULL) { - if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL) - if (xml_purge(xa) < 0) - goto done; - } +#endif + } + /* Special case, no yang output (single - or empty body?) + * RFC 7950 7.14.4 + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element + * RFC 8040 3.6.2 + * If the "rpc" statement has no "output" section, the response message + * MUST NOT include a message-body and MUST send a "204 No Content" + * status-line instead. + */ + isempty = xml_child_nr_type(xoutput, CX_ELMNT) == 0 || + (xml_child_nr_type(xoutput, CX_ELMNT) == 1 && + (xok = xml_child_i_type(xoutput, 0, CX_ELMNT)) != NULL && + strcmp(xml_name(xok),"ok")==0); + if (isempty) { + /* Internal error - invalid output from rpc handler */ + FCGX_SetExitStatus(204, r->out); /* OK */ + FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + goto fail; + } + /* Clear namespace of parameters */ + x = NULL; + while ((x = xml_child_each(xoutput, x, CX_ELMNT)) != NULL) { + if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL) + if (xml_purge(xa) < 0) + goto done; } /* Set namespace on output */ if (xmlns_set(xoutput, NULL, namespace) < 0) @@ -1643,7 +1627,7 @@ api_operations_post(clicon_handle h, } /* Here xtop is: 42 */ -#if 0 +#if 1 if (debug){ cbuf *ccc=cbuf_new(); if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0) @@ -1652,8 +1636,8 @@ api_operations_post(clicon_handle h, __FUNCTION__, cbuf_get(ccc)); } #endif - /* 6. Validate outgoing RPC and fill in defaults */ - if (xml_spec_populate_rpc(h, xtop, yspec) < 0) + /* 6. Validate incoming RPC and fill in defaults */ + if (xml_spec_populate_rpc(h, xtop, yspec) < 0) /* */ goto done; if ((ret = xml_yang_validate_rpc(xtop, cbret)) < 0) goto done; @@ -1710,7 +1694,7 @@ api_operations_post(clicon_handle h, /* 8. Receive reply from local/backend handler as Netconf RPC * 0 */ -#if 0 +#if 1 if (debug){ cbuf *ccc=cbuf_new(); if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0) diff --git a/example/clixon-example@2019-01-13.yang b/example/clixon-example@2019-01-13.yang index 40e53cc6..c664a0ab 100644 --- a/example/clixon-example@2019-01-13.yang +++ b/example/clixon-example@2019-01-13.yang @@ -76,7 +76,20 @@ module clixon-example { } } rpc empty { - description "Smallest possible RPC with no input or output"; + description "Smallest possible RPC with no input or output sections"; + } + rpc optional { + description "Small RPC with optional input and output"; + input { + leaf x { + type string; + } + } + output { + leaf x { + type string; + } + } } rpc example { description "Some example input/output for testing RFC7950 7.14. diff --git a/example/example_backend.c b/example/example_backend.c index 615d7937..e0adc3ff 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -156,19 +156,20 @@ example_rpc(clicon_handle h, /* Clicon handle */ goto done; } cprintf(cbret, ""); - while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) { - if (xmlns_set(x, NULL, namespace) < 0) - goto done; - if (clicon_xml2cbuf(cbret, x, 0, 0) < 0) - goto done; - } + if (!xml_child_nr_type(xe, CX_ELMNT)) + cprintf(cbret, ""); + else while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) { + if (xmlns_set(x, NULL, namespace) < 0) + goto done; + if (clicon_xml2cbuf(cbret, x, 0, 0) < 0) + goto done; + } cprintf(cbret, ""); retval = 0; done: return retval; } - /*! Called to get state data from plugin * @param[in] h Clicon handle * @param[in] xpath String with XPATH syntax. or NULL for all @@ -338,6 +339,12 @@ clixon_plugin_init(clicon_handle h) "empty"/* Xml tag when callback is made */ ) < 0) goto done; + /* Same as example but with optional input/output */ + if (rpc_callback_register(h, example_rpc, + NULL, + "optional"/* Xml tag when callback is made */ + ) < 0) + goto done; if (rpc_callback_register(h, example_rpc, NULL, "example"/* Xml tag when callback is made */ @@ -349,4 +356,3 @@ clixon_plugin_init(clicon_handle h) done: return NULL; } - diff --git a/test/test_restconf.sh b/test/test_restconf.sh index d0aabca5..6f41f6f7 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -30,27 +30,6 @@ cat < $cfg EOF -cat < $fyang -module mymod{ - yang-version 1.1; - namespace "urn:example:my"; - prefix me; - import clixon-example { - prefix ex; - } - import ietf-interfaces { - prefix if; - } - import ietf-ip { - prefix ip; - } - import ietf-inet-types { - prefix "inet"; - revision-date "2013-07-15"; - } -} -EOF - # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there state='{"clixon-example:state": {"op": "42"}}' @@ -94,12 +73,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r # Should be alphabetically ordered new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"clixon-example:client-rpc": null,"clixon-example:empty": null,"clixon-example:example": null,"clixon-lib:debug": null} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"clixon-example:client-rpc": null,"clixon-example:empty": null,"clixon-example:optional": null,"clixon-example:example": null,"clixon-lib:debug": null} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -275,7 +254,7 @@ new2 "restconf rpc using wrong prefix" expecteq "$(curl -s -X POST -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "restconf local client rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"request":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc) +ret=$(curl -s -i -X POST -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"request":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc) expect='ok' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then diff --git a/test/test_rpc.sh b/test/test_rpc.sh index 464a0fc5..076ced83 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -2,6 +2,9 @@ # RPC tests # Validate parameters in restconf and netconf, check namespaces, etc # See rfc8040 3.6 +# Use the example application that has one mandatory input arg, +# At the end is an alternative Yang without mandatory arg for +# valid empty input and output. APPNAME=example # include err() and new() functions and creates $dir @@ -95,7 +98,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$'