From 6e714beea54e5e93acc663e676fddcf859b0b54b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 16 Jun 2020 21:51:00 +0200 Subject: [PATCH] * Restconf FCGI (eg via nginx) have changed reply message syntax slightly * native http: new restconf_err files, generic data input, restconf_methods generalized. * test: expecteq removed. --- CHANGELOG.md | 8 +- apps/restconf/Makefile.in | 8 +- apps/restconf/restconf_api.h | 10 +- apps/restconf/restconf_api_evhtp.c | 50 +- apps/restconf/restconf_api_fcgi.c | 58 +- apps/restconf/restconf_err.c | 495 ++++++++++++++++++ .../{restconf_fcgi_lib.h => restconf_err.h} | 40 +- apps/restconf/restconf_evhtp_main.c | 62 +-- apps/restconf/restconf_fcgi_lib.c | 480 ----------------- apps/restconf/restconf_fcgi_main.c | 192 ++++--- apps/restconf/restconf_lib.c | 13 - apps/restconf/restconf_lib.h | 1 - apps/restconf/restconf_methods.c | 133 ++--- apps/restconf/restconf_methods.h | 9 +- apps/restconf/restconf_methods_get.c | 103 ++-- apps/restconf/restconf_methods_get.h | 7 +- apps/restconf/restconf_methods_post.c | 180 ++++--- apps/restconf/restconf_methods_post.h | 10 +- apps/restconf/restconf_root.c | 472 ++++++++++++++++- apps/restconf/restconf_root.h | 1 + apps/restconf/restconf_stream.c | 6 +- test/lib.sh | 32 +- test/test_api_path.sh | 2 +- test/test_choice.sh | 11 +- test/test_instance_id.sh | 2 +- test/test_nacm.sh | 27 +- test/test_nacm_default.sh | 39 +- test/test_nacm_ext.sh | 25 +- test/test_nacm_protocol.sh | 19 +- test/test_perf_state.sh | 3 +- test/test_perf_state_only.sh | 3 +- test/test_restconf.sh | 101 ++-- test/test_restconf2.sh | 68 +-- test/test_restconf_listkey.sh | 48 +- test/test_restconf_startup.sh | 8 +- test/test_submodule.sh | 8 +- test/test_yang_namespace.sh | 13 +- 37 files changed, 1619 insertions(+), 1128 deletions(-) create mode 100644 apps/restconf/restconf_err.c rename apps/restconf/{restconf_fcgi_lib.h => restconf_err.h} (59%) delete mode 100644 apps/restconf/restconf_fcgi_lib.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 40fa5a2b..0a2a0329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,13 +32,19 @@ Expected: July 2020 * New mode `GT_HIDE` set by option `CLICON_CLI_GENMODEL_TYPE` to collapse non-presence containers that only contain a single list * Added a prefix for cli_show_config/cli_show_auto so that it can produce parseable output * Thanks dcornejo@netgate.com for trying it out and suggestions -* Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. Experimental. +* Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. (Experimental). * The existing FCGI restconf solution will continue to be supported for NGINX and other reverese proxies with an fast CGI API. * The restconf code has been refactored to support both modes. Hopefully, it should be straightforward to add another embedded server, such as GNU microhttpd. * The new restconf module is selected using a compile-time autotools configure as follows: * `--with-restconf=fcgi FCGI interface for stand-alone web rev-proxy eg nginx (default)` * `--with-restconf=evhtp Integrate restconf with libevhtp server` * `--without-restconf Disable restconf altogether` + +### API changes on existing protocol/config features (For users) + + * Restconf FCGI (eg via nginx) have changed reply message syntax as follows (due to refactoring and common code with evhtp): + * Bodies in error reyruns including html code have been removed + * Some (extra) CRLF:s have been removed ### C/CLI-API changes on existing features (For developers) diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index b0680280..d47cf6af 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -85,15 +85,15 @@ APPL = clixon_restconf APPSRC = APPSRC += restconf_api.c # maybe empty APPSRC += restconf_api_$(with_restconf).c # cant be .so since libevhtp is a. +APPSRC += restconf_err.c APPSRC += restconf_root.c APPSRC += restconf_$(with_restconf)_main.c +APPSRC += restconf_methods.c +APPSRC += restconf_methods_post.c +APPSRC += restconf_methods_get.c # Fcgi-specific source including main ifeq ($(with_restconf),fcgi) -APPSRC += restconf_fcgi_lib.c # Most of these should be made generic -APPSRC += restconf_methods.c -APPSRC += restconf_methods_post.c -APPSRC += restconf_methods_get.c APPSRC += restconf_stream.c endif diff --git a/apps/restconf/restconf_api.h b/apps/restconf/restconf_api.h index 50084654..21ab31da 100644 --- a/apps/restconf/restconf_api.h +++ b/apps/restconf/restconf_api.h @@ -41,14 +41,14 @@ /* * Prototypes */ -int restconf_reply_status_code(void *req, int code); - #if defined(__GNUC__) && __GNUC__ >= 3 -int restconf_reply_header_add(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4))); +int restconf_reply_header(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4))); #else -int restconf_reply_header_add(FCGX_Request *req, char *name, char *vfmt, ...); +int restconf_reply_header(FCGX_Request *req, char *name, char *vfmt, ...); #endif -int restconf_reply_send(void *req, cbuf *cb); +int restconf_reply_send(void *req, int code, cbuf *cb); + +cbuf *restconf_get_indata(void *req); #endif /* _RESTCONF_API_H_ */ diff --git a/apps/restconf/restconf_api_evhtp.c b/apps/restconf/restconf_api_evhtp.c index be3d4aa6..1f7b559d 100644 --- a/apps/restconf/restconf_api_evhtp.c +++ b/apps/restconf/restconf_api_evhtp.c @@ -65,22 +65,6 @@ #include "restconf_lib.h" #include "restconf_api.h" /* Virtual api */ - -/*! Add HTTP header field name and value to reply, evhtp specific - * @param[in] req Evhtp http request handle - * @param[in] code HTTP status code - * @see eg RFC 7230 - */ -int -restconf_reply_status_code(void *req0, - int code) -{ - evhtp_request_t *req = (evhtp_request_t *)req0; - - req->status = code; - return 0; -} - /*! Add HTTP header field name and value to reply, evhtp specific * @param[in] req Evhtp http request handle * @param[in] name HTTP header field name @@ -88,10 +72,10 @@ restconf_reply_status_code(void *req0, * @see eg RFC 7230 */ int -restconf_reply_header_add(void *req0, - char *name, - char *vfmt, - ...) +restconf_reply_header(void *req0, + char *name, + char *vfmt, + ...) { evhtp_request_t *req = (evhtp_request_t *)req0; @@ -142,13 +126,20 @@ restconf_reply_header_add(void *req0, */ int restconf_reply_send(void *req0, + int code, cbuf *cb) { evhtp_request_t *req = (evhtp_request_t *)req0; int retval = -1; evhtp_connection_t *conn; struct evbuffer *eb = NULL; - + const char *reason_phrase; + + req->status = code; + if ((reason_phrase = restconf_code2reason(code)) == NULL) + reason_phrase=""; + if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0) + goto done; #if 1 /* Optional? */ if ((conn = evhtp_request_get_connection(req)) == NULL){ clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); @@ -181,3 +172,20 @@ restconf_reply_send(void *req0, evhtp_safe_free(eb, evbuffer_free); return retval; } + +/*! get input data + * @param[in] req Fastcgi request handle + * @note Pulls up an event buffer and then copies it to a cbuf. This is not efficient. + */ +cbuf * +restconf_get_indata(void *req0) +{ + evhtp_request_t *req = (evhtp_request_t *)req0; + cbuf *cb = NULL; + + if ((cb = cbuf_new()) == NULL) + return NULL; + if (evbuffer_get_length(req->buffer_in)) + cprintf(cb, "%s", evbuffer_pullup(req->buffer_in, -1)); + return cb; +} diff --git a/apps/restconf/restconf_api_fcgi.c b/apps/restconf/restconf_api_fcgi.c index ef5be280..9f19be58 100644 --- a/apps/restconf/restconf_api_fcgi.c +++ b/apps/restconf/restconf_api_fcgi.c @@ -63,20 +63,6 @@ #include "restconf_lib.h" #include "restconf_api.h" /* Virtual api */ -/*! Add HTTP header field name and value to reply, fcgi specific - * @param[in] req Fastcgi request handle - * @param[in] code HTTP status code - * @see eg RFC 7230 - */ -int -restconf_reply_status_code(void *req0, - int code) -{ - FCGX_Request *req = (FCGX_Request *)req0; - - FCGX_SetExitStatus(code, req->out); - return 0; -} /*! HTTP headers done, if there is a message body coming next * @param[in] req Fastcgi request handle @@ -102,11 +88,10 @@ restconf_reply_body_start(void *req0) * @see eg RFC 7230 */ int -restconf_reply_header_add(void *req0, - char *name, - char *vfmt, - ...) - +restconf_reply_header(void *req0, + char *name, + char *vfmt, + ...) { FCGX_Request *req = (FCGX_Request *)req0; int retval = -1; @@ -195,23 +180,50 @@ restconf_reply_body_add(void *req0, } /*! Send HTTP reply with potential message body - * @param[in] req Fastcgi request handle - * @param[in] cb Body as a cbuf, send if + * @param[in] req Fastcgi request handle + * @param[in] code Status code + * @param[in] cb Body as a cbuf if non-NULL * * Prerequisites: status code set, headers given, body if wanted set */ int restconf_reply_send(void *req0, + int code, cbuf *cb) { FCGX_Request *req = (FCGX_Request *)req0; int retval = -1; + const char *reason_phrase; + FCGX_SetExitStatus(code, req->out); + if ((reason_phrase = restconf_code2reason(code)) == NULL) + reason_phrase=""; + if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0) + goto done; + FCGX_FPrintF(req->out, "\r\n"); /* Write a body if cbuf is nonzero */ if (cb != NULL && cbuf_len(cb)){ - FCGX_FPrintF(req->out, "\r\n"); FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(req->out, "\r\n"); } retval = 0; - return retval; + done: + return retval; +} + +/*! + * @param[in] req Fastcgi request handle + */ +cbuf * +restconf_get_indata(void *req0) +{ + FCGX_Request *req = (FCGX_Request *)req0; + int c; + cbuf *cb = NULL; + + if ((cb = cbuf_new()) == NULL) + return NULL; + while ((c = FCGX_GetChar(req->in)) != -1) + cprintf(cb, "%c", c); + return cb; } diff --git a/apps/restconf/restconf_err.c b/apps/restconf/restconf_err.c new file mode 100644 index 00000000..047d3be9 --- /dev/null +++ b/apps/restconf/restconf_err.c @@ -0,0 +1,495 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2020 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * + * Return errors + * @see RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "restconf_lib.h" +#include "restconf_api.h" +#include "restconf_err.h" + +/* + * Constants + */ +/* In the fcgi implementations some errors had body, it would be cleaner to skip them + * None seem mandatory according to RFC 7231 + */ +#define SKIP_BODY + +/* + * NOTE, fcgi seems not enough with a status code (libevhtp is) but must also have a status + * header. + */ + +/*! HTTP error 400 + * @param[in] h Clicon handle + * @param[in] req Generic Www handle + */ +int +restconf_badrequest(clicon_handle h, + void *req) +{ + int retval = -1; + +#ifdef SKIP_BODY /* Remove the body - should it really be there? */ + if (restconf_reply_send(req, 400, NULL) < 0) + goto done; + retval = 0; + done: +#else + char *path; + cbuf *cb = NULL; + + /* Create body */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + path = clixon_restconf_param_get("REQUEST_URI", r->envp); + if (restconf_reply_header(req, "Content-Type", "text/html") < 0) + goto done; + cprintf(cb, "The requested URL %s or data is in some way badly formed.\n", path); + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, 400, cb) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); +#endif + return retval; +} + +/*! HTTP error 401 + * @param[in] h Clicon handle + * @param[in] req Generic Www handle + */ +int +restconf_unauthorized(clicon_handle h, + void *req) + +{ + int retval = -1; + +#ifdef SKIP_BODY /* Remove the body - should it really be there? */ + if (restconf_reply_send(req, 400, NULL) < 0) + goto done; + retval = 0; + done: +#else + char *path; + cbuf *cb = NULL; + + /* Create body */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + path = clixon_restconf_param_get("REQUEST_URI", r->envp); + if (restconf_reply_header(req, "Content-Type", "text/html") < 0) + goto done; + cprintf(cb, "access-denied\n"); + cprintf(cb, "The requested URL %s was unauthorized.\n", path); + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + + if (restconf_reply_send(req, 400, cb) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); +#endif + return retval; +} + +/*! HTTP error 403 + * @param[in] h Clicon handle + * @param[in] req Generic Www handle + */ +int +restconf_forbidden(clicon_handle h, + void *req) +{ + int retval = -1; +#ifdef SKIP_BODY /* Remove the body - should it really be there? */ + if (restconf_reply_send(req, 403, NULL) < 0) + goto done; + retval = 0; + done: +#else + char *path; + cbuf *cb = NULL; + + /* Create body */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + path = clixon_restconf_param_get("REQUEST_URI", r->envp); + if (restconf_reply_header(req, "Content-Type", "text/html") < 0) + goto done; + cprintf(cb, "The requested URL %s was forbidden.\n", path); + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, 403, cb) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); +#endif + return retval; +} + +/*! HTTP error 404 + * @param[in] h Clicon handle + * @param[in] req Generic Www handle + * XXX skip body? + */ +int +restconf_notfound(clicon_handle h, + void *req) +{ + int retval = -1; +#ifdef SKIP_BODY /* Remove the body - should it really be there? */ + if (restconf_reply_send(req, 404, NULL) < 0) + goto done; + retval = 0; + done: +#else + char *path; + cbuf *cb = NULL; + + /* Create body */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + path = clixon_restconf_param_get("REQUEST_URI", r->envp); + if (restconf_reply_header(req, "Content-Type", "text/html") < 0) + goto done; + cprintf(cb, "The requested URL %s was not found on this server.\n", path); + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, 404, cb) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); +#endif + return retval; +} + +/*! HTTP error 405 + * @param[in] req Generic Www handle + * @param[in] allow Which methods are allowed + */ +int +restconf_method_notallowed(void *req, + char *allow) +{ + int retval = -1; + + if (restconf_reply_header(req, "Allow", "%s", allow) < 0) + goto done; + if (restconf_reply_send(req, 405, NULL) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! HTTP error 406 Not acceptable + * @param[in] h Clicon handle + * @param[in] req Generic Www handle + */ +int +restconf_notacceptable(clicon_handle h, + void *req) +{ + int retval = -1; + +#ifdef SKIP_BODY /* Remove the body - should it really be there? */ + if (restconf_reply_send(req, 406, NULL) < 0) + goto done; + retval = 0; + done: +#else + char *path; + cbuf *cb = NULL; + + /* Create body */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + path = clixon_restconf_param_get("REQUEST_URI", r->envp); + if (restconf_reply_header(req, "Content-Type", "text/html") < 0) + goto done; + cprintf(cb, "The target resource does not have a current representation that would be acceptable to the user agent.\n", path); + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, 406, cb) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); +#endif + return retval; +} + +/*! HTTP error 409 + * @param[in] req Generic Www handle + */ +int +restconf_conflict(void *req) + +{ + int retval = -1; + + if (restconf_reply_send(req, 409, NULL) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! HTTP error 409 Unsupporte dmedia + * @param[in] req Generic Www handle + */ +int +restconf_unsupported_media(void *req) +{ + int retval = -1; + + if (restconf_reply_send(req, 415, NULL) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! HTTP error 500 Internal server error + * @param[in] h Clicon handle + * @param[in] req Generic Www handle + */ +int +restconf_internal_server_error(clicon_handle h, + void *req) +{ + int retval = -1; +#ifdef SKIP_BODY /* Remove the body - should it really be there? */ + if (restconf_reply_send(req, 500, NULL) < 0) + goto done; + retval = 0; + done: +#else + char *path; + cbuf *cb = NULL; + + /* Create body */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + path = clixon_restconf_param_get("REQUEST_URI", r->envp); + if (restconf_reply_header(req, "Content-Type", "text/html") < 0) + goto done; + cprintf(cb, "Internal server error when accessing %s\n", path); + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)= + goto done; + if (restconf_reply_send(req, 500, cb) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); +#endif + return retval; +} + +/*! HTTP error 501 Not implemented + * @param[in] req Generic Www handle + */ +int +restconf_notimplemented(void *req) +{ + int retval = -1; + + if (restconf_reply_send(req, 501, NULL) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Generic restconf error function on get/head request + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] xerr XML error message from backend + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media Output media + * @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping + * otherwise use this code + */ +int +api_return_err(clicon_handle h, + void *req, + cxobj *xerr, + int pretty, + restconf_media media, + int code0) +{ + int retval = -1; + cbuf *cb = NULL; + cbuf *cberr = NULL; + cxobj *xtag; + char *tagstr; + int code; + cxobj *xerr2 = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* A well-formed error message when entering here should look like: + * ...invalid-value + * Check this is so, otherwise generate an internal error. + */ + if (strcmp(xml_name(xerr), "rpc-error") != 0 || + (xtag = xpath_first(xerr, NULL, "error-tag")) == NULL){ + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cberr, "Internal error, system returned invalid error message: "); + if (netconf_err2cb(xerr, cberr) < 0) + goto done; + if (netconf_operation_failed_xml(&xerr2, "application", + cbuf_get(cberr)) < 0) + goto done; + if ((xerr = xpath_first(xerr2, NULL, "//rpc-error")) == NULL){ + clicon_err(OE_XML, 0, "Internal error, shouldnt happen"); + goto done; + } + } + if (xml_name_set(xerr, "error") < 0) + goto done; + tagstr = xml_body(xtag); + if (code0 != 0) + code = code0; + else{ + if ((code = restconf_err2code(tagstr)) < 0) + code = 500; /* internal server error */ + } + if (restconf_reply_header(req, "Content_Type", "%s", restconf_media_int2str(media)) < 0) + goto done; + switch (media){ + case YANG_DATA_XML: + clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); + if (pretty){ + cprintf(cb, " \n"); + if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0) + goto done; + cprintf(cb, " \r\n"); + } + else { + cprintf(cb, ""); + if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0) + goto done; + cprintf(cb, "\r\n"); + } + break; + case YANG_DATA_JSON: + clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); + if (pretty){ + cprintf(cb, "{\n\"ietf-restconf:errors\" : "); + if (xml2json_cbuf(cb, xerr, pretty) < 0) + goto done; + cprintf(cb, "\n}\r\n"); + } + else{ + cprintf(cb, "{"); + cprintf(cb, "\"ietf-restconf:errors\":"); + if (xml2json_cbuf(cb, xerr, pretty) < 0) + goto done; + cprintf(cb, "}\r\n"); + } + break; + default: + clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media); + goto done; + break; + } /* switch media */ + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, code, cb) < 0) + goto done; + // ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); + if (cberr) + cbuf_free(cberr); + return retval; +} diff --git a/apps/restconf/restconf_fcgi_lib.h b/apps/restconf/restconf_err.h similarity index 59% rename from apps/restconf/restconf_fcgi_lib.h rename to apps/restconf/restconf_err.h index af7a7910..bef6cc78 100644 --- a/apps/restconf/restconf_fcgi_lib.h +++ b/apps/restconf/restconf_err.h @@ -31,30 +31,30 @@ the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** - + * + * Return errors + * @see RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content */ -#ifndef _RESTCONF_FCGI_LIB_H_ -#define _RESTCONF_FCGI_LIB_H_ +#ifndef _RESTCONF_ERR_H_ +#define _RESTCONF_ERR_H_ /* * Prototypes */ -int restconf_badrequest(clicon_handle h, FCGX_Request *r); -int restconf_unauthorized(clicon_handle h, FCGX_Request *r); -int restconf_forbidden(clicon_handle h, FCGX_Request *r); -int restconf_notfound(clicon_handle h, FCGX_Request *r); -int restconf_notacceptable(clicon_handle h, FCGX_Request *r); -int restconf_conflict(FCGX_Request *r); -int restconf_unsupported_media(FCGX_Request *r); -int restconf_internal_server_error(clicon_handle h, FCGX_Request *r); -int restconf_notimplemented(FCGX_Request *r); -int restconf_test(FCGX_Request *r, int dbg); -int clixon_restconf_params_set(clicon_handle h, - char **envp); -int clixon_restconf_params_clear(clicon_handle h, char **envp); -cbuf *readdata(FCGX_Request *r); -int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, restconf_media media, int code0); -int http_location(clicon_handle h, FCGX_Request *r, cxobj *xobj); -#endif /* _RESTCONF_FCGI_LIB_H_ */ +int restconf_badrequest(clicon_handle h, void *req); +int restconf_unauthorized(clicon_handle h, void *req); +int restconf_forbidden(clicon_handle h, void *req); +int restconf_notfound(clicon_handle h, void *req); +int restconf_method_notallowed(void *req, char *allow); +int restconf_notacceptable(clicon_handle h, void *req); +int restconf_conflict(void *req); +int restconf_unsupported_media(void *req); +int restconf_internal_server_error(clicon_handle h, void *req); +int restconf_notimplemented(void *req); + +int api_return_err(clicon_handle h, void *req, cxobj *xerr, int pretty, restconf_media media, int code0); + + +#endif /* _RESTCONF_ERR_H_ */ diff --git a/apps/restconf/restconf_evhtp_main.c b/apps/restconf/restconf_evhtp_main.c index 2cdcd7e1..3ed9dcd0 100644 --- a/apps/restconf/restconf_evhtp_main.c +++ b/apps/restconf/restconf_evhtp_main.c @@ -178,6 +178,7 @@ query_iterator(evhtp_header_t *hdr, * several different places in evhtp * @param[in] h Clicon handle * @param[in] req Evhtp request struct + * @param[out] qvec Query parameters, ie the ?=&= stuff * @retval 0 OK * @retval -1 Error * The following parameters are set: @@ -211,7 +212,10 @@ evhtp_params_set(clicon_handle h, } meth = evhtp_request_get_method(req); - /* QUERY_STRING */ + /* QUERY_STRING in fcgi but go direct to the info instead of putting it in a string? + * This is different from all else: Ie one could have re-created a string here but + * that would mean double parsing,... + */ if (qvec && uri->query) if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){ clicon_err(OE_CFG, errno, "evhtp_kvs_for_each"); @@ -349,70 +353,29 @@ static void cx_path_restconf(evhtp_request_t *req, void *arg) { - evhtp_connection_t *conn; - clicon_handle h = arg; - struct evbuffer *b = NULL; - cvec *qvec = NULL; - size_t len = 0; - cbuf *cblen = NULL; + clicon_handle h = arg; + cvec *qvec = NULL; - clicon_debug(1, "%s", __FUNCTION__); - if (req == NULL){ - errno = EINVAL; - goto done; - } /* input debug */ if (clicon_debug_get()) evhtp_headers_for_each(req->headers_in, print_header, h); - - if ((cblen = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } + /* get accepted connection */ /* Query vector, ie the ?a=x&b=y stuff */ if ((qvec = cvec_new(0)) ==NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); goto done; } - /* get accepted connection */ - if ((conn = evhtp_request_get_connection(req)) == NULL){ - clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection"); - goto done; - } - /* Get all parameters from this request (resembling fcgi) */ + /* set fcgi-like paramaters (ignore query vector) */ if (evhtp_params_set(h, req, qvec) < 0) goto done; - - /* 1. create body */ - if ((b = evbuffer_new()) == NULL){ - clicon_err(OE_DAEMON, errno, "evbuffer_new"); + /* call generic function */ + if (api_root_restconf(h, req, qvec) < 0) goto done; - } - cprintf(cblen, "%lu", len); - - /* 2. add headers (can mix with body) */ - evhtp_headers_add_header(req->headers_out, evhtp_header_new("Cache-Control", "no-cache", 0, 0)); - evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Type", "application/xrd+xml", 0, 0)); - evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Length", cbuf_get(cblen), 0, 0)); - /* Optional? */ - htp_sslutil_add_xheaders(req->headers_out, conn->ssl, HTP_SSLUTILS_XHDR_ALL); - - /* 3. send reply */ - evhtp_send_reply_start(req, EVHTP_RES_OK); - evhtp_send_reply_body(req, b); - evhtp_send_reply_end(req); - - /* Clear (fcgi)paramaters */ + /* Clear (fcgi) paramaters from this request */ if (evhtp_params_clear(h) < 0) goto done; done: - if (qvec) - cvec_free(qvec); - if (cblen) - cbuf_free(cblen); - if (b) - evhtp_safe_free(b, evbuffer_free); return; /* void */ } @@ -756,7 +719,6 @@ main(int argc, if (clicon_options_main(h) < 0) goto done; - event_base_loop(evbase, 0); evhtp_unbind_socket(htp); diff --git a/apps/restconf/restconf_fcgi_lib.c b/apps/restconf/restconf_fcgi_lib.c deleted file mode 100644 index 34ae8c0c..00000000 --- a/apps/restconf/restconf_fcgi_lib.c +++ /dev/null @@ -1,480 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2020 Olof Hagsand - Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https - * @note The response payload for errors uses text_html. RFC7231 is vague - * on the response payload (and its media). Maybe it should be omitted - * altogether? - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clicon */ -#include - -#include /* Need to be after clixon_xml-h due to attribute format */ - -#include "restconf_lib.h" -#include "restconf_fcgi_lib.h" - -/*! HTTP error 400 - * @param[in] req Fastcgi request handle - */ -int -restconf_badrequest(clicon_handle h, - FCGX_Request *req) -{ - char *path; - - path = clixon_restconf_param_get(h, "REQUEST_URI"); - FCGX_SetExitStatus(400, req->out); - FCGX_FPrintF(req->out, "Status: 400 Bad Request\r\n"); /* 400 bad request */ - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Clixon Bad request/h1>\n"); - FCGX_FPrintF(req->out, "The requested URL %s or data is in some way badly formed.\n", - path); - return 0; -} - -/*! HTTP error 401 - * @param[in] req Fastcgi request handle - */ -int -restconf_unauthorized(clicon_handle h, - FCGX_Request *req) -{ - char *path; - - path = clixon_restconf_param_get(h, "REQUEST_URI"); - FCGX_SetExitStatus(401, req->out); - FCGX_FPrintF(req->out, "Status: 401 Unauthorized\r\n"); /* 401 unauthorized */ - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "access-denied\n"); - FCGX_FPrintF(req->out, "The requested URL %s was unauthorized.\n", path); - return 0; -} - -/*! HTTP error 403 - * @param[in] req Fastcgi request handle - */ -int -restconf_forbidden(clicon_handle h, - FCGX_Request *req) -{ - char *path; - - path = clixon_restconf_param_get(h, "REQUEST_URI"); - FCGX_SetExitStatus(403, req->out); - FCGX_FPrintF(req->out, "Status: 403 Forbidden\r\n"); /* 403 forbidden */ - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Forbidden

