* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:

* `201 Created` for created resources
  * `204 No Content` for replaced resources.
  * See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)

* HTTP `Location:` fields added in RESTCONF POST replies
This commit is contained in:
Olof hagsand 2019-07-25 13:15:31 +02:00
parent 291f173505
commit c1bae276ff
27 changed files with 1734 additions and 1298 deletions

View file

@ -4,13 +4,23 @@
### Major New features ### Major New features
* Restconf RFC 8040 increased feature compliance * Restconf RFC 8040 increased feature compliance
* Cache-Control: no-cache added in HTTP responses (RFC Section 5.5) * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources
* `204 No Content` for replaced resources.
* See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
* Implementation detail: due to difference between RESTCONF and NETCONF semantics, a PUT first to make en internal netconf edit-config create operation; if that fails, a replace operation is tried.
* HTTP `Location:` fields added in RESTCONF POST replies
* HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5)
* Restconf monitoring capabilities (RFC Section 9.1) * Restconf monitoring capabilities (RFC Section 9.1)
* Added support for Yang extensions *
* Yang extensions support
* New plugin callback: ca_extension * New plugin callback: ca_extension
* Main backend example includes example code on how to implement a Yang extension in a plugin. * Main backend example includes example code on how to implement a Yang extension in a plugin.
### API changes on existing features (you may need to change your code) ### API changes on existing features (you may need to change your code)
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources
* `204 No Content` for replaced resources.
* JSON changes * JSON changes
* Non-pretty-print output removed all extra spaces. * Non-pretty-print output removed all extra spaces.
* Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}` * Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}`
@ -23,6 +33,7 @@
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions * pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
### Corrected Bugs ### Corrected Bugs
* See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
* Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error. * Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error.
## 4.0.0 (13 July 2019) ## 4.0.0 (13 July 2019)

View file

@ -241,6 +241,7 @@ The following features are supported:
- query parameters start-time and stop-time(RFC8040 section 4.9) - query parameters start-time and stop-time(RFC8040 section 4.9)
The following features are not implemented: The following features are not implemented:
- ETag/Last-Modified
- PATCH - PATCH
- query parameters other than start/stop-time. - query parameters other than start/stop-time.

View file

@ -76,6 +76,8 @@ APPL = clixon_restconf
# Not accessible from plugin # Not accessible from plugin
APPSRC = restconf_main.c APPSRC = restconf_main.c
APPSRC += restconf_methods.c APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c
APPSRC += restconf_stream.c APPSRC += restconf_stream.c
APPOBJ = $(APPSRC:.c=.o) APPOBJ = $(APPSRC:.c=.o)

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.
@ -51,7 +51,7 @@ int notfound(FCGX_Request *r);
int conflict(FCGX_Request *r); int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r); int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r); int notimplemented(FCGX_Request *r);
int test(FCGX_Request *r, int dbg); int restconf_test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r); cbuf *readdata(FCGX_Request *r);
int get_user_cookie(char *cookiestr, char *attribute, char **val); int get_user_cookie(char *cookiestr, char *attribute, char **val);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.
@ -31,6 +31,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/ */
#include <stdlib.h> #include <stdlib.h>
@ -302,11 +303,11 @@ printparam(FCGX_Request *r,
return 0; return 0;
} }
/*! /*! Print all FCGI headers
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
*/ */
int int
test(FCGX_Request *r, restconf_test(FCGX_Request *r,
int dbg) int dbg)
{ {
printparam(r, "QUERY_STRING", dbg); printparam(r, "QUERY_STRING", dbg);
@ -328,6 +329,7 @@ test(FCGX_Request *r,
printparam(r, "SERVER_NAME", dbg); printparam(r, "SERVER_NAME", dbg);
printparam(r, "HTTP_COOKIE", dbg); printparam(r, "HTTP_COOKIE", dbg);
printparam(r, "HTTPS", dbg); printparam(r, "HTTPS", dbg);
printparam(r, "HTTP_HOST", dbg);
printparam(r, "HTTP_ACCEPT", dbg); printparam(r, "HTTP_ACCEPT", dbg);
printparam(r, "HTTP_CONTENT_TYPE", dbg); printparam(r, "HTTP_CONTENT_TYPE", dbg);
printparam(r, "HTTP_AUTHORIZATION", dbg); printparam(r, "HTTP_AUTHORIZATION", dbg);
@ -478,6 +480,50 @@ api_return_err(clicon_handle h,
return retval; return retval;
} }
/*! Print location header from FCGI environment
* @param[in] r 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(FCGX_Request *r,
cxobj *xobj)
{
int retval = -1;
char *https;
char *host;
char *request_uri;
cbuf *cb = NULL;
https = FCGX_GetParam("HTTPS", r->envp);
host = FCGX_GetParam("HTTP_HOST", r->envp);
request_uri = FCGX_GetParam("REQUEST_URI", r->envp);
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(r->out, "Location: http%s://%s%s%s\r\n",
https?"s":"",
host,
request_uri,
cbuf_get(cb));
}
else
FCGX_FPrintF(r->out, "Location: http%s://%s%s\r\n",
https?"s":"",
host,
request_uri);
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Clean and close all state of restconf process (but dont exit). /*! Clean and close all state of restconf process (but dont exit).
* Cannot use h after this * Cannot use h after this
* @param[in] h Clixon handle * @param[in] h Clixon handle

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.
@ -56,11 +56,12 @@ int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r); int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r); int notimplemented(FCGX_Request *r);
int test(FCGX_Request *r, int dbg); int restconf_test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r); cbuf *readdata(FCGX_Request *r);
int get_user_cookie(char *cookiestr, char *attribute, char **val); int get_user_cookie(char *cookiestr, char *attribute, char **val);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
int pretty, int use_xml, int code); int pretty, int use_xml, int code);
int http_location(FCGX_Request *r, cxobj *xobj);
int restconf_terminate(clicon_handle h); int restconf_terminate(clicon_handle h);
#endif /* _RESTCONF_LIB_H_ */ #endif /* _RESTCONF_LIB_H_ */

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.
@ -78,6 +78,8 @@
/* restconf */ /* restconf */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_methods.h" #include "restconf_methods.h"
#include "restconf_methods_post.h"
#include "restconf_methods_get.h"
#include "restconf_stream.h" #include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
@ -368,7 +370,7 @@ api_restconf(clicon_handle h,
retval = notfound(r); retval = notfound(r);
goto done; goto done;
} }
test(r, 1); restconf_test(r, 1);
if (pn == 2){ if (pn == 2){
retval = api_root(h, r); retval = api_root(h, r);
@ -429,7 +431,7 @@ api_restconf(clicon_handle h,
goto done; goto done;
} }
else if (strcmp(method, "test") == 0) else if (strcmp(method, "test") == 0)
test(r, 0); restconf_test(r, 0);
else else
notfound(r); notfound(r);
ok: ok:

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.
@ -31,28 +31,17 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* Restconf method implementation
*/ */
#ifndef _RESTCONF_METHODS_H_ #ifndef _RESTCONF_METHODS_H_
#define _RESTCONF_METHODS_H_ #define _RESTCONF_METHODS_H_
/*
* Constants
*/
/* /*
* Prototypes * Prototypes
*/ */
int api_data_options(clicon_handle h, FCGX_Request *r); int api_data_options(clicon_handle h, FCGX_Request *r);
int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
cvec *qvec, int pretty, int use_xml);
int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
cvec *qvec, int pretty, int use_xml);
int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data,
int pretty, int use_xml, int parse_xml);
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi, cvec *pcvec, int pi,
cvec *qvec, char *data, cvec *qvec, char *data,
@ -63,14 +52,4 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
int pretty, int use_xml); int pretty, int use_xml);
int api_operations_get(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data,
int pretty, int use_xml);
int api_operations_post(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data,
int pretty, int use_xml, int parse_xml);
#endif /* _RESTCONF_METHODS_H_ */ #endif /* _RESTCONF_METHODS_H_ */

View file

@ -0,0 +1,421 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
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 *****
* Restconf method implementation for operations get and data get and head
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <assert.h>
#include <time.h>
#include <signal.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_methods.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] 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] use_xml Set to 0 for JSON and 1 for XML
* @param[in] head If 1 is HEAD, otherwise GET
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* See RFC8040 Sec 4.2 and 4.3
* XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json.
* Request may contain
* Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of:
* Content-Type: application/yang-data+xml
* Content-Type: application/yang-data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a
* "400 Bad Request" status-line MUST be returned by the server.
* Netconf: <get-config>, <get>
*/
static int
api_data_get2(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int pretty,
int use_xml,
int head)
{
int retval = -1;
cbuf *cbpath = NULL;
char *xpath = NULL;
cbuf *cbx = NULL;
yang_stmt *yspec;
cxobj *xret = NULL;
cxobj *xerr = NULL; /* malloced */
cxobj *xe = NULL; /* not malloced */
cxobj **xvec = NULL;
size_t xlen;
int i;
cxobj *x;
int ret;
char *namespace = NULL;
cvec *nsc = NULL;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((cbpath = cbuf_new()) == NULL)
goto done;
cprintf(cbpath, "/");
/* We know "data" is element pi-1 */
if ((ret = api_path2xpath_cvv(pcvec, pi, yspec, cbpath, &namespace)) < 0)
goto done;
if (ret == 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
xpath = cbuf_get(cbpath);
clicon_debug(1, "%s path:%s", __FUNCTION__, xpath);
/* Create a namespace context for ymod as the default namespace to use with
* xpath expressions */
if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
goto done;
if (clicon_rpc_get(h, xpath, namespace, &xret) < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
if (xml_apply(xret, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* We get return via netconf which is complete tree from root
* We need to cut that tree to only the object.
*/
#if 0 /* DEBUG */
if (debug){
cbuf *cb = cbuf_new();
clicon_xml2cbuf(cb, xret, 0, 0);
clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb));
cbuf_free(cb);
}
#endif
/* Check if error return */
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
/* Normal return, no error */
if ((cbx = cbuf_new()) == NULL)
goto done;
if (head){
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
goto ok;
}
if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
if (use_xml){
if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */
goto done;
}
else{
if (xml2json_cbuf(cbx, xret, pretty) < 0)
goto done;
}
}
else{
if (xpath_vec_nsc(xret, nsc, "%s", &xvec, &xlen, xpath) < 0){
if (netconf_operation_failed_xml(&xerr, "application", clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
/* Check if not exists */
if (xlen == 0){
/* 4.3: If a retrieval request for a data resource represents an
instance that does not exist, then an error response containing
a "404 Not Found" status-line MUST be returned by the server.
The error-tag value "invalid-value" is used in this case. */
if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0)
goto done;
/* override invalid-value default 400 with 404 */
if (api_return_err(h, r, xerr, pretty, use_xml, 404) < 0)
goto done;
goto ok;
}
if (use_xml){
for (i=0; i<xlen; i++){
char *prefix, *namespace2; /* Same as namespace? */
x = xvec[i];
/* Some complexities in grafting namespace in existing trees to new */
prefix = xml_prefix(x);
if (xml_find_type_value(x, prefix, "xmlns", CX_ATTR) == NULL){
if (xml2ns(x, prefix, &namespace2) < 0)
goto done;
if (namespace2 && xmlns_set(x, prefix, namespace2) < 0)
goto done;
}
if (clicon_xml2cbuf(cbx, x, 0, pretty) < 0) /* Dont print top object? */
goto done;
}
}
else{
/* In: <x xmlns="urn:example:clixon">0</x>
* Out: {"example:x": {"0"}}
*/
if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
goto done;
}
}
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: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (nsc)
xml_nsctx_free(nsc);
if (cbx)
cbuf_free(cbx);
if (cbpath)
cbuf_free(cbpath);
if (xret)
xml_free(xret);
if (xerr)
xml_free(xerr);
if (xvec)
free(xvec);
return retval;
}
/*! REST HEAD method
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @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] use_xml Set to 0 for JSON and 1 for XML
*
* The HEAD method is sent by the client to retrieve just the header fields
* that would be returned for the comparable GET method, without the
* response message-body.
* Relation to netconf: none
*/
int
api_data_head(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int pretty,
int use_xml)
{
return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 1);
}
/*! REST GET method
* According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @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] use_xml Set to 0 for JSON and 1 for XML
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json.
* Request may contain
* Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of:
* Content-Type: application/yang-data+xml
* Content-Type: application/yang-data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a
* "400 Bad Request" status-line MUST be returned by the server.
* Netconf: <get-config>, <get>
*/
int
api_data_get(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int pretty,
int use_xml)
{
return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 0);
}
/*! GET restconf/operations resource
* @param[in] h Clixon handle
* @param[in] r Fastcgi request 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
* @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] use_xml Set to 0 for JSON and 1 for XML
*
* @code
* curl -G http://localhost/restconf/operations
* @endcode
* RFC8040 Sec 3.3.2:
* This optional resource is a container that provides access to the
* data-model-specific RPC operations supported by the server. The
* server MAY omit this resource if no data-model-specific RPC
* operations are advertised.
* From ietf-restconf.yang:
* In XML, the YANG module namespace identifies the module:
* <system-restart xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
* In JSON, the YANG module name identifies the module:
* { 'ietf-system:system-restart' : [null] }
*/
int
api_operations_get(clicon_handle h,
FCGX_Request *r,
char *path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
int use_xml)
{
int retval = -1;
yang_stmt *yspec;
yang_stmt *ymod; /* yang module */
yang_stmt *yc;
char *namespace;
cbuf *cbx = NULL;
cxobj *xt = NULL;
int i;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((cbx = cbuf_new()) == NULL)
goto done;
if (use_xml)
cprintf(cbx, "<operations>");
else
cprintf(cbx, "{\"operations\": {");
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;
if (use_xml)
cprintf(cbx, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace);
else{
if (i++)
cprintf(cbx, ",");
cprintf(cbx, "\"%s:%s\": null", yang_argument_get(ymod), yang_argument_get(yc));
}
}
}
if (use_xml)
cprintf(cbx, "</operations>");
else
cprintf(cbx, "}}");
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
// ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
if (xt)
xml_free(xt);
return retval;
}