\n"); - FCGX_FPrintF(req->out, "The requested URL %s was forbidden.\n", path); - return 0; -} - -/*! HTTP error 404 - * @param[in] req Fastcgi request handle - */ -int -restconf_notfound(clicon_handle h, - FCGX_Request *req) -{ - char *path; - - path = clixon_restconf_param_get(h, "REQUEST_URI"); - FCGX_SetExitStatus(404, req->out); - FCGX_FPrintF(req->out, "Status: 404 Not Found\r\n"); /* 404 not found */ - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Not Found

\n"); - FCGX_FPrintF(req->out, "Not Found\n"); - FCGX_FPrintF(req->out, "The requested URL %s was not found on this server.\n", - path); - return 0; -} - -/*! HTTP error 406 Not acceptable - * @param[in] req Fastcgi request handle - */ -int -restconf_notacceptable(clicon_handle h, - FCGX_Request *req) -{ - char *path; - - path = clixon_restconf_param_get(h, "REQUEST_URI"); - FCGX_SetExitStatus(406, req->out); - FCGX_FPrintF(req->out, "Status: 406 Not Acceptable\r\n"); /* 406 not acceptible */ - - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Not Acceptable

\n"); - FCGX_FPrintF(req->out, "Not Acceptable\n"); - FCGX_FPrintF(req->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n", - path); - return 0; -} - -/*! HTTP error 409 - * @param[in] req Fastcgi request handle - */ -int -restconf_conflict(FCGX_Request *req) -{ - FCGX_SetExitStatus(409, req->out); - FCGX_FPrintF(req->out, "Status: 409 Conflict\r\n"); /* 409 Conflict */ - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Data resource already exists

\n"); - return 0; -} - -/*! HTTP error 409 - * @param[in] req Fastcgi request handle - */ -int -restconf_unsupported_media(FCGX_Request *req) -{ - FCGX_SetExitStatus(415, req->out); - FCGX_FPrintF(req->out, "Status: 415 Unsupported Media Type\r\n"); - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Unsupported Media Type

\n"); - return 0; -} - -/*! HTTP error 500 - * @param[in] req Fastcgi request handle - */ -int -restconf_internal_server_error(clicon_handle h, - FCGX_Request *req) -{ - char *path; - - clicon_debug(1, "%s", __FUNCTION__); - path = clixon_restconf_param_get(h, "REQUEST_URI"); - FCGX_FPrintF(req->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */ - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Internal server error when accessing %s

\n", path); - return 0; -} - -/*! HTTP error 501 - * @param[in] req Fastcgi request handle - */ -int -restconf_notimplemented(FCGX_Request *req) -{ - clicon_debug(1, "%s", __FUNCTION__); - FCGX_FPrintF(req->out, "Status: 501 Not Implemented\r\n"); - FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "

Not Implemented/h1>\n"); - return 0; -} - -/*! Print all FCGI headers - * @param[in] req Fastcgi request handle - * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https - */ -int -restconf_test(FCGX_Request *req, - int dbg) -{ - char **environ = req->envp; - int i; - - clicon_debug(1, "All environment vars:"); - for (i = 0; environ[i] != NULL; i++){ - clicon_debug(1, "%s", environ[i]); - } - clicon_debug(1, "End environment vars"); - return 0; -} - -/*! Convert FCGI parameters to clixon runtime data - * @param[in] h Clixon handle - * @param[in] envp Fastcgi request handle parameter array on the format "=" - * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https - */ -int -clixon_restconf_params_set(clicon_handle h, - char **envp) -{ - int retval = -1; - int i; - char *param = NULL; - char *val = NULL; - - clicon_debug(1, "%s", __FUNCTION__); - for (i = 0; envp[i] != NULL; i++){ /* on the form = */ - if (clixon_strsplit(envp[i], '=', ¶m, &val) < 0) - goto done; - clicon_debug(1, "%s param:%s val:%s", __FUNCTION__, param, val); - if (clixon_restconf_param_set(h, param, val) < 0) - goto done; - if (param){ - free(param); - param = NULL; - } - if (val){ - free(val); - val = NULL; - } - } - retval = 0; - done: - clicon_debug(1, "%s %d", __FUNCTION__, retval); - return retval; -} - -/*! Clear all FCGI parameters in an environment - * @param[in] h Clixon handle - * @param[in] envp Fastcgi request handle parameter array on the format "=" - * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https - */ -int -clixon_restconf_params_clear(clicon_handle h, - char **envp) -{ - int retval = -1; - int i; - char *param = NULL; - - clicon_debug(1, "%s", __FUNCTION__); - for (i = 0; envp[i] != NULL; i++){ /* on the form = */ - if (clixon_strsplit(envp[i], '=', ¶m, NULL) < 0) - goto done; - clicon_debug(1, "%s param:%s", __FUNCTION__, param); - if (clixon_restconf_param_del(h, param) < 0) - goto done; - if (param){ - free(param); - param = NULL; - } - } - retval = 0; - done: - clicon_debug(1, "%s %d", __FUNCTION__, retval); - return retval; -} - -/*! - * @param[in] req Fastcgi request handle - */ -cbuf * -readdata(FCGX_Request *req) -{ - int c; - cbuf *cb; - - if ((cb = cbuf_new()) == NULL) - return NULL; - while ((c = FCGX_GetChar(req->in)) != -1) - cprintf(cb, "%c", c); - return cb; -} - -/*! Return restconf error on get/head request - * @param[in] h Clixon handle - * @param[in] req Fastcgi request handle - * @param[in] xerr XML error message from backend - * @param[in] pretty Set to 1 for pretty-printed xml/json output - * @param[in] media Output media - * @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping - * otherwise use this code - */ -int -api_return_err(clicon_handle h, - FCGX_Request *req, - cxobj *xerr, - int pretty, - restconf_media media, - int code0) -{ - int retval = -1; - cbuf *cb = NULL; - cbuf *cberr = NULL; - cxobj *xtag; - char *tagstr; - int code; - cxobj *xerr2 = NULL; - const char *reason_phrase; - - clicon_debug(1, "%s", __FUNCTION__); - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* A well-formed error message when entering here should look like: - * ...invalid-value - * Check this is so, otherwise generate an internal error. - */ - if (strcmp(xml_name(xerr), "rpc-error") != 0 || - (xtag = xpath_first(xerr, NULL, "error-tag")) == NULL){ - if ((cberr = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cberr, "Internal error, system returned invalid error message: "); - if (netconf_err2cb(xerr, cberr) < 0) - goto done; - if (netconf_operation_failed_xml(&xerr2, "application", - cbuf_get(cberr)) < 0) - goto done; - if ((xerr = xpath_first(xerr2, NULL, "//rpc-error")) == NULL){ - clicon_err(OE_XML, 0, "Internal error, shouldnt happen"); - goto done; - } - } - if (xml_name_set(xerr, "error") < 0) - goto done; - tagstr = xml_body(xtag); - if (code0 != 0) - code = code0; - else{ - if ((code = restconf_err2code(tagstr)) < 0) - code = 500; /* internal server error */ - } - if ((reason_phrase = restconf_code2reason(code)) == NULL) - reason_phrase=""; - FCGX_SetExitStatus(code, req->out); /* Created */ - FCGX_FPrintF(req->out, "Status: %d %s\r\n", code, reason_phrase); - FCGX_FPrintF(req->out, "Content-Type: %s\r\n\r\n", restconf_media_int2str(media)); - switch (media){ - case YANG_DATA_XML: - if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0) - goto done; - clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); - if (pretty){ - FCGX_FPrintF(req->out, " \n", cbuf_get(cb)); - FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(req->out, " \r\n"); - } - else { - FCGX_FPrintF(req->out, "", cbuf_get(cb)); - FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(req->out, "\r\n"); - } - break; - case YANG_DATA_JSON: - if (xml2json_cbuf(cb, xerr, pretty) < 0) - goto done; - clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); - if (pretty){ - FCGX_FPrintF(req->out, "{\n"); - FCGX_FPrintF(req->out, " \"ietf-restconf:errors\" : %s\n", - cbuf_get(cb)); - FCGX_FPrintF(req->out, "}\r\n"); - } - else{ - FCGX_FPrintF(req->out, "{"); - FCGX_FPrintF(req->out, "\"ietf-restconf:errors\":"); - FCGX_FPrintF(req->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(req->out, "}\r\n"); - } - break; - default: - clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media); - goto done; - break; - } /* switch media */ - // ok: - retval = 0; - done: - clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); - if (cb) - cbuf_free(cb); - if (cberr) - cbuf_free(cberr); - return retval; -} - -/*! Print location header from FCGI environment - * @param[in] req Fastcgi request handle - * @param[in] xobj If set (eg POST) add to api-path - * $https “on” if connection operates in SSL mode, or an empty string otherwise - * @note ports are ignored - */ -int -http_location(clicon_handle h, - FCGX_Request *req, - cxobj *xobj) -{ - int retval = -1; - char *https; - char *host; - char *request_uri; - cbuf *cb = NULL; - - https = clixon_restconf_param_get(h, "HTTPS"); - host = clixon_restconf_param_get(h, "HTTP_HOST"); - request_uri = clixon_restconf_param_get(h, "REQUEST_URI"); - if (xobj != NULL){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, 0, "cbuf_new"); - goto done; - } - if (xml2api_path_1(xobj, cb) < 0) - goto done; - FCGX_FPrintF(req->out, "Location: http%s://%s%s%s\r\n", - https?"s":"", - host, - request_uri, - cbuf_get(cb)); - } - else - FCGX_FPrintF(req->out, "Location: http%s://%s%s\r\n", - https?"s":"", - host, - request_uri); - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} - diff --git a/apps/restconf/restconf_fcgi_main.c b/apps/restconf/restconf_fcgi_main.c index fa4422be..6552ee6c 100644 --- a/apps/restconf/restconf_fcgi_main.c +++ b/apps/restconf/restconf_fcgi_main.c @@ -79,9 +79,8 @@ /* restconf */ #include "restconf_lib.h" /* generic shared with plugins */ #include "restconf_api.h" /* generic not shared with plugins */ - +#include "restconf_err.h" #include "restconf_root.h" /* generic not shared with plugins */ -#include "restconf_fcgi_lib.h" /* fcgi specific */ #include "restconf_methods.h" /* fcgi specific */ #include "restconf_methods_get.h" #include "restconf_methods_post.h" @@ -90,6 +89,90 @@ /* Command line options to be passed to getopt(3) */ #define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:" +/*! Convert FCGI parameters to clixon runtime data + * @param[in] h Clixon handle + * @param[in] envp Fastcgi request handle parameter array on the format "=" + * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https + */ +static int +fcgi_params_set(clicon_handle h, + char **envp) +{ + int retval = -1; + int i; + char *param = NULL; + char *val = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + for (i = 0; envp[i] != NULL; i++){ /* on the form = */ + if (clixon_strsplit(envp[i], '=', ¶m, &val) < 0) + goto done; + clicon_debug(1, "%s param:%s val:%s", __FUNCTION__, param, val); + if (clixon_restconf_param_set(h, param, val) < 0) + goto done; + if (param){ + free(param); + param = NULL; + } + if (val){ + free(val); + val = NULL; + } + } + retval = 0; + done: + clicon_debug(1, "%s %d", __FUNCTION__, retval); + return retval; +} + +/*! Clear all FCGI parameters in an environment + * @param[in] h Clixon handle + * @param[in] envp Fastcgi request handle parameter array on the format "=" + * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https + */ +static int +fcgi_params_clear(clicon_handle h, + char **envp) +{ + int retval = -1; + int i; + char *param = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + for (i = 0; envp[i] != NULL; i++){ /* on the form = */ + if (clixon_strsplit(envp[i], '=', ¶m, NULL) < 0) + goto done; + clicon_debug(1, "%s param:%s", __FUNCTION__, param); + if (clixon_restconf_param_del(h, param) < 0) + goto done; + if (param){ + free(param); + param = NULL; + } + } + retval = 0; + done: + clicon_debug(1, "%s %d", __FUNCTION__, retval); + return retval; +} + +/*! Print all FCGI headers + * @param[in] req Fastcgi request handle + * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https + */ +static int +fcgi_print_headers(FCGX_Request *req) +{ + char **environ = req->envp; + int i; + + clicon_debug(1, "All environment vars:"); + for (i = 0; environ[i] != NULL; i++) + clicon_debug(1, "%s", environ[i]); + clicon_debug(1, "End environment vars"); + return 0; +} + /*! Generic REST method, GET, PUT, DELETE, etc * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle @@ -97,7 +180,6 @@ * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] dvec Stream input daat * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_in Input media * @param[in] media_out Output media @@ -176,46 +258,6 @@ api_operations(clicon_handle h, return retval; } -/*! Determine the root of the RESTCONF API - * @param[in] h Clicon handle - * @param[in] r Fastcgi request handle - * @note Hardcoded to "/restconf" - * Return see RFC8040 3.1 and RFC7320 - * In line with the best practices defined by [RFC7320], RESTCONF - * enables deployments to specify where the RESTCONF API is located. - */ -#if 0 -static int -api_well_known(clicon_handle h, - FCGX_Request *req) -{ - char *request_method; - FCGX_Request *body; - - /* call generic function */ - if (api_well_known(h, req) < 0) - goto done; - - clicon_debug(1, "%s", __FUNCTION__); - if (req == NULL){ - errno = EINVAL; - goto done; - } - request_method = clixon_restconf_param_get(h, "REQUEST_METHOD"); - if (strcmp(request_method, "GET") !=0 ) - return restconf_method_notallowed(req, "GET"); - restconf_reply_status_code(req, 200); /* OK */ - restconf_reply_header_add(req, "Cache-Control", "no-cache"); - restconf_reply_header_add(req, "Content-Type", "application/xrd+xml"); - body = restconf_reply_body_start(req); - - restconf_reply_body_add(body, NULL, "\n"); - restconf_reply_body_add(body, NULL, " \n"); - restconf_reply_body_add(body, NULL, "\r\n"); - done: - return 0; -} -#endif /*! Retrieve the Top-Level API Resource * @param[in] h Clicon handle * @param[in] r Fastcgi request handle @@ -224,10 +266,10 @@ api_well_known(clicon_handle h, * XXX doesnt check method */ static int -api_root(clicon_handle h, - FCGX_Request *r, - int pretty, - restconf_media media_out) +api_fcgi_root(clicon_handle h, + FCGX_Request *r, + int pretty, + restconf_media media_out) { int retval = -1; @@ -337,25 +379,23 @@ api_yang_library_version(clicon_handle h, * @param[in] r Fastcgi request handle */ static int -api_restconf(clicon_handle h, - FCGX_Request *req) +api_fcgi_restconf(clicon_handle h, + FCGX_Request *req) { int retval = -1; char *path; char *query = NULL; - char *method; + char *api_resource; char **pvec = NULL; int pn; cvec *qvec = NULL; - cvec *dvec = NULL; cvec *pcvec = NULL; /* for rest api */ cbuf *cb = NULL; - char *data; + char *indata = NULL; int authenticated = 0; char *media_str = NULL; restconf_media media_out = YANG_DATA_JSON; int pretty; - cbuf *cbret = NULL; cxobj *xret = NULL; cxobj *xerr; @@ -398,30 +438,28 @@ api_restconf(clicon_handle h, retval = restconf_notfound(h, req); goto done; } - restconf_test(req, 1); + fcgi_print_headers(req); if (pn == 2){ - retval = api_root(h, req, pretty, media_out); + retval = api_fcgi_root(h, req, pretty, media_out); goto done; } - if ((method = pvec[2]) == NULL){ + if ((api_resource = pvec[2]) == NULL){ retval = restconf_notfound(h, req); goto done; } - clicon_debug(1, "%s: method=%s", __FUNCTION__, method); + clicon_debug(1, "%s: api_resource=%s", __FUNCTION__, api_resource); if (query != NULL && strlen(query)) if (str2cvec(query, '&', '=', &qvec) < 0) goto done; if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ goto done; - /* data */ - if ((cb = readdata(req)) == NULL) + /* indata */ + if ((cb = restconf_get_indata(req)) == NULL) goto done; - data = cbuf_get(cb); - clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); + indata = cbuf_get(cb); + clicon_debug(1, "%s DATA=%s", __FUNCTION__, indata); - if (str2cvec(data, '&', '=', &dvec) < 0) - goto done; /* If present, check credentials. See "plugin_credentials" in plugin * See RFC 8040 section 2.5 */ @@ -429,7 +467,7 @@ api_restconf(clicon_handle h, goto done; clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); - /* If set but no user, we set a dummy user */ + /* If set but no user, set a dummy user */ if (authenticated){ if (clicon_username_get(h) == NULL) clicon_username_set(h, "none"); @@ -445,22 +483,20 @@ api_restconf(clicon_handle h, goto ok; } clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); - if (strcmp(method, "yang-library-version")==0){ + if (strcmp(api_resource, "yang-library-version")==0){ if (api_yang_library_version(h, req, pretty, media_out) < 0) goto done; } - else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ - if (api_data(h, req, path, pcvec, 2, qvec, data, + else if (strcmp(api_resource, "data") == 0){ /* restconf, skip /api/data */ + if (api_data(h, req, path, pcvec, 2, qvec, indata, pretty, media_out) < 0) goto done; } - else if (strcmp(method, "operations") == 0){ /* rpc */ - if (api_operations(h, req, path, pcvec, 2, qvec, data, + else if (strcmp(api_resource, "operations") == 0){ /* rpc */ + if (api_operations(h, req, path, pcvec, 2, qvec, indata, pretty, media_out) < 0) goto done; } - else if (strcmp(method, "test") == 0) - restconf_test(req, 0); else restconf_notfound(h, req); ok: @@ -469,16 +505,12 @@ api_restconf(clicon_handle h, clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (pvec) free(pvec); - if (dvec) - cvec_free(dvec); if (qvec) cvec_free(qvec); if (pcvec) cvec_free(pcvec); if (cb) cbuf_free(cb); - if (cbret) - cbuf_free(cbret); if (xret) xml_free(xret); return retval; @@ -834,12 +866,16 @@ main(int argc, /* Translate from FCGI parameter form to Clixon runtime data * XXX: potential name collision? */ - if (clixon_restconf_params_set(h, req->envp) < 0) + if (fcgi_params_set(h, req->envp) < 0) goto done; + /* Debug_ print headers */ + if (clicon_debug_get()) + fcgi_print_headers(req); + if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){ clicon_debug(1, "path: %s", path); if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0) - api_restconf(h, req); /* This is the function */ + api_fcgi_restconf(h, req); /* This is the function */ else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) { api_stream(h, req, stream_path, &finish); } @@ -853,7 +889,7 @@ main(int argc, } else clicon_debug(1, "NULL URI"); - if (clixon_restconf_params_clear(h, req->envp) < 0) + if (fcgi_params_clear(h, req->envp) < 0) goto done; if (finish) FCGX_Finish_r(req); diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 750be387..4741b2d2 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -478,7 +478,6 @@ restconf_uripath(clicon_handle h) return path; } - /*! Drop privileges from root to user (or already at user) * @param[in] h Clicon handle * @param[in] user Drop to this level @@ -540,15 +539,3 @@ restconf_drop_privileges(clicon_handle h, return retval; } -/*! HTTP error 405 - * @param[in] req Generic Www handle - * @param[in] allow Which methods are allowed - */ -int -restconf_method_notallowed(void *req, - char *allow) -{ - restconf_reply_status_code(req, 405); - restconf_reply_header_add(req, "Allow", "%s", allow); - return 0; -} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 5b570c34..f0a6c254 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -69,6 +69,5 @@ int clixon_restconf_param_set(clicon_handle h, char *param, char *val); int clixon_restconf_param_del(clicon_handle h, char *param); char *restconf_uripath(clicon_handle h); int restconf_drop_privileges(clicon_handle h, char *user); -int restconf_method_notallowed(void *req, char *allow); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index e646fbad..112ca34f 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -119,16 +119,16 @@ Mapping netconf error-tag -> status code /* clicon */ #include -#include /* Need to be after clixon_xml-h due to attribute format */ - #include "restconf_lib.h" -#include "restconf_fcgi_lib.h" +#include "restconf_api.h" +#include "restconf_err.h" #include "restconf_methods.h" /*! REST OPTIONS method * According to restconf * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle + * @param[in] req Generic Www handle + * * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode @@ -139,14 +139,20 @@ Mapping netconf error-tag -> status code */ int api_data_options(clicon_handle h, - FCGX_Request *r) + void *req) { + int retval = -1; + clicon_debug(1, "%s", __FUNCTION__); - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE\r\n"); - FCGX_FPrintF(r->out, "Accept-Patch: application/yang-data+xml,application/yang-data+json\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - return 0; + if (restconf_reply_header(req, "Allow", "OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE") < 0) + goto done; + if (restconf_reply_header(req, "Accept-Patch", "application/yang-data+xml,application/yang-data+json") < 0) + goto done; + if (restconf_reply_send(req, 200, NULL) < 0) + goto done; + retval = 0; + done: + return retval; } /*! Check matching keys @@ -226,7 +232,7 @@ match_list_keys(yang_stmt *y, */ static int api_data_write(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path0, cvec *pcvec, int pi, @@ -285,7 +291,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -300,7 +306,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; @@ -311,7 +317,7 @@ api_data_write(clicon_handle h, #endif if (xml_child_nr(xret) == 0){ /* Object does not exist */ if (plain_patch){ /* If the target resource instance does not exist, the server MUST NOT create it. */ - restconf_badrequest(h, r); + restconf_badrequest(h, req); goto ok; } else @@ -340,7 +346,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -357,7 +363,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -400,7 +406,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -409,7 +415,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -422,7 +428,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -431,13 +437,13 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } break; default: - restconf_unsupported_media(r); + restconf_unsupported_media(req); goto ok; break; } /* switch media_in */ @@ -452,7 +458,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -471,7 +477,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -520,7 +526,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -545,7 +551,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -569,7 +575,7 @@ api_data_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -610,7 +616,7 @@ api_data_write(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -631,7 +637,7 @@ api_data_write(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -659,14 +665,13 @@ api_data_write(clicon_handle h, } /* Check if it was created, or if we tried again and replaced it */ if (op == OP_CREATE){ - FCGX_SetExitStatus(201, r->out); /* Created */ - FCGX_FPrintF(r->out, "Status: 201 Created\r\n"); + if (restconf_reply_send(req, 201, NULL) < 0) + goto done; } else{ - FCGX_SetExitStatus(204, r->out); /* Replaced */ - FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); + if (restconf_reply_send(req, 204, NULL) < 0) + goto done; } - FCGX_FPrintF(r->out, "\r\n"); ok: retval = 0; done: @@ -693,14 +698,14 @@ api_data_write(clicon_handle h, } /* api_data_write */ /*! Generic REST PUT method - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where to start pcvec - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] data Stream input data - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * @note restconf PUT is mapped to edit-config replace. @@ -729,7 +734,7 @@ api_data_write(clicon_handle h, */ int api_data_put(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path0, cvec *pcvec, int pi, @@ -741,19 +746,19 @@ api_data_put(clicon_handle h, restconf_media media_in; media_in = restconf_content_type(h); - return api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty, + return api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_in, media_out, 0); } /*! Generic REST PATCH method for plain patch - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where to start pcvec - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] data Stream input data - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * Netconf: (nc:operation="merge") * See RFC8040 Sec 4.6.1 @@ -764,7 +769,7 @@ api_data_put(clicon_handle h, */ int api_data_patch(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path0, cvec *pcvec, int pi, @@ -780,26 +785,26 @@ api_data_patch(clicon_handle h, switch (media_in){ case YANG_DATA_XML: case YANG_DATA_JSON: /* plain patch */ - ret = api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty, + ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_in, media_out, 1); break; case YANG_PATCH_XML: case YANG_PATCH_JSON: /* RFC 8072 patch */ - ret = restconf_notimplemented(r); + ret = restconf_notimplemented(req); break; default: - ret = restconf_unsupported_media(r); + ret = restconf_unsupported_media(req); break; } return ret; } /*! Generic REST DELETE method translated to edit-config - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] pi Offset, where path starts - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] pi Offset, where path starts + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * See RFC 8040 Sec 4.7 * Example: @@ -808,7 +813,7 @@ api_data_patch(clicon_handle h, */ int api_data_delete(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path, int pi, int pretty, @@ -850,7 +855,7 @@ api_data_delete(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -879,7 +884,7 @@ api_data_delete(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -901,7 +906,7 @@ api_data_delete(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -927,10 +932,8 @@ api_data_delete(clicon_handle h, clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__); } } - FCGX_SetExitStatus(204, r->out); - FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); - FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); - FCGX_FPrintF(r->out, "\r\n"); + if (restconf_reply_send(req, 204, NULL) < 0) + goto done; ok: retval = 0; done: diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 922ee2f9..4a665470 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -3,6 +3,7 @@ ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. @@ -41,18 +42,18 @@ /* * Prototypes */ -int api_data_options(clicon_handle h, FCGX_Request *r); -int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, +int api_data_options(clicon_handle h, void *req); +int api_data_put(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi, cvec *qvec, char *data, int pretty, restconf_media media_out); -int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, +int api_data_patch(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi, cvec *qvec, char *data, int pretty, restconf_media media_out); -int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, +int api_data_delete(clicon_handle h, void *req, char *api_path, int pi, int pretty, restconf_media media_out); #endif /* _RESTCONF_METHODS_H_ */ diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c index 43b36414..8794d581 100644 --- a/apps/restconf/restconf_methods_get.c +++ b/apps/restconf/restconf_methods_get.c @@ -57,21 +57,20 @@ /* clicon */ #include -#include /* Need to be after clixon_xml-h due to attribute format */ - #include "restconf_lib.h" -#include "restconf_fcgi_lib.h" +#include "restconf_api.h" +#include "restconf_err.h" #include "restconf_methods_get.h" /*! Generic GET (both HEAD and GET) * According to restconf - * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where path starts - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where path starts + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * @param[in] head If 1 is HEAD, otherwise GET * @code @@ -93,7 +92,7 @@ */ static int api_data_get2(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path, cvec *pcvec, /* XXX remove? */ int pi, @@ -148,7 +147,7 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -165,7 +164,7 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -187,7 +186,7 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -213,7 +212,7 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -226,7 +225,7 @@ api_data_get2(clicon_handle h, #endif /* Check if error return */ if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -234,9 +233,13 @@ api_data_get2(clicon_handle h, if ((cbx = cbuf_new()) == NULL) goto done; if (head){ - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); - FCGX_FPrintF(r->out, "\r\n"); + /* Same headers as the GET, but no body */ + if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) + goto done; + if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) + goto done; + if (restconf_reply_send(req, 200, NULL) < 0) + goto done; goto ok; } if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */ @@ -261,7 +264,7 @@ api_data_get2(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -275,7 +278,7 @@ api_data_get2(clicon_handle h, goto done; /* override invalid-value default 400 with 404 */ if ((xe = xpath_first(xerr, NULL, "rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, media_out, 404) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 404) < 0) goto done; } goto ok; @@ -309,12 +312,12 @@ api_data_get2(clicon_handle h, } } clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); - FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) + goto done; + if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) + goto done; + if (restconf_reply_send(req, 200, cbx) < 0) + goto done; ok: retval = 0; done: @@ -337,13 +340,13 @@ api_data_get2(clicon_handle h, } /*! REST HEAD method - * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where path starts - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where path starts + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * * The HEAD method is sent by the client to retrieve just the header fields @@ -353,7 +356,7 @@ api_data_get2(clicon_handle h, */ int api_data_head(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path, cvec *pcvec, int pi, @@ -361,18 +364,18 @@ api_data_head(clicon_handle h, int pretty, restconf_media media_out) { - return api_data_get2(h, r, api_path, pcvec, pi, qvec, pretty, media_out, 1); + return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 1); } /*! REST GET method * According to restconf - * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where path starts - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where path starts + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 @@ -392,7 +395,7 @@ api_data_head(clicon_handle h, */ int api_data_get(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path, cvec *pcvec, int pi, @@ -400,12 +403,12 @@ api_data_get(clicon_handle h, int pretty, restconf_media media_out) { - return api_data_get2(h, r, api_path, pcvec, pi, qvec, pretty, media_out, 0); + return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 0); } /*! GET restconf/operations resource * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle + * @param[in] req Generic Www handle * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts @@ -430,7 +433,7 @@ api_data_get(clicon_handle h, */ int api_operations_get(clicon_handle h, - FCGX_Request *r, + void *req, char *path, int pi, cvec *qvec, @@ -505,11 +508,13 @@ api_operations_get(clicon_handle h, default: break; } - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + + if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) + goto done; + if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) + goto done; + if (restconf_reply_send(req, 200, cbx) < 0) + goto done; // ok: retval = 0; done: diff --git a/apps/restconf/restconf_methods_get.h b/apps/restconf/restconf_methods_get.h index 304d0cf9..56d758f5 100644 --- a/apps/restconf/restconf_methods_get.h +++ b/apps/restconf/restconf_methods_get.h @@ -3,6 +3,7 @@ ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. @@ -40,11 +41,11 @@ /* * Prototypes */ -int api_data_head(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, +int api_data_head(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi, cvec *qvec, int pretty, restconf_media media_out); -int api_data_get(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, +int api_data_get(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi, cvec *qvec, int pretty, restconf_media media_out); -int api_operations_get(clicon_handle h, FCGX_Request *r, +int api_operations_get(clicon_handle h, void *req, char *api_path, int pi, cvec *qvec, char *data, int pretty, restconf_media media_out); diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index 9a384a82..60580941 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -59,21 +59,67 @@ /* clicon */ #include -#include /* Need to be after clixon_xml.h due to attribute format */ - #include "restconf_lib.h" -#include "restconf_fcgi_lib.h" +#include "restconf_api.h" +#include "restconf_err.h" #include "restconf_methods_post.h" +/*! Print location header from + * @param[in] req Generic Www handle + * @param[in] xobj If set (eg POST) add to api-path + * $https “on” if connection operates in SSL mode, or an empty string otherwise + * @note ports are ignored + */ +static int +http_location_header(clicon_handle h, + void *req, + cxobj *xobj) +{ + int retval = -1; + char *https; + char *host; + char *request_uri; + cbuf *cb = NULL; + + https = clixon_restconf_param_get(h, "HTTPS"); + host = clixon_restconf_param_get(h, "HTTP_HOST"); + request_uri = clixon_restconf_param_get(h, "REQUEST_URI"); + if (xobj != NULL){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + if (xml2api_path_1(xobj, cb) < 0) + goto done; + if (restconf_reply_header(req, "Location", "http%s://%s%s%s", + https?"s":"", + host, + request_uri, + cbuf_get(cb)) < 0) + goto done; + } + else + if (restconf_reply_header(req, "Location", "http%s://%s%s", + https?"s":"", + host, + request_uri) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + /*! Generic REST POST method - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where to start pcvec - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] data Stream input data - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * restconf POST is mapped to edit-config create. * @see RFC8040 Sec 4.4.1 @@ -99,7 +145,7 @@ */ int api_data_post(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path, int pi, cvec *qvec, @@ -153,7 +199,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -168,7 +214,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -198,7 +244,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -207,7 +253,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -220,7 +266,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -229,13 +275,13 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } break; default: - restconf_unsupported_media(r); + restconf_unsupported_media(req); goto ok; break; } /* switch media_in */ @@ -251,7 +297,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -284,7 +330,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; @@ -298,7 +344,7 @@ api_data_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -332,7 +378,7 @@ api_data_post(clicon_handle h, if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -354,7 +400,7 @@ api_data_post(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) /* Use original xe */ + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) /* Use original xe */ goto done; goto ok; } @@ -376,14 +422,13 @@ api_data_post(clicon_handle h, goto done; /* If copy-config failed, log and ignore (already committed) */ if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){ - clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__); } } - FCGX_SetExitStatus(201, r->out); - FCGX_FPrintF(r->out, "Status: 201 Created\r\n"); - http_location(h, r, xdata); - FCGX_FPrintF(r->out, "\r\n"); + if (http_location_header(h, req, xdata) < 0) + goto done; + if (restconf_reply_send(req, 201, NULL) < 0) + goto done; ok: retval = 0; done: @@ -404,8 +449,8 @@ api_data_post(clicon_handle h, } /* api_data_post */ /*! Handle input data to api_operations_post - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] data Stream input data * @param[in] yspec Yang top-level specification * @param[in] yrpc Yang rpc spec @@ -426,7 +471,7 @@ api_data_post(clicon_handle h, */ static int api_operations_post_input(clicon_handle h, - FCGX_Request *r, + void *req, char *data, yang_stmt *yspec, yang_stmt *yrpc, @@ -462,17 +507,16 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto fail; } if (ret == 0){ if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){ - clicon_debug(1, "%s F", __FUNCTION__); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto fail; } @@ -487,7 +531,7 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto fail; } @@ -496,17 +540,16 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto fail; } break; default: - restconf_unsupported_media(r); + restconf_unsupported_media(req); goto fail; break; } /* switch media_in */ - clicon_debug(1, "%s F", __FUNCTION__); xml_name_set(xdata, "data"); /* Here xdata is: * ... @@ -531,7 +574,7 @@ api_operations_post_input(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto fail; } @@ -560,8 +603,8 @@ api_operations_post_input(clicon_handle h, } /*! Handle output data to api_operations_post - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] xret XML reply messages from backend/handler * @param[in] yspec Yang top-level specification * @param[in] youtput Yang rpc output specification @@ -575,7 +618,7 @@ api_operations_post_input(clicon_handle h, */ static int api_operations_post_output(clicon_handle h, - FCGX_Request *r, + void *req, cxobj *xret, yang_stmt *yspec, yang_stmt *youtput, @@ -605,7 +648,7 @@ api_operations_post_output(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto fail; } @@ -638,7 +681,7 @@ api_operations_post_output(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto fail; } @@ -659,9 +702,8 @@ api_operations_post_output(clicon_handle h, 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"); + if (restconf_reply_send(req, 204, NULL) < 0) + goto done; goto fail; } /* Clear namespace of parameters */ @@ -687,12 +729,12 @@ api_operations_post_output(clicon_handle h, } /*! REST operation POST method - * @param[in] h CLIXON handle - * @param[in] r Fastcgi request handle + * @param[in] h Clixon handle + * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] data Stream input data - * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * See RFC 8040 Sec 3.6 / 4.4.2 * @note We map post to edit-config create. @@ -717,7 +759,7 @@ api_operations_post_output(clicon_handle h, */ int api_operations_post(clicon_handle h, - FCGX_Request *r, + void *req, char *api_path, int pi, cvec *qvec, @@ -766,7 +808,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -785,7 +827,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -796,7 +838,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -821,7 +863,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto done; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -834,7 +876,7 @@ api_operations_post(clicon_handle h, namespace = xml_find_type_value(xbot, NULL, "xmlns", CX_ATTR); clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data); if (data && strlen(data)){ - if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot, + if ((ret = api_operations_post_input(h, req, data, yspec, yrpc, xbot, pretty, media_out)) < 0) goto done; if (ret == 0) @@ -854,7 +896,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto ok; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -866,7 +908,7 @@ api_operations_post(clicon_handle h, clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); goto ok; } - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -884,14 +926,14 @@ api_operations_post(clicon_handle h, /* Look for local (client-side) restconf plugins. * -1:Error, 0:OK local, 1:OK backend */ - if ((ret = rpc_callback_call(h, xbot, cbret, r)) < 0) + if ((ret = rpc_callback_call(h, xbot, cbret, req)) < 0) goto done; if (ret > 0){ /* Handled locally */ if (clixon_xml_parse_string(cbuf_get(cbret), YB_NONE, NULL, &xret, NULL) < 0) goto done; /* Local error: return it and quit */ if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -900,7 +942,7 @@ api_operations_post(clicon_handle h, if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) + if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) goto done; goto ok; } @@ -913,16 +955,14 @@ api_operations_post(clicon_handle h, clicon_log_xml(LOG_DEBUG, xret, "%s Receive reply:", __FUNCTION__); #endif youtput = yang_find(yrpc, Y_OUTPUT, NULL); - if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace, + if ((ret = api_operations_post_output(h, req, xret, yspec, youtput, namespace, pretty, media_out, &xoutput)) < 0) goto done; if (ret == 0) goto ok; /* xoutput should now look: 0 */ - FCGX_SetExitStatus(200, r->out); /* OK */ - - FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); - FCGX_FPrintF(r->out, "\r\n"); + if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) + goto done; cbuf_reset(cbret); switch (media_out){ case YANG_DATA_XML: @@ -938,8 +978,8 @@ api_operations_post(clicon_handle h, default: break; } - FCGX_FPrintF(r->out, "%s", cbuf_get(cbret)); - FCGX_FPrintF(r->out, "\r\n\r\n"); + if (restconf_reply_send(req, 200, cbret) < 0) + goto done; ok: retval = 0; done: diff --git a/apps/restconf/restconf_methods_post.h b/apps/restconf/restconf_methods_post.h index 923e66fa..b8d8bd83 100644 --- a/apps/restconf/restconf_methods_post.h +++ b/apps/restconf/restconf_methods_post.h @@ -3,6 +3,7 @@ ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. @@ -34,22 +35,19 @@ * Restconf method implementation for post: operation(rpc) and data */ - #ifndef _RESTCONF_METHODS_POST_H_ #define _RESTCONF_METHODS_POST_H_ /* * Prototypes */ -int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path, - int pi, - cvec *qvec, char *data, +int api_data_post(clicon_handle h, void *req, char *api_path, + int pi, cvec *qvec, char *data, int pretty, restconf_media media_out); -int api_operations_post(clicon_handle h, FCGX_Request *r, char *api_path, +int api_operations_post(clicon_handle h, void *req, char *api_path, int pi, cvec *qvec, char *data, int pretty, restconf_media media_out); - #endif /* _RESTCONF_METHODS_POST_H_ */ diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index 56640608..9c9cb27b 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -63,13 +63,13 @@ /* restconf */ #include "restconf_lib.h" #include "restconf_api.h" +#include "restconf_err.h" #include "restconf_root.h" /*! Determine the root of the RESTCONF API * @param[in] h Clicon handle * @param[in] req Generic Www handle (can be part of clixon handle) - * @param[in] cb Body buffer * @see RFC8040 3.1 and RFC7320 * In line with the best practices defined by [RFC7320], RESTCONF * enables deployments to specify where the RESTCONF API is located. @@ -92,9 +92,10 @@ api_well_known(clicon_handle h, restconf_method_notallowed(req, "GET"); goto ok; } - restconf_reply_status_code(req, 200); /* OK */ - restconf_reply_header_add(req, "Cache-Control", "no-cache"); - restconf_reply_header_add(req, "Content-Type", "application/xrd+xml"); + if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) + goto done; + if (restconf_reply_header(req, "Content-Type", "application/xrd+xml") < 0) + goto done; /* Create body */ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); @@ -105,8 +106,9 @@ api_well_known(clicon_handle h, cprintf(cb, "\r\n"); /* Must be after body */ - restconf_reply_header_add(req, "Content-Length", "%d", cbuf_len(cb)); - if (restconf_reply_send(req, cb) < 0) + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, 200, cb) < 0) goto done; ok: retval = 0; @@ -116,3 +118,461 @@ api_well_known(clicon_handle h, return retval; } +/*! Retrieve the Top-Level API Resource + * @param[in] h Clicon handle + * @param[in] r Fastcgi request handle + * @note Only returns null for operations and data,... + * See RFC8040 3.3 + * XXX doesnt check method + */ +static int +api_root(clicon_handle h, + void *req, + char *request_method, + int pretty, + restconf_media media_out) + +{ + int retval = -1; + yang_stmt *yspec; + cxobj *xt = NULL; + cbuf *cb = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if (strcmp(request_method, "GET") != 0){ + restconf_method_notallowed(req, "GET"); + goto ok; + } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) + goto done; + if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) + goto done; + + if (clixon_xml_parse_string("" + "2016-06-21", + YB_MODULE, yspec, &xt, NULL) < 0) + goto done; + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + switch (media_out){ + case YANG_DATA_XML: + if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0) + goto done; + break; + case YANG_DATA_JSON: + if (xml2json_cbuf(cb, xt, pretty) < 0) + goto done; + break; + default: + break; + } + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, 200, cb) < 0) + goto done; + ok: + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (xt) + xml_free(xt); + return retval; +} + +/*! + * See https://tools.ietf.org/html/rfc7895 + */ +static int +api_yang_library_version(clicon_handle h, + void *req, + int pretty, + restconf_media media_out) + +{ +#if 1 + clicon_debug(1, "%s", __FUNCTION__); + return 0; +#else + int retval = -1; + cxobj *xt = NULL; + cbuf *cb = NULL; + char *ietf_yang_library_revision = "2016-06-21"; /* XXX */ + + clicon_debug(1, "%s", __FUNCTION__); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); + FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out)); + FCGX_FPrintF(r->out, "\r\n"); + if (clixon_xml_parse_va(YB_NONE, NULL, &xt, NULL, + "%s", + ietf_yang_library_revision) < 0) + goto done; + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + if ((cb = cbuf_new()) == NULL){ + goto done; + } + switch (media_out){ + case YANG_DATA_XML: + if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0) + goto done; + break; + case YANG_DATA_JSON: + if (xml2json_cbuf(cb, xt, pretty) < 0) + goto done; + break; + default: + break; + } + clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb)); + FCGX_FPrintF(r->out, "%s\n", cb?cbuf_get(cb):""); + FCGX_FPrintF(r->out, "\n\n"); + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (xt) + xml_free(xt); + return retval; +#endif +} + +/*! Generic REST method, GET, PUT, DELETE, etc + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_in Input media + * @param[in] media_out Output media + */ +static int +api_data(clicon_handle h, + void *req, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + int pretty, + restconf_media media_out) +{ +#if 1 + clicon_debug(1, "%s", __FUNCTION__); + return 0; +#else + int retval = -1; + char *request_method; + + clicon_debug(1, "%s", __FUNCTION__); + request_method = clixon_restconf_param_get(h, "REQUEST_METHOD"); + clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); + if (strcmp(request_method, "OPTIONS")==0) + retval = api_data_options(h, req); + else if (strcmp(request_method, "HEAD")==0) + retval = api_data_head(h, req, api_path, pcvec, pi, qvec, pretty, media_out); + else if (strcmp(request_method, "GET")==0) + retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out); + else if (strcmp(request_method, "POST")==0) + retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out); + else if (strcmp(request_method, "PUT")==0) + retval = api_data_put(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out); + else if (strcmp(request_method, "PATCH")==0) + retval = api_data_patch(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out); + else if (strcmp(request_method, "DELETE")==0) + retval = api_data_delete(h, req, api_path, pi, pretty, media_out); + else + retval = restconf_notfound(h, req); + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + return retval; +#endif +} + +/*! Move back to restconf_methods_get + */ +static int +api_operations_get(clicon_handle h, + void *req, + char *path, + int pi, + cvec *qvec, + char *data, + int pretty, + restconf_media media_out) +{ + int retval = -1; + yang_stmt *yspec; + yang_stmt *ymod; /* yang module */ + yang_stmt *yc; + char *namespace; + cbuf *cb = NULL; + cxobj *xt = NULL; + int i; + + clicon_debug(1, "%s", __FUNCTION__); + yspec = clicon_dbspec_yang(h); + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + switch (media_out){ + case YANG_DATA_XML: + cprintf(cb, ""); + break; + case YANG_DATA_JSON: + if (pretty) + cprintf(cb, "{\"operations\": {\n"); + else + cprintf(cb, "{\"operations\":{"); + break; + default: + break; + } + ymod = NULL; + i = 0; + while ((ymod = yn_each(yspec, ymod)) != NULL) { + namespace = yang_find_mynamespace(ymod); + yc = NULL; + while ((yc = yn_each(ymod, yc)) != NULL) { + if (yang_keyword_get(yc) != Y_RPC) + continue; + switch (media_out){ + case YANG_DATA_XML: + cprintf(cb, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace); + break; + case YANG_DATA_JSON: + if (i++){ + cprintf(cb, ","); + if (pretty) + cprintf(cb, "\n\t"); + } + if (pretty) + cprintf(cb, "\"%s:%s\": [null]", yang_argument_get(ymod), yang_argument_get(yc)); + else + cprintf(cb, "\"%s:%s\":[null]", yang_argument_get(ymod), yang_argument_get(yc)); + break; + default: + break; + } + } + } + switch (media_out){ + case YANG_DATA_XML: + cprintf(cb, ""); + break; + case YANG_DATA_JSON: + if (pretty) + cprintf(cb, "}\n}"); + else + cprintf(cb, "}}"); + break; + default: + break; + } + if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) + goto done; + if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0) + goto done; + if (restconf_reply_send(req, 200, cb) < 0) + goto done; + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); + if (xt) + xml_free(xt); + return retval; +} + +/*! Operations REST method, POST + * @param[in] h CLIXON handle + * @param[in] req Generic Www handle (can be part of clixon handle) + * @param[in] request_method eg GET,... + * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] media_out Output media + */ +static int +api_operations(clicon_handle h, + void *req, + char *request_method, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + int pretty, + restconf_media media_out) +{ + int retval; + + clicon_debug(1, "%s", __FUNCTION__); + if (strcmp(request_method, "GET")==0) + retval = api_operations_get(h, req, path, pi, qvec, data, pretty, media_out); +#ifdef NYI + else if (strcmp(request_method, "POST")==0) + retval = api_operations_post(h, req, path, pi, qvec, data, + pretty, media_out); +#endif + else + retval = restconf_notfound(h, req); + return retval; +} + +/*! Process a /restconf root input, this is the root of the restconf processing + * @param[in] h Clicon handle + * @param[in] req Generic Www handle (can be part of clixon handle) + * @param[in] qvec Query parameters, ie the ?=&= stuff + */ +int +api_root_restconf(clicon_handle h, + void *req, + cvec *qvec) +{ + int retval = -1; + char *request_method = NULL; /* GET,.. */ + char *api_resource = NULL; /* RFC8040 3.3: eg data/operations */ + char *path; + char **pvec = NULL; + cvec *pcvec = NULL; /* for rest api */ + int pn; + int pretty; + cbuf *cb = NULL; + char *media_str = NULL; + restconf_media media_out = YANG_DATA_JSON; + char *indata = NULL; + int authenticated = 0; + cxobj *xret = NULL; + cxobj *xerr; + + clicon_debug(1, "%s", __FUNCTION__); + if (req == NULL){ + errno = EINVAL; + goto done; + } + request_method = clixon_restconf_param_get(h, "REQUEST_METHOD"); + path = restconf_uripath(h); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + /* Get media for output (proactive negotiation) RFC7231 by using + * Accept:. This is for methods that have output, such as GET, + * operation POST, etc + * If accept is * default is yang-json + */ + if ((media_str = clixon_restconf_param_get(h, "HTTP_ACCEPT")) == NULL){ + // retval = restconf_unsupported_media(r); + // goto done; + } + else if ((int)(media_out = restconf_media_str2int(media_str)) == -1){ + if (strcmp(media_str, "*/*") == 0) /* catch-all */ + media_out = YANG_DATA_JSON; + else{ + retval = restconf_unsupported_media(req); + goto done; + } + } + + + clicon_debug(1, "%s ACCEPT: %s %s", __FUNCTION__, media_str, restconf_media_int2str(media_out)); + + if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) + goto done; + /* Sanity check of path. Should be /restconf/ */ + if (pn < 2){ + restconf_notfound(h, req); + goto ok; + } + if (strlen(pvec[0]) != 0){ + retval = restconf_notfound(h, req); + goto done; + } + if (strcmp(pvec[1], RESTCONF_API)){ + retval = restconf_notfound(h, req); + goto done; + } + if (pn == 2){ + retval = api_root(h, req, request_method, pretty, media_out); + goto done; + } + if ((api_resource = pvec[2]) == NULL){ + retval = restconf_notfound(h, req); + goto done; + } + clicon_debug(1, "%s: api_resource=%s", __FUNCTION__, api_resource); + if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ + goto done; + /* data */ + if ((cb = restconf_get_indata(req)) == NULL) /* XXX NYI ACTUALLY not always needed, do this later? */ + goto done; + indata = cbuf_get(cb); + clicon_debug(1, "%s DATA=%s", __FUNCTION__, indata); + + /* If present, check credentials. See "plugin_credentials" in plugin + * See RFC 8040 section 2.5 + */ + if ((authenticated = clixon_plugin_auth_all(h, req)) < 0) + goto done; + clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); + + /* If set but no user, set a dummy user */ + if (authenticated){ + if (clicon_username_get(h) == NULL) + clicon_username_set(h, "none"); + } + else{ + if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) + goto done; + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ + if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0) + goto done; + goto ok; + } + goto ok; + } + clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); + if (strcmp(api_resource, "yang-library-version")==0){ + if (api_yang_library_version(h, req, pretty, media_out) < 0) /* XXX NYI */ + goto done; + } + else if (strcmp(api_resource, "data") == 0){ /* restconf, skip /api/data */ /* XXX NYI */ + if (api_data(h, req, path, pcvec, 2, qvec, indata, + pretty, media_out) < 0) + goto done; + } + else if (strcmp(api_resource, "operations") == 0){ /* rpc */ + if (api_operations(h, req, request_method, path, pcvec, 2, qvec, indata, + pretty, media_out) < 0) + goto done; + } + else + restconf_notfound(h, req); + ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (pcvec) + cvec_free(pcvec); + if (pvec) + free(pvec); + if (cb) + cbuf_free(cb); + if (xret) + xml_free(xret); + return retval; +} + diff --git a/apps/restconf/restconf_root.h b/apps/restconf/restconf_root.h index d7a06160..10010483 100644 --- a/apps/restconf/restconf_root.h +++ b/apps/restconf/restconf_root.h @@ -53,5 +53,6 @@ * Prototypes */ int api_well_known(clicon_handle h, void *req); +int api_root_restconf(clicon_handle h, void *req, cvec *qvec); #endif /* _RESTCONF_ROOT_H_ */ diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 2c7e2b36..c6bc72ed 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -86,7 +86,8 @@ #include /* Need to be after clixon_xml.h due to attribute format */ #include "restconf_lib.h" -#include "restconf_fcgi_lib.h" +#include "restconf_api.h" +#include "restconf_err.h" #include "restconf_stream.h" /* @@ -377,7 +378,6 @@ api_stream(clicon_handle h, path = restconf_uripath(h); query = clixon_restconf_param_get(h, "QUERY_STRING"); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - restconf_test(r, 1); if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) goto done; /* Sanity check of path. Should be /stream/ */ @@ -404,7 +404,7 @@ api_stream(clicon_handle h, if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ goto done; /* data */ - if ((cb = readdata(r)) == NULL) + if ((cb = restconf_get_indata(r)) == NULL) goto done; data = cbuf_get(cb); clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); diff --git a/test/lib.sh b/test/lib.sh index 26b6746b..d725062b 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -3,7 +3,7 @@ # Create working dir as variable "dir" # The functions are somewhat wildgrown, a little too many: # - expectfn -# - expecteq +# - expectpart # - expecteof # - expecteofeq # - expecteofx @@ -286,7 +286,7 @@ new(){ # Example: expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "line1" "line2" # XXX: for some reason some curl commands dont work here, eg # curl -H 'Accept: application/xrd+xml' -# instead use expectpart +# NOTE: Please us expectpart instead!! expectfn(){ cmd=$1 retval=$2 @@ -336,34 +336,6 @@ expectfn(){ } # Evaluate and return -# Example: expecteq $(fn arg) 0 "my return" -# - evaluated expression -# - expected command return value (0 if OK) -# - expected stdout outcome -expecteq(){ - r=$? - ret=$1 - retval=$2 - expect=$3 -# echo "r:$r" -# echo "ret:\"$ret\"" -# echo "retval:$retval" -# echo "expect:$expect" - if [ $r != $retval ]; then - echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:" - echo -e "\e[0m:" - exit -1 - fi - if [ -z "$ret" -a -z "$expect" ]; then - return - fi - if [[ "$ret" != "$expect" ]]; then - err "$expect" "$ret" - fi -} - -# Evaluate and return -# like expecteq but partial match is OK # Example: expectpart $(fn arg) 0 "my return" -- "foo" # - evaluated expression # - expected command return value (0 if OK) diff --git a/test/test_api_path.sh b/test/test_api_path.sh index 3c19f9e8..97ceccb0 100755 --- a/test/test_api_path.sh +++ b/test/test_api_path.sh @@ -207,7 +207,7 @@ new "api-path double string key k1=a$rnd, - empty k2 string" expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /moda:x3/y=a1,)" 0 "0: a1foo1" new "api-path double string key k1=a$rnd, - no k2 string - three matches" -expecteq "$($clixon_util_path -f $xml3 -y $ydir -p /moda:x3/y=a1)" 0 "0: a1foo1 +expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /moda:x3/y=a1)" 0 "0: a1foo1 1: a1a1foo1 2: a1b1foob1" diff --git a/test/test_choice.sh b/test/test_choice.sh index a75a7bfd..32f4aed9 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -170,20 +170,19 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^a1foo1" new "instance-id double string key k1=a$rnd, - no k2 string - three matches" - expecteq "$($clixon_util_path -f $xml3 -y $ydir -p /a:x3/a:y[k1=\"a1\"])" 0 "0: a1foo1 + expectpart "$($clixon_util_path -f $xml3 -y $ydir -p /a:x3/a:y[k1=\"a1\"])" 0 "0: a1foo1 1: a1a1foo1 2: a1b1foob1" diff --git a/test/test_nacm.sh b/test/test_nacm.sh index fb90a352..e15bc0fe 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -137,11 +137,11 @@ if [ $RC -ne 0 ]; then fi new "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}} ' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 404 Not Found" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' # explicitly disable nacm (regression on netgate bug) new "disable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" +expectpart "$(curl -u andy:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 201 Created" new "auth set authentication config" expecteof "$clixon_netconf -qf $cfg" 0 "$RULES]]>]]>" "^]]>]]>$" @@ -150,39 +150,36 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "auth get (no user: access denied)" -expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' +expectpart "$(curl -sik -X GET -H \"Accept:\ application/yang-data+json\" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' new "auth get (wrong passwd: access denied)" -expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' +expectpart "$(curl -u andy:foo -sik -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' new "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} - ' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' #----------------Enable NACM new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" +expectpart "$(curl -u andy:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" new "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} - ' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' new "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} - ' +expectpart "$(curl -u wilma:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' new "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET 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"}}} ' +expectpart "$(curl -u guest:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":1}' http://localhost/restconf/data/nacm-example:x)" 0 "" +expectpart "$(curl -u andy:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":1}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 204 No Content" new "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -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"}}} ' +expectpart "$(curl -u wilma:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"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 -H "Content-Type: application/yang-data+json" -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"}}} ' +expectpart "$(curl -u guest:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh index e874bb88..41226f6d 100755 --- a/test/test_nacm_default.sh +++ b/test/test_nacm_default.sh @@ -135,50 +135,57 @@ EOF # Use POST (instead of startup) if [ $db = init ]; then new "Set Initial data using POST" - expectpart "$(curl -u guest:bar -siS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" + expectpart "$(curl -u guest:bar -sik -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" new "Set NACM using POST" - expectpart "$(curl -u guest:bar -siS -X POST -H "Content-Type: application/yang-data+xml" -d "$NACM" http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" + expectpart "$(curl -u guest:bar -sik -X POST -H "Content-Type: application/yang-data+xml" -d "$NACM" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" fi #----------- First get case "$ret1" in - 0) ret='{"nacm-example:x":42} - ' + 0) ret='{"nacm-example:x":42}' + status="HTTP/1.1 200 OK" ;; - 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' + 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' + status="HTTP/1.1 403 Forbidden" ;; - 2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}} ' + 2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' + status="HTTP/1.1 404 Not Found" ;; esac new "get startup 42" - expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 "$ret" + expectpart "$(curl -u guest:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "$status" "$ret" #----------- Then edit case "$ret2" in 0) ret='' + status="HTTP/1.1 204 No Content" ;; - 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' + 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' + status="HTTP/1.1 403 Forbidden" ;; esac new "edit new 99" - expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 99}' http://localhost/restconf/data/nacm-example:x)" 0 "$ret" + expectpart "$(curl -u guest:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 99}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "$status" "$ret" #----------- Then second get case "$ret3" in - 0) ret='{"nacm-example:x":99} - ' + 0) ret='{"nacm-example:x":99}' + status="HTTP/1.1 200 OK" ;; - 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' + 1) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' + status="HTTP/1.1 403 Forbidden" ;; - 2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}} ' + 2) ret='{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' + status="HTTP/1.1 404 Not Found" + ;; + 3) ret='{"nacm-example:x":42}' + status="HTTP/1.1 200 OK" ;; - 3) ret='{"nacm-example:x":42} - ' esac new "get 99" - expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 "$ret" + expectpart "$(curl -u guest:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "$status" "$ret" new "Kill restconf daemon" stop_restconf diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 512db9df..f852941f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -157,40 +157,37 @@ if [ $RC -ne 0 ]; then fi new "auth get" -expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data)" 0 'HTTP/1.1 200 OK' '{"data":{"clixon-example:state":{"op":\["41","42","43"\]}' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 200 OK' '{"data":{"clixon-example:state":{"op":\["41","42","43"\]}' new "Set x to 0" -expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 0}' http://localhost/restconf/data/nacm-example:x)" 0 "" +expectpart "$(curl -u andy:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 0}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 201 Created" new "auth get (no user: access denied)" -expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' +expectpart "$(curl -sik -X GET -H \"Accept:\ application/yang-data+json\" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' new "auth get (wrong passwd: access denied)" -expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}} ' +expectpart "$(curl -u andy:foo -sik -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"access-denied","error-severity":"error","error-message":"The requested URL was unauthorized"}}}' new "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} - ' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' new "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} - ' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' new "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} - ' +expectpart "$(curl -u wilma:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' new "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET 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"}}} ' +expectpart "$(curl -u guest:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" 0 "" +expectpart "$(curl -u andy:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 1}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 204 No Content" new "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -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"}}} ' +expectpart "$(curl -u wilma:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"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 -H "Content-Type: application/yang-data+json" -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"}}} ' +expectpart "$(curl -u guest:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x": 3}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"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_protocol.sh b/test/test_nacm_protocol.sh index f1648462..bd4e392d 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -164,13 +164,12 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" +expectpart "$(curl -u andy:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" #--------------- nacm enabled new "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":0} - ' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 200 OK" '{"nacm-example:x":0}' # Rule 1: deny-kill-session new "deny-kill-session: limited fail (netconf)" @@ -187,17 +186,17 @@ new "deny-delete-config: limited fail (netconf)" expecteof "$clixon_netconf -qf $cfg -U wilma" 0 "]]>]]>" "^applicationaccess-deniederroraccess denied]]>]]>$" new "deny-delete-config: guest fail (restconf)" -expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' +expectpart "$(curl -u guest:bar -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' # In restconf delete-config is translated to edit-config which is permitted new "deny-delete-config: limited fail (restconf) ok" -expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" 0 '' +expectpart "$(curl -u wilma:bar -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" new "admin get nacm (should fail)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}} ' +expectpart "$(curl -u andy:bar -sik -X GET $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 404 Not Found" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "deny-delete-config: admin ok (restconf)" -expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" 0 '' +expectpart "$(curl -u andy:bar -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" # Here the whole config is gone so we need to start again new "auth set authentication config (restart)" @@ -207,14 +206,14 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "" +expectpart "$(curl -u andy:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" # Rule 3: permit-edit-config new "permit-edit-config: limited ok restconf" -expecteq "$(curl -u wilma:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' http://localhost/restconf/data/nacm-example:x)" 0 '' +expectpart "$(curl -u wilma:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 204 No Content" new "permit-edit-config: guest fail restconf" -expecteq "$(curl -u guest:bar -sS -X PUT -H "Content-Type: application/yang-data+json" -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"}}} ' +expectpart "$(curl -u guest:bar -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"nacm-example:x":2}' $RCPROTO://localhost/restconf/data/nacm-example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "Kill restconf daemon" stop_restconf diff --git a/test/test_perf_state.sh b/test/test_perf_state.sh index ab7a8419..8b0c701a 100755 --- a/test/test_perf_state.sh +++ b/test/test_perf_state.sh @@ -147,8 +147,7 @@ done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}' # RESTCONF get new "restconf get test single req" -expecteq "$(curl -s -X GET http://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 '{"example:interface":[{"name":"e1","type":"ex:eth","enabled":true,"status":"up"}]} - ' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 "HTTP/1.1 200 OK" '{"example:interface":\[{"name":"e1","type":"ex:eth","enabled":true,"status":"up"}\]}' new "restconf get $perfreq single reqs" #echo "curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e0" diff --git a/test/test_perf_state_only.sh b/test/test_perf_state_only.sh index 3335fdde..02a94dc8 100755 --- a/test/test_perf_state_only.sh +++ b/test/test_perf_state_only.sh @@ -139,8 +139,7 @@ done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}' # RESTCONF get #echo "curl -s -X GET http://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1" new "restconf get test single req" -time -p expecteq "$(curl -s -X GET http://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 '{"example:interface":[{"name":"e1","type":"ex:eth","status":"up"}]} - ' | awk '/real/ {print $2}' +time -p expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:interfaces/a=foo/b/interface=e1)" 0 '{"example:interface":[{"name":"e1","type":"ex:eth","status":"up"}]}' | awk '/real/ {print $2}' new "restconf get $perfreq single reqs" #echo "curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e0" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 9bb0ff5c..90725307 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -65,18 +65,18 @@ new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expectpart "$(curl -sik -X GET $RCPROTO://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" new "restconf get restconf resource. RFC 8040 3.3 (json)" -expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}' +expectpart "$(curl -sik -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}' new "restconf get restconf resource. RFC 8040 3.3 (xml)" # Get XML instead of JSON? -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '2016-06-21' +expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '2016-06-21' # Should be alphabetically ordered new "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]}}' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" -ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations) +ret=$(curl -sik -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations) expect='' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -84,10 +84,10 @@ if [ -z "$match" ]; then fi new "restconf get restconf/yang-library-version. RFC8040 3.3.3" -expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}' new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" -ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version) +ret=$(curl -sik -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version) expect="2016-06-21" match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -95,46 +95,45 @@ if [ -z "$match" ]; then fi new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}' +expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}' new "restconf options. RFC 8040 4.1" expectpart "$(curl -is -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" # -I means HEAD new "restconf HEAD. RFC 8040 4.2" -expectpart "$(curl -si -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json" +expectpart "$(curl -sik -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json" new "restconf empty rpc JSON" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" new "restconf empty rpc XML" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+xml" -d '' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+xml" -d '' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" new "restconf empty rpc, default media type should fail" -expectpart "$(curl -si -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' +expectpart "$(curl -sik -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' new "restconf empty rpc, default media type should fail (JSON)" -expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' +expectpart "$(curl -sik -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' new "restconf empty rpc with extra args (should fail)" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}' # Irritiating to get debugs on the terminal #new "restconf debug rpc" -#expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $RCPROTO://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content" +#expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $RCPROTO://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content" new "restconf get empty config + state json" -expecteq "$(curl -sS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} - ' +expectpart "$(curl -kisS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "restconf get empty config + state json with wrong module name" -expectpart "$(curl -siSG $RCPROTO://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}' +expectpart "$(curl -sikS -X GET $RCPROTO://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}' #'HTTP/1.1 404 Not Found' #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}' new "restconf get empty config + state xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state) +ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state) expect='414243' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -142,12 +141,11 @@ if [ -z "$match" ]; then fi new "restconf get data type json" -expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} - ' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}' new "restconf get state operation" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) +ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) expect='42' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -155,12 +153,11 @@ if [ -z "$match" ]; then fi new "restconf get state operation type json" -expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} - ' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}' new "restconf get state operation type xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) +ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42) expect='42' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -168,78 +165,72 @@ if [ -z "$match" ]; then fi new "restconf GET datastore" -expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} - ' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}' # Exact match new "restconf Add subtree eth/0/0 to datastore using POST" -expectpart "$(curl -s -i -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces' +expectpart "$(curl -sik -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces' new "restconf Re-add subtree eth/0/0 which should give error" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://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"}}}' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://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"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}} - ' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' new "restconf delete interfaces" -expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content" new "restconf Check empty config" -expectfn "curl -sG $RCPROTO://localhost/restconf/data/clixon-example:state" 0 "$state - " +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" "$state" new "restconf Add interfaces subtree eth/0/0 using POST" -expectpart "$(curl -si -X POST $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -sik -X POST $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created" new "restconf Check eth/0/0 added config" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' +expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' new "restconf Check eth/0/0 GET augmented state level 1" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}' +expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}' new "restconf Check eth/0/0 GET augmented state level 2" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}' +expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}' new "restconf Check eth/0/0 added state XXXXXXX" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' +expectpart "$(curl -sik -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "restconf Re-post eth/0/0 which should generate error" -expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 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 "Add leaf description using POST" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" new "Add nothing using POST (expect fail)" expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}' new "restconf Check description added" -expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} - ' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' new "restconf delete eth/0/0" -expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content" new "Check deleted eth/0/0" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 "$state" +expectpart "$(curl -sik -X GET http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "$state" new "restconf Re-Delete eth/0/0 using none should generate error" -expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} ' +expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}' new "restconf Add subtree eth/0/0 using PUT" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" new "restconf get subtree" -expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} - ' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' new "restconf rpc using POST json" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}} - ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/1.1 200 OK" '{"clixon-example:output":{"x":"42","y":"42"}}' if ! $YANG_UNKNOWN_ANYDATA ; then new "restconf rpc using POST json wrong" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}' fi new "restconf rpc non-existing rpc without namespace" @@ -255,7 +246,7 @@ new "restconf rpc missing input" expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example) +ret=$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example) expect='4242' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -263,10 +254,10 @@ if [ -z "$match" ]; then fi new "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 "HTTP/1.1 412 Precondition Failed" '{"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 -i -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $RCPROTO://localhost/restconf/operations/clixon-example:client-rpc) +ret=$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $RCPROTO://localhost/restconf/operations/clixon-example:client-rpc) expect='example' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -277,7 +268,7 @@ new "restconf Add subtree without key (expected error)" expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected' new "restconf Add subtree with too many keys (expected error)" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 6a602263..972d3246 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -94,22 +94,22 @@ if [ $RC -ne 0 ]; then fi new "restconf POST tree without key" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 400 Bad Request" '{"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 initial tree" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" new "restconf POST top without namespace" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} ' new "restconf GET datastore initial" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' new "restconf GET interface subtree" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"regular"}\]}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 200 OK" '{"example:interface":\[{"name":"local0","type":"regular"}\]}' new "restconf GET interface subtree xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0) +ret=$(curl -sik -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0) expect='local0regular' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then @@ -117,83 +117,83 @@ if [ -z "$match" ]; then fi new "restconf GET if-type" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 200 OK" '{"example:type":"regular"}' new "restconf POST interface without mandatory type" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://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"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 "HTTP/1.1 400 Bad Request" '{"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" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://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"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 "HTTP/1.1 400 Bad Request" '{"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" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created" new "restconf POST interface without namespace" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}}' new "restconf POST again" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://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"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 409 Conflict" '{"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 -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' $RCPROTO://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"}}} ' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 409 Conflict" '{"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 DELETE" -expectfn "curl -si -X DELETE $RCPROTO://localhost/restconf/data/example:cont1" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 204 No Content" new "restconf GET null datastore" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 404 Not Found" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "restconf POST initial tree" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "" +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" new "restconf GET initial tree" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' new "restconf DELETE whole datastore" -expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 "" +expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" new "restconf GET null datastore" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 404 Not Found" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "restconf PUT initial datastore" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" new "restconf GET datastore" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' new "restconf PUT replace datastore" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" new "restconf GET replaced datastore" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont2)" 0 "HTTP/1.1 200 OK" '{"example:cont2":{"name":"foo"}}' new "restconf PUT initial datastore again" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" new "restconf PUT change interface" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content" new "restconf GET datastore atm" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 200 OK" '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}' new "restconf PUT add interface" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created" +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created" new "restconf PUT change key error" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"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)" -expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content" +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content" new "restconf GET datastore eth" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 200 OK" '{"example:interface":\[{"name":"local0","type":"eth0"}\]}' #--------------- json type tests -new "restconf POST type x3" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' $RCPROTO://localhost/restconf/data)" 0 '' +new "restconf POST type x3 POST" +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example:types" -new "restconf POST type x3" -expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:types" 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' +new "restconf POST type x3 GET" +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example:types)" 0 "HTTP/1.1 200 OK" '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_listkey.sh b/test/test_restconf_listkey.sh index 1150d599..2a73d544 100755 --- a/test/test_restconf_listkey.sh +++ b/test/test_restconf_listkey.sh @@ -92,78 +92,76 @@ if [ $RC -ne 0 ]; then fi new "restconf PUT add whole list entry" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 "HTTP/1.1 201 Created" # GETs to ensure you get list [] in JSON new "restconf GET whole list entry" -expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/)" 0 '{"list:c":{"a":[{"b":"x","c":"y","nonkey":"0"}]}} - ' +expectpart "$(curl -sik -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/)" 0 "HTTP/1.1 200 OK" '{"list:c":{"a":\[{"b":"x","c":"y","nonkey":"0"}\]}}' new "restconf GET list entry itself (should fail)" expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected ' new "restconf GET list entry" -expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y)" 0 '{"list:a":[{"b":"x","c":"y","nonkey":"0"}]} - ' +expectpart "$(curl -sik -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y)" 0 "HTTP/1.1 200 OK" '{"list:a":\[{"b":"x","c":"y","nonkey":"0"}\]}' new "restconf PUT add whole list entry XML" -expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xxxy0' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 '' +expectpart "$(curl -sik -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xxxy0' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 "HTTP/1.1 201 Created" -expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a/b)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected ' +expectpart "$(curl -sik -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a/b)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected ' new "restconf PUT change whole list entry (same keys)" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 "HTTP/1.1 204 No Content" new "restconf PUT change whole list entry (no namespace)(expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} ' new "restconf PUT change list entry (wrong keys)(expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"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 list entry (wrong keys)(expect fail) XML" -expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xyxz0' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 'protocoloperation-failederrorapi-path keys do not match data keys ' +expectpart "$(curl -sik -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d 'xyxz0' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 "HTTP/1.1 412 Precondition Failed" 'protocoloperation-failederrorapi-path keys do not match data keys ' new "restconf PUT change list entry (just one key)(expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' new "restconf PUT sub non-key" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 "HTTP/1.1 204 No Content" new "restconf PUT sub key same value" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 "204 No Content" new "restconf PUT just key other value (should fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}' new "restconf PUT add leaf-list entry" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 "HTTP/1.1 201 Created" new "restconf PUT change leaf-list entry (expect fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"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 list-list" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 "HTTP/1.1 201 Created" new "restconf PUT change list-lst entry (wrong keys)(expect fail)" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonkey":"0"}}')" 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"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonkey":"0"}}')" 0 "HTTP/1.1 412 Precondition Failed" '{"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 list-list sub non-key" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 "HTTP/1.1 204 No Content" new "restconf PUT list-list single first key" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://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"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 "HTTP/1.1 400 Bad Request" '{"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" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 "HTTP/1.1 204 No Content" new "restconf PUT list-list just key just key wrong value (should fail)" -expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"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" -expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 '' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 "HTTP/1.1 201 Created" new "restconf PUT change list+leaf-list entry (expect fail)" -expectpart "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 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"}}}' +expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 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"}}}' if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_restconf_startup.sh b/test/test_restconf_startup.sh index 87480978..ab6e457c 100755 --- a/test/test_restconf_startup.sh +++ b/test/test_restconf_startup.sh @@ -74,16 +74,16 @@ testrun(){ wait_restconf new "restconf put 42" - expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 "" + expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 "HTTP/1.1 201 Created" new "restconf put 99" - expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 "" + expectpart "$(curl -sik -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 "HTTP/1.1 201 Created" new "restconf post 123" - expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "" + expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "HTTP/1.1 201 Created" new "restconf delete 42" - expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 "" + expectpart "$(curl -sik -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 "HTTP/1.1 204 No Content" new "Kill restconf daemon" stop_restconf diff --git a/test/test_submodule.sh b/test/test_submodule.sh index 8350a6f5..082b3e27 100755 --- a/test/test_submodule.sh +++ b/test/test_submodule.sh @@ -205,16 +205,16 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^< # Now same with restconf new "restconf edit main" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:main":{"x":"foo","ext":"foo"}}')" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"main:main":{"x":"foo","ext":"foo"}}')" 0 'HTTP/1.1 201 Created' new "restconf edit sub1" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:sub1":{"x":"foo","ext1":"foo"}}')" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"main:sub1":{"x":"foo","ext1":"foo"}}')" 0 'HTTP/1.1 201 Created' new "restconf edit sub2" -expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"main:sub2":{"x":"foo","ext2":"foo"}}')" 0 'HTTP/1.1 201 Created' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"main:sub2":{"x":"foo","ext2":"foo"}}')" 0 'HTTP/1.1 201 Created' new "restconf check main/sub1/sub2 contents" -expectpart "$(curl -si -X GET http://localhost/restconf/data?content=config)" 0 'HTTP/1.1 200 OK' '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}' +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data?content=config)" 0 'HTTP/1.1 200 OK' '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}' new "Kill restconf daemon" stop_restconf diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index f56d0b8e..6047a9d1 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -103,24 +103,23 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "restconf set x in example1" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example1:x":42}' http://localhost/restconf/data)" 0 '' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example1:x":42}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" new "restconf get config example1" -expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42} +expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/1.1 200 OK" '{"example1:x":42} ' new "restconf set x in example2" -expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example2:x":{"y":99}}' http://localhost/restconf/data)" 0 '' +expectpart "$(curl -sik -X POST -H "Content-Type: application/yang-data+json" -d '{"example2:x":{"y":99}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" # XXX GET ../example1:x is translated to select=/x which gets both example1&2 #new "restconf get config example1" -#expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" 0 '{"example1:x":42} -# ' +#expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example1:x)" 0 "HTTP/1.1 200 OK" '{"example1:x":42}' + # XXX GET ../example2:x is translated to select=/x which gets both example1&2 #new "restconf get config example2" -#expecteq "$(curl -s -X GET http://localhost/restconf/data/example2:x)" 0 '{"example2:x":{"y":42}} -# ' +#expectpart "$(curl -sik -X GET $RCPROTO://localhost/restconf/data/example2:x)" 0 "HTTP/1.1 200 OK" '{"example2:x":{"y":42}}' new "restconf get config example1 and example2" ret=$(curl -s -X GET http://localhost/restconf/data)