View file

@ -0,0 +1,53 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
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 *****
* Restconf method implementation for operations get and data get and head
*/
#ifndef _RESTCONF_METHODS_GET_H_
#define _RESTCONF_METHODS_GET_H_
/*
* Prototypes
*/
int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
cvec *qvec, int pretty, int use_xml);
int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
cvec *qvec, int pretty, int use_xml);
int api_operations_get(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data,
int pretty, int use_xml);
#endif /* _RESTCONF_METHODS_GET_H_ */

View file

@ -0,0 +1,897 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
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 *****
* Restconf method implementation for post: operation(rpc) and data
* From RFC 8040 Section 4.4. POST
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <assert.h>
#include <time.h>
#include <signal.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h"
#include "restconf_methods.h"
#include "restconf_methods_post.h"
/*! Generic REST POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request 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] use_xml Set to 0 for JSON and 1 for XML for output data
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
* restconf POST is mapped to edit-config create.
* @see RFC8040 Sec 4.4.1
POST:
target resource type is datastore --> create a top-level resource
target resource type is data resource --> create child resource
The message-body MUST contain exactly one instance of the
expected data resource. The data model for the child tree is the
subtree, as defined by YANG for the child resource.
If the POST method succeeds, a "201 Created" status-line is returned
and there is no response message-body. A "Location" header
identifying the child resource that was created MUST be present in
the response in this case.
If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned.
* @see RFC8040 Section 4.4
*/
int
api_data_post(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
int use_xml,
int parse_xml)
{
int retval = -1;
enum operation_type op = OP_CREATE;
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
cxobj *xdata; /* -d data (without top symbol)*/
int i;
cbuf *cbx = NULL;
cxobj *xtop = NULL; /* top of api-path */
cxobj *xbot = NULL; /* bottom of api-path */
yang_stmt *ybot = NULL; /* yang of xbot */
yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */
yang_stmt *ymoddata = NULL; /* yang module of data (-d) */
yang_stmt *yspec;
cxobj *xa;
cxobj *xret = NULL;
cxobj *xretcom = NULL; /* return from commit */
cxobj *xretdis = NULL; /* return from discard-changes */
cxobj *xerr = NULL; /* malloced must be freed */
cxobj *xe; /* dont free */
char *username;
int ret;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__,
api_path, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done;
/* Translate api_path to xtop/xbot */
xbot = xtop;
if (api_path){
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0)
goto done;
if (ybot)
ymodapi=ys_module(ybot);
if (ret == 0){ /* validation failed */
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
/* Parse input data as json or xml into xml */
if (parse_xml){
if (xml_parse_string(data, NULL, &xdata0) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
else {
if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
if (ret == 0){
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
/* 4.4.1: The message-body MUST contain exactly one instance of the
* expected data resource.
*/
if (xml_child_nr(xdata0) != 1){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
xdata = xml_child_i(xdata0,0);
/* If the api-path (above) defines a module, then xdata must have a prefix
* and it match the module defined in api-path.
* In a POST, maybe there are cornercases where xdata (which is a child) and
* xbot (which is the parent) may have non-matching namespaces?
* This does not apply if api-path is / (no module)
*/
if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0)
goto done;
if (ymoddata && ymodapi){
if (ymoddata != ymodapi){
if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
/* Add operation (create/replace) as attribute */
if ((xa = xml_new("operation", xdata, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
/* Replace xbot with x, ie bottom of api-path with data */
if (xml_addsub(xbot, xdata) < 0)
goto done;
/* xbot is already populated, resolve yang for added xdata too */
if (xml_spec_populate(xdata, yspec) < 0)
goto done;
/* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<edit-config><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
goto done;
cprintf(cbx, "</edit-config></rpc>");
clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done;
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
/* Assume this is validation failed since commit includes validate */
cbuf_reset(cbx);
/* commit/discard should be done automaticaly by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<discard-changes/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
goto done;
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, "//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, use_xml, 0) < 0) /* Use original xe */
goto done;
goto ok;
}
if (xretcom){ /* Clear: can be reused again below */
xml_free(xretcom);
xretcom = NULL;
}
if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
*/
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
cprintf(cbx, "<copy-config><source><running/></source><target><startup/></target></copy-config></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
/* If copy-config failed, log and ignore (already committed) */
if ((xe = xpath_first(xretcom, "//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(r, xdata);
FCGX_GetParam("HTTP_ACCEPT", r->envp);
FCGX_FPrintF(r->out, "\r\n");
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xret)
xml_free(xret);
if (xerr)
xml_free(xerr);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
if (xdata0)
xml_free(xdata0);
if (cbx)
cbuf_free(cbx);
return retval;
} /* api_data_post */
/*! Handle input data to api_operations_post
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] data Stream input data
* @param[in] yspec Yang top-level specification
* @param[in] yrpc Yang rpc spec
* @param[in] xrpc XML pointer to rpc method
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] use_xml Set to 0 for JSON and 1 for XML for output data
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
* @retval 1 OK
* @retval 0 Fail, Error message sent
* @retval -1 Fatal error, clicon_err called
*
* RFC8040 3.6.1
* If the "rpc" or "action" statement has an "input" section, then
* instances of these input parameters are encoded in the module
* namespace where the "rpc" or "action" statement is defined, in an XML
* element or JSON object named "input", which is in the module
* namespace where the "rpc" or "action" statement is defined.
* (Any other input is assumed as error.)
*/
static int
api_operations_post_input(clicon_handle h,
FCGX_Request *r,
char *data,
yang_stmt *yspec,
yang_stmt *yrpc,
cxobj *xrpc,
int pretty,
int use_xml,
int parse_xml)
{
int retval = -1;
cxobj *xdata = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
cxobj *xe;
cxobj *xinput;
cxobj *x;
cbuf *cbret = NULL;
int ret;
clicon_debug(1, "%s %s", __FUNCTION__, data);
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
/* Parse input data as json or xml into xml */
if (parse_xml){
if (xml_parse_string(data, yspec, &xdata) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto fail;
}
}
else { /* JSON */
if ((ret = json_parse_str(data, yspec, &xdata, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto fail;
}
if (ret == 0){
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto fail;
}
}
xml_name_set(xdata, "data");
/* Here xdata is:
* <data><input xmlns="urn:example:clixon">...</input></data>
*/
#if 1
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0)
goto done;
clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
/* Validate that exactly only <input> tag */
if ((xinput = xml_child_i_type(xdata, 0, CX_ELMNT)) == NULL ||
strcmp(xml_name(xinput),"input") != 0 ||
xml_child_nr_type(xdata, CX_ELMNT) != 1){
if (xml_child_nr_type(xdata, CX_ELMNT) == 0){
if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have input statement") < 0)
goto done;
}
else
if (netconf_malformed_message_xml(&xerr, "restconf RPC has malformed input statement (multiple or not called input)") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto fail;
}
// clicon_debug(1, "%s input validation passed", __FUNCTION__);
/* Add all input under <rpc>path */
x = NULL;
while ((x = xml_child_i_type(xinput, 0, CX_ELMNT)) != NULL)
if (xml_addsub(xrpc, x) < 0)
goto done;
/* Here xrpc is: <myfn xmlns="uri"><x>42</x></myfn>
*/
// ok:
retval = 1;
done:
clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
if (cbret)
cbuf_free(cbret);
if (xerr)
xml_free(xerr);
if (xdata)
xml_free(xdata);
return retval;
fail:
retval = 0;
goto done;
}
/*! Handle output data to api_operations_post
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] xret XML reply messages from backend/handler
* @param[in] yspec Yang top-level specification
* @param[in] youtput Yang rpc output specification
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] use_xml Set to 0 for JSON and 1 for XML for output data
* @param[out] xoutputp Restconf JSON/XML output
* @retval 1 OK
* @retval 0 Fail, Error message sent
* @retval -1 Fatal error, clicon_err called
* xret should like: <top><rpc-reply><x xmlns="uri">0</x></rpc-reply></top>
*/
static int
api_operations_post_output(clicon_handle h,
FCGX_Request *r,
cxobj *xret,
yang_stmt *yspec,
yang_stmt *youtput,
char *namespace,
int pretty,
int use_xml,
cxobj **xoutputp)
{
int retval = -1;
cxobj *xoutput = NULL;
cxobj *xerr = NULL; /* assumed malloced, will be freed */
cxobj *xe; /* just pointer */
cxobj *xa; /* xml attribute (xmlns) */
cxobj *x;
cxobj *xok;
int isempty;
// clicon_debug(1, "%s", __FUNCTION__);
/* Validate that exactly only <rpc-reply> tag */
if ((xoutput = xml_child_i_type(xret, 0, CX_ELMNT)) == NULL ||
strcmp(xml_name(xoutput),"rpc-reply") != 0 ||
xml_child_nr_type(xret, CX_ELMNT) != 1){
if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have single input") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto fail;
}
/* xoutput should now look: <rpc-reply><x xmlns="uri">0</x></rpc-reply> */
/* 9. Translate to restconf RPC data */
xml_name_set(xoutput, "output");
/* xoutput should now look: <output><x xmlns="uri">0</x></output> */
#if 1
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xoutput, 0, 0) < 0)
goto done;
clicon_debug(1, "%s XOUTPUT:%s", __FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
/* Sanity check of outgoing XML
* For now, skip outgoing checks.
* (1) Does not handle <ok/> properly
* (2) Uncertain how validation errors should be logged/handled
*/
if (youtput!=NULL){
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
#if 0
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if ((ret = xml_yang_validate_all(xoutput, &xerr)) < 0)
goto done;
if (ret == 1 &&
(ret = xml_yang_validate_add(h, xoutput, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if ((xe = xpath_first(xerr, "rpc-reply/rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto fail;
}
#endif
}
/* Special case, no yang output (single <ok/> - or empty body?)
* RFC 7950 7.14.4
* If the RPC operation invocation succeeded and no output parameters
* are returned, the <rpc-reply> contains a single <ok/> element
* RFC 8040 3.6.2
* If the "rpc" statement has no "output" section, the response message
* MUST NOT include a message-body and MUST send a "204 No Content"
* status-line instead.
*/
isempty = xml_child_nr_type(xoutput, CX_ELMNT) == 0 ||
(xml_child_nr_type(xoutput, CX_ELMNT) == 1 &&
(xok = xml_child_i_type(xoutput, 0, CX_ELMNT)) != NULL &&
strcmp(xml_name(xok),"ok")==0);
if (isempty) {
/* Internal error - invalid output from rpc handler */
FCGX_SetExitStatus(204, r->out); /* OK */
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
FCGX_FPrintF(r->out, "\r\n");
goto fail;
}
/* Clear namespace of parameters */
x = NULL;
while ((x = xml_child_each(xoutput, x, CX_ELMNT)) != NULL) {
if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL)
if (xml_purge(xa) < 0)
goto done;
}
/* Set namespace on output */
if (xmlns_set(xoutput, NULL, namespace) < 0)
goto done;
*xoutputp = xoutput;
retval = 1;
done:
clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
if (xerr)
xml_free(xerr);
return retval;
fail:
retval = 0;
goto done;
}
/*! REST operation POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request 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 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] use_xml Set to 0 for JSON and 1 for XML for output data
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
* See RFC 8040 Sec 3.6 / 4.4.2
* @note We map post to edit-config create.
* POST {+restconf}/operations/<operation>
* 1. Initialize
* 2. Get rpc module and name from uri (oppath) and find yang spec
* 3. Build xml tree with user and rpc: <rpc username="foo"><myfn xmlns="uri"/>
* 4. Parse input data (arguments):
* JSON: {"example:input":{"x":0}}
* XML: <input xmlns="uri"><x>0</x></input>
* 5. Translate input args to Netconf RPC, add to xml tree:
* <rpc username="foo"><myfn xmlns="uri"><x>42</x></myfn></rpc>
* 6. Validate outgoing RPC and fill in default values
* <rpc username="foo"><myfn xmlns="uri"><x>42</x><y>99</y></myfn></rpc>
* 7. Send to RPC handler, either local or backend
* 8. Receive reply from local/backend handler as Netconf RPC
* <rpc-reply><x xmlns="uri">0</x></rpc-reply>
* 9. Translate to restconf RPC data:
* JSON: {"example:output":{"x":0}}
* XML: <output xmlns="uri"><x>0</x></input>
* 10. Validate and send reply to originator
*/
int
api_operations_post(clicon_handle h,
FCGX_Request *r,
char *path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
int use_xml,
int parse_xml)
{
int retval = -1;
int i;
char *oppath = path;
yang_stmt *yspec;
yang_stmt *youtput = NULL;
yang_stmt *yrpc = NULL;
cxobj *xret = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
cxobj *xtop = NULL; /* xpath root */
cxobj *xbot = NULL;
yang_stmt *y = NULL;
cxobj *xoutput = NULL;
cxobj *xa;
cxobj *xe;
char *username;
cbuf *cbret = NULL;
int ret = 0;
char *prefix = NULL;
char *id = NULL;
yang_stmt *ys = NULL;
char *namespace = NULL;
clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path);
/* 1. Initialize */
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
for (i=0; i<pi; i++)
oppath = index(oppath+1, '/');
if (oppath == NULL || strcmp(oppath,"/")==0){
if (netconf_operation_failed_xml(&xerr, "protocol", "Operation name expected") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
/* 2. Get rpc module and name from uri (oppath) and find yang spec
* POST {+restconf}/operations/<operation>
*
* The <operation> field identifies the module name and rpc identifier
* string for the desired operation.
*/
if (nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */
goto done;
if ((ys = yang_find(yspec, Y_MODULE, prefix)) == NULL){
if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
if ((yrpc = yang_find(ys, Y_RPC, id)) == NULL){
if (netconf_missing_element_xml(&xerr, "application", id, "RPC not defined") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
/* 3. Build xml tree with user and rpc:
* <rpc username="foo"><myfn xmlns="uri"/>
*/
if ((xtop = xml_new("rpc", NULL, NULL)) == NULL)
goto done;
xbot = xtop;
/* Here xtop is: <rpc/> */
if ((username = clicon_username_get(h)) != NULL){
if ((xa = xml_new("username", xtop, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, username) < 0)
goto done;
/* Here xtop is: <rpc username="foo"/> */
}
if ((ret = api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, 1, &xbot, &y)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
/* Here xtop is: <rpc username="foo"><myfn xmlns="uri"/></rpc>
* xbot is <myfn xmlns="uri"/>
* 4. Parse input data (arguments):
* JSON: {"example:input":{"x":0}}
* XML: <input xmlns="uri"><x>0</x></input>
*/
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,
pretty, use_xml, parse_xml)) < 0)
goto done;
if (ret == 0)
goto ok;
}
/* Here xtop is:
<rpc username="foo"><myfn xmlns="uri"><x>42</x></myfn></rpc> */
#if 1
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0)
goto done;
clicon_debug(1, "%s 5. Translate input args: %s",
__FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
/* 6. Validate incoming RPC and fill in defaults */
if (xml_spec_populate_rpc(h, xtop, yspec) < 0) /* */
goto done;
if ((ret = xml_yang_validate_rpc(h, xtop, &xret)) < 0)
goto done;
if (ret == 0){
if ((xe = xpath_first(xret, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto ok;
}
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
/* Here xtop is (default values):
* <rpc username="foo"><myfn xmlns="uri"><x>42</x><y>99</y></myfn></rpc>
*/
#if 0
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0)
goto done;
clicon_debug(1, "%s 6. Validate and defaults:%s", __FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
/* 7. Send to RPC handler, either local or backend
* Note (1) xtop is <rpc><method> xbot is <method>
* (2) local handler wants <method> and backend wants <rpc><method>
*/
/* 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)
goto done;
if (ret > 0){ /* Handled locally */
if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0)
goto done;
/* Local error: return it and quit */
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
else { /* Send to backend */
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
goto done;
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
goto done;
goto ok;
}
}
/* 8. Receive reply from local/backend handler as Netconf RPC
* <rpc-reply><x xmlns="uri">0</x></rpc-reply>
*/
#if 1
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0)
goto done;
clicon_debug(1, "%s 8. Receive reply:%s", __FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
youtput = yang_find(yrpc, Y_OUTPUT, NULL);
if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace,
pretty, use_xml, &xoutput)) < 0)
goto done;
if (ret == 0)
goto ok;
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
cbuf_reset(cbret);
if (use_xml){
if (clicon_xml2cbuf(cbret, xoutput, 0, pretty) < 0)
goto done;
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
}
else{
if (xml2json_cbuf(cbret, xoutput, pretty) < 0)
goto done;
/* xoutput should now look: {"example:output": {"x":0,"y":42}} */
}
FCGX_FPrintF(r->out, "%s", cbuf_get(cbret));
FCGX_FPrintF(r->out, "\r\n\r\n");
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (prefix)
free(prefix);
if (id)
free(id);
if (xtop)
xml_free(xtop);
if (xret)
xml_free(xret);
if (xerr)
xml_free(xerr);
if (cbret)
cbuf_free(cbret);
return retval;
}

View file

@ -0,0 +1,54 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
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 *****
*
* 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,
cvec *pcvec, int pi,
cvec *qvec, char *data,
int pretty, int use_xml, int parse_xml);
int api_operations_post(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data,
int pretty, int use_xml, int parse_xml);
#endif /* _RESTCONF_METHODS_POST_H_ */

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.
@ -275,6 +275,7 @@ restconf_stream(clicon_handle h,
} }
/* Setting up stream */ /* Setting up stream */
FCGX_SetExitStatus(201, r->out); /* Created */ FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Status: 201 Created\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n"); FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n");
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Connection: keep-alive\r\n"); FCGX_FPrintF(r->out, "Connection: keep-alive\r\n");
@ -368,7 +369,7 @@ api_stream(clicon_handle h,
path = FCGX_GetParam("DOCUMENT_URI", r->envp); path = FCGX_GetParam("DOCUMENT_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp); query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
test(r, 1); restconf_test(r, 1);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done; goto done;
/* Sanity check of path. Should be /stream/<name> */ /* Sanity check of path. Should be /stream/<name> */

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -77,6 +77,7 @@ int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop,
yang_class nodeclass, int strict, cxobj **xpathp, yang_stmt **ypathp); yang_class nodeclass, int strict, cxobj **xpathp, yang_stmt **ypathp);
int xml2xpath(cxobj *x, char **xpath); int xml2xpath(cxobj *x, char **xpath);
int xml2api_path_1(cxobj *x, cbuf *cb);
int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason);
int yang_enum_int_value(cxobj *node, int32_t *val); int yang_enum_int_value(cxobj *node, int32_t *val);

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -1588,10 +1588,16 @@ cvec2xml_1(cvec *cvv,
* Algorithm to compare two sorted lists A, B: * Algorithm to compare two sorted lists A, B:
* A 0 1 2 3 5 6 * A 0 1 2 3 5 6
* B 0 2 4 5 6 * B 0 2 4 5 6
* Let a,b be first elements of A,B respectively * Let (a, b) be first elements of (A, B) respectively(*)
* a = b : recurse; get next a,b * a = b : EITHER leafs: a!=b : add a in changed_x0, b in changed_x1,
* OR: Set (A,B) to children of (a,b) and call algorithm recursively
* , get next (a,b)
* a < b : add a in x0, get next a * a < b : add a in x0, get next a
* a > b : add b in x1, get next b * a > b : add b in x1, get next b
* (*) "comparing" a&b here is made by xml_cmp() which judges equality from a structural
* perspective, ie both have the same yang spec, if they are lists, they have the
* the same keys. NOT that the values are equal!
* @see xml_diff API function, this one is internal and recursive
*/ */
static int static int
xml_diff1(yang_stmt *ys, xml_diff1(yang_stmt *ys,
@ -1700,7 +1706,6 @@ xml_diff1(yang_stmt *ys,
* @param[out] changed_x1 Pointervector to XML nodes changed wanted value * @param[out] changed_x1 Pointervector to XML nodes changed wanted value
* @param[out] changedlen Length of changed vector * @param[out] changedlen Length of changed vector
* All xml vectors should be freed after use. * All xml vectors should be freed after use.
* Bot xml trees should be freed with xml_free()
*/ */
int int
xml_diff(yang_stmt *yspec, xml_diff(yang_stmt *yspec,
@ -1750,7 +1755,9 @@ xml_diff(yang_stmt *yspec,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] cb api_path_fmt, * @param[out] cb api_path_fmt,
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 * @retval 0 OK
* @retval -1 Error
* @see RFC8040 3.5.3 where "api-path" is defined as "URI-encoded path expression"
*/ */
static int static int
yang2api_path_fmt_1(yang_stmt *ys, yang2api_path_fmt_1(yang_stmt *ys,
@ -2819,12 +2826,14 @@ xml2xpath1(cxobj *x,
cprintf(cb, "/%s", xml_name(x)); cprintf(cb, "/%s", xml_name(x));
if ((y = xml_spec(x)) != NULL){ if ((y = xml_spec(x)) != NULL){
keyword = yang_keyword_get(y); keyword = yang_keyword_get(y);
if (keyword == Y_LEAF_LIST){ switch (keyword){
case Y_LEAF_LIST:
if ((b = xml_body(x)) != NULL) if ((b = xml_body(x)) != NULL)
cprintf(cb, "[.=\"%s\"]", b); cprintf(cb, "[.=\"%s\"]", b);
else else
cprintf(cb, "[.=\"\"]"); cprintf(cb, "[.=\"\"]");
} else if (keyword == Y_LIST){ break;
case Y_LIST:
cvk = yang_cvec_get(y); cvk = yang_cvec_get(y);
cvi = NULL; cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) { while ((cvi = cvec_each(cvk, cvi)) != NULL) {
@ -2836,6 +2845,9 @@ xml2xpath1(cxobj *x,
b = xml_body(xb); b = xml_body(xb);
cprintf(cb, "[%s=\"%s\"]", keyname, b?b:""); cprintf(cb, "[%s=\"%s\"]", keyname, b?b:"");
} }
break;
default:
break;
} }
} }
retval = 0; retval = 0;
@ -2866,25 +2878,6 @@ xml2xpath(cxobj *x,
goto done; goto done;
/* XXX: see xpath in test statement,.. */ /* XXX: see xpath in test statement,.. */
xpath = cbuf_get(cb); xpath = cbuf_get(cb);
#if 0 /* debug test */
{
cxobj *xt = x;
cxobj *xcp;
cxobj *x2;
while (xml_parent(xt) != NULL &&
xml_spec(xt) != NULL)
xt = xml_parent(xt);
xcp = xml_parent(xt);
xml_parent_set(xt, NULL);
x2 = xpath_first(xt, "%s", xpath); /* +1: skip first / */
xml_parent_set(xt, xcp);
assert(x2 && x==x2);
if (x==x2)
clicon_debug(1, "%s %s match", __FUNCTION__, xpath);
else
clicon_debug(1, "%s %s no match", __FUNCTION__, xpath);
}
#endif
if (xpathp){ if (xpathp){
if ((*xpathp = strdup(xpath)) == NULL){ if ((*xpathp = strdup(xpath)) == NULL){
clicon_err(OE_UNIX, errno, "strdup"); clicon_err(OE_UNIX, errno, "strdup");
@ -2899,6 +2892,94 @@ xml2xpath(cxobj *x,
return retval; return retval;
} }
/*! Construct an api_path from an XML node (single level not recursive)
* @param[in] x XML node (need to be yang populated)
* @param[out] cb api_path, must be initialized
* @retval 0 OK
* @retval -1 Error
* @see yang2api_path_fmt
* @see xml2xpath
*/
int
xml2api_path_1(cxobj *x,
cbuf *cb)
{
int retval = -1;
yang_stmt *y = NULL;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
enum rfc_6020 keyword;
int i;
char *keyname;
cxobj *xkey;
cxobj *xb;
char *b;
char *enc;
yang_stmt *ymod;
cxobj *xp;
if ((y = xml_spec(x)) == NULL){
cprintf(cb, "/%s", xml_name(x));
goto ok;
}
ymod = ys_module(y);
xp = xml_parent(x);
if (ymod && xp && xml_spec(xp)==NULL) /* Add prefix only if root */
cprintf(cb, "/%s:%s", yang_argument_get(ymod), xml_name(x));
else
cprintf(cb, "/%s", xml_name(x));
keyword = yang_keyword_get(y);
switch (keyword){
case Y_LEAF_LIST:
b = xml_body(x);
enc = NULL;
if (uri_percent_encode(&enc, "%s", b) < 0)
goto done;
cprintf(cb, "=%s", enc?enc:"");
if (enc)
free(enc);
break;
case Y_LIST:
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
if (cvec_len(cvk))
cprintf(cb, "=");
/* Iterate over individual keys */
cvi = NULL;
i = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if ((xkey = xml_find(x, keyname)) == NULL)
goto done; /* No key in xml */
if ((xb = xml_find(x, keyname)) == NULL)
goto done;
if (i++)
cprintf(cb, ",");
b = xml_body(xb);
enc = NULL;
if (uri_percent_encode(&enc, "%s", b) < 0)
goto done;
cprintf(cb, "%s", enc?enc:"");
if (enc)
free(enc);
}
break;
default:
break;
}
#if 0
{ /* Just for testing */
cxobj *xc;
if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL)
if (xml2api_path_1(xc, cb) < 0)
goto done;
}
#endif
ok:
retval = 0;
done:
return retval;
}
/*! Check if the module tree x is in is assigned right XML namespace, assign if not /*! Check if the module tree x is in is assigned right XML namespace, assign if not
* @param[in] x XML node * @param[in] x XML node
*(0. You should probably find the XML root and apply this function to that.) *(0. You should probably find the XML root and apply this function to that.)

View file

@ -207,6 +207,15 @@ xml_child_spec(cxobj *x,
* @note empty value/NULL is smallest value * @note empty value/NULL is smallest value
* @note some error cases return as -1 (qsort cant handle errors) * @note some error cases return as -1 (qsort cant handle errors)
* @note some error cases return as -1 (qsort cant handle errors) * @note some error cases return as -1 (qsort cant handle errors)
*
* NOTE: "comparing" x1 and x2 here judges equality from a structural (model)
* perspective, ie both have the same yang spec, if they are lists, they have the
* the same keys. NOT that the values are equal!
* In other words, if x is a leaf with the same yang spec, <x>1</x> and <x>2</x> are
* "equal".
* If x is a list element (with key "k"),
* <x><k>42</42><y>foo</y></x> and <x><k>42</42><y>bar</y></x> are equal,
* but is not equal to <x><k>71</42><y>bar</y></x>
*/ */
int int
xml_cmp(cxobj *x1, xml_cmp(cxobj *x1,
@ -299,10 +308,10 @@ xml_cmp(cxobj *x1,
else{ else{
if (xml_cv_cache(x1b, &cv1) < 0) /* error case */ if (xml_cv_cache(x1b, &cv1) < 0) /* error case */
goto done; goto done;
assert(cv1); // assert(cv1);
if (xml_cv_cache(x2b, &cv2) < 0) /* error case */ if (xml_cv_cache(x2b, &cv2) < 0) /* error case */
goto done; goto done;
assert(cv2); // assert(cv2);
if ((equal = cv_cmp(cv1, cv2)) != 0) if ((equal = cv_cmp(cv1, cv2)) != 0)
goto done; goto done;
} }

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# Basic NACM default rulw without any groups # Basic NACM default rule without any groups
# Start from startup db # Start from startup db
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)

View file

@ -176,10 +176,6 @@ nacm
# update | p/d | xp/dx | p/d # update | p/d | xp/dx | p/d
# delete | p/d | xp/dx | p/d # delete | p/d | xp/dx | p/d
#----------root
new "update root list default deny"
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d '<data><x xmlns="urn:example:nacm">42</x>$RULES</data>')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} '
# replace all, then must include NACM rules as well # replace all, then must include NACM rules as well
MSG="<data>$RULES</data>" MSG="<data>$RULES</data>"
new "update root list permit" new "update root list permit"

View file

@ -160,7 +160,7 @@ expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)"
# Exact match # Exact match
new "restconf Add subtree eth/0/0 to datastore using POST" new "restconf Add subtree eth/0/0 to datastore using POST"
expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 201 Created'
new "restconf Re-add subtree eth/0/0 which should give error" new "restconf Re-add subtree eth/0/0 which should give error"
expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}' expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'

View file

@ -7,7 +7,7 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example APPNAME=example
cfg=$dir/conf.xml cfg=$dir/conf.xml
fyang=$dir/restconf.yang fyang=$dir/example-jukebox.yang
fxml=$dir/initial.xml fxml=$dir/initial.xml
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN> # <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
@ -326,9 +326,28 @@ expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+json' http://l
new "B.1.3. Retrieve the Server Capability Information" new "B.1.3. Retrieve the Server Capability Information"
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability></capabilities>' expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability></capabilities>'
new "B.2.1. Create New Data Resources (artist+json)"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters"
new "B.2.1. Create New Data Resources (album+xml)"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light"
new "B.2.1. Add Data Resources again (conflict - not in RFC)"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 409 Conflict"
new "4.5. PUT replace content"
# XXX should be: jbox:alternative --> example-jukebox:alternative
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"jbox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content"
new "4.5. PUT replace content (xml encoding)"
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content"
new "4.5. PUT create new"
# XXX should be: jbox:alternative --> example-jukebox:alternative
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"
if false; then # NYI if false; then # NYI
new "B.2.1. Create New Data Resources"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location:" # etc
new "B.2.2. Detect Datastore Resource Entity-Tag Change" new "B.2.2. Detect Datastore Resource Entity-Tag Change"
new "B.2.3. Edit a Datastore Resource" new "B.2.3. Edit a Datastore Resource"

View file

@ -204,13 +204,13 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<
# Now same with restconf # Now same with restconf
new "restconf edit main" new "restconf edit main"
expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:main":{"x":"foo","ext":"foo"}}' 0 'HTTP/1.1 200 OK' expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:main":{"x":"foo","ext":"foo"}}' 0 'HTTP/1.1 201 Created'
new "restconf edit sub1" new "restconf edit sub1"
expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:sub1":{"x":"foo","ext1":"foo"}}' 0 'HTTP/1.1 200 OK' expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:sub1":{"x":"foo","ext1":"foo"}}' 0 'HTTP/1.1 201 Created'
new "restconf edit sub2" new "restconf edit sub2"
expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:sub2":{"x":"foo","ext2":"foo"}}' 0 'HTTP/1.1 200 OK' expectfn 'curl -s -i -X POST http://localhost/restconf/data -d {"main:sub2":{"x":"foo","ext2":"foo"}}' 0 'HTTP/1.1 201 Created'
new "restconf check main/sub1/sub2 contents" new "restconf check main/sub1/sub2 contents"
expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}' expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data":{"main:main":{"ext":"foo","x":"foo"},"main:sub1":{"ext1":"foo","x":"foo"},"main:sub2":{"ext2":"foo","x":"foo"}}}'