clixon/apps/restconf/restconf_methods.c

1520 lines
52 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 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 rfc8040
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
* sudo su -c "/www-data/clixon_restconf -D 1 f /usr/local/etc/example.xml " -s /bin/sh www-data
* This is the interface:
* api/data/profile=<name>/metric=<name> PUT data:enable=<flag>
* api/test
*/
#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 <time.h>
#include <signal.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
// TODO - remove this include if cbuf_trunc() is added to cligen repo
#include "../cligen/cligen_buf_internal.h"
/* clicon */
#include <clixon/clixon.h>
#include "restconf_lib.h"
#include "restconf_handle.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods.h"
#include "restconf_methods_post.h"
/*! REST OPTIONS method
* According to restconf
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
*
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* Minimal support:
* 200 OK
* Allow: HEAD,GET,PUT,DELETE,OPTIONS
* @see RFC5789 PATCH Method for HTTP Section 3.2
*/
int
api_data_options(clicon_handle h,
void *req)
{
int retval = -1;
clicon_debug(1, "%s", __FUNCTION__);
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) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Check matching keys
*
* Check that x1 and x2 are of type list/leaf-list and share the same key statements
* I.e that if x1=<list><key>b</key></list> then x2 = <list><key>b</key></list> as
* well. Otherwise return -1.
* @param[in] y Yang statement, should be list or leaf-list
* @param[in] x1 First XML tree (eg data)
* @param[in] x2 Second XML tree (eg api-path)
* @retval 0 Yes, keys match
* @retval -1 No, keys do not match
* If the target resource represents a YANG leaf-list, then the PUT
* method MUST NOT change the value of the leaf-list instance.
*
* If the target resource represents a YANG list instance, then the key
* leaf values, in message-body representation, MUST be the same as the
* key leaf values in the request URI. The PUT method MUST NOT be used
* to change the key leaf values for a data resource instance.
*/
static int
match_list_keys(yang_stmt *y,
cxobj *x1,
cxobj *x2)
{
int retval = -1;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
cxobj *xkey1; /* xml key object of x1 */
cxobj *xkey2; /* xml key object of x2 */
char *key1;
char *key2;
clicon_debug(1, "%s", __FUNCTION__);
switch (yang_keyword_get(y)){
case Y_LIST:
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if ((xkey2 = xml_find(x2, keyname)) == NULL)
goto done; /* No key in api-path */
if ((key2 = xml_body(xkey2)) == NULL)
goto done;
if ((xkey1 = xml_find(x1, keyname)) == NULL)
goto done; /* No key in data */
if ((key1 = xml_body(xkey1)) == NULL)
goto done;
if (strcmp(key2, key1) != 0)
goto done; /* keys dont match */
}
break;
case Y_LEAF_LIST:
if ((key2 = xml_body(x2)) == NULL)
goto done; /* No key in api-path */
if ((key1 = xml_body(x1)) == NULL)
goto done; /* No key in data */
if (strcmp(key2, key1) != 0)
goto done; /* keys dont match */
break;
default:
goto ok;
}
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Common PUT plain PATCH method
* Code checks if object exists.
* PUT: If it does not, set op to create, otherwise replace
* PATCH: If it does not, fail, otherwise replace/merge
* @param[in] plain_patch fail if object does not exists AND merge (not replace)
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] pretty Pretty-print
* @param[in] media_in Restconf input media
* @param[in] media_out Restconf output media
*/
static int
api_data_write(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_in,
restconf_media media_out,
int plain_patch,
ietf_ds_t ds)
{
int retval = -1;
enum operation_type op;
int i;
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
cxobj *xdata; /* -d data (without top symbol)*/
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) */
cxobj *xparent;
yang_stmt *yp; /* yang parent */
yang_stmt *yspec;
cxobj *xa;
char *api_path;
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; /* direct pointer into tree, dont free */
char *username;
int ret;
char *namespace = NULL;
char *dname;
cvec *nsc = NULL;
yang_bind yb;
char *xpath = NULL;
char *attr;
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
api_path=api_path0;
/* strip /... from start */
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
if (api_path){
/* Translate api-path to xpath: xpath (cbpath) and namespace context (nsc) */
if ((ret = api_path2xpath(api_path, yspec, &xpath, &nsc, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
if (plain_patch)
op = OP_MERGE; /* bad request if it does not exist */
else
op = OP_REPLACE; /* OP_CREATE if it does not exist */
/* Create config top-of-tree */
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
/* Translate api_path to xml in the form of xtop/xbot */
xbot = xtop;
if (api_path){ /* If URI, otherwise top data/config object */
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ybot)
ymodapi = ys_module(ybot);
}
/* 4.4.1: The message-body MUST contain exactly one instance of the
* expected data resource. (tested again below)
*/
if (data == NULL || strlen(data) == 0){
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* Create a dummy data tree parent to hook in the parsed data.
*/
if ((xdata0 = xml_new(XML_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if (api_path){ /* XXX mv to copy? */
cxobj *xfrom;
cxobj *xac;
if (api_path && (strcmp(api_path, "/") != 0))
xfrom = xml_parent(xbot);
else
xfrom = xbot; // XXX xbot is /config has NULL parent
if (xml_copy_one(xfrom, xdata0) < 0)
goto done;
xa = NULL;
while ((xa = xml_child_each(xfrom, xa, CX_ATTR)) != NULL) {
if ((xac = xml_new(xml_name(xa), xdata0, CX_ATTR)) == NULL)
goto done;
if (xml_copy(xa, xac) < 0) /* recursion */
goto done;
}
}
if (xml_spec(xdata0)==NULL){
if (api_path==NULL)
yb = YB_MODULE_NEXT; /* data is eg: <data><x> */
else
yb = YB_MODULE; /* data is eg: <x> */
}
else
yb = YB_PARENT;
/* Parse input data as json or xml into xml
* Note that in POST (api_data_post) the new object is grafted on xbot, since it is a new
* object. In that case all yang bindings can be made since xbot is available.
* Here the new object replaces xbot and is therefore more complicated to make when parsing.
* Instead, xbots parent is copied into xdata0 (but not its children).
*/
switch (media_in){
case YANG_DATA_XML:
if ((ret = clixon_xml_parse_string(data, yb, yspec, &xdata0, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
break;
case YANG_DATA_JSON:
if ((ret = clixon_json_parse_string(data, yb, yspec, &xdata0, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
break;
default:
restconf_unsupported_media(h, req, pretty, media_out);
goto ok;
break;
} /* switch media_in */
/* The message-body MUST contain exactly one instance of the
* expected data resource.
*/
if (xml_child_nr_type(xdata0, CX_ELMNT) != 1){
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
xdata = xml_child_i_type(xdata0, 0, CX_ELMNT);
/* If the api-path (above) defines a module, then xdata must have a prefix
* and it match the module defined in api-path
* This does not apply if api-path is / (no module)
*/
if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0)
goto done;
if (ymoddata && ymodapi){
if (ymoddata != ymodapi){
if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
/* Add operation create as attribute. If that fails with Conflict, then
* try "replace" (see comment in function header)
*/
if ((xa = xml_new("operation", xdata, CX_ATTR)) == NULL)
goto done;
if (xml_prefix_set(xa, NETCONF_BASE_PREFIX) < 0)
goto done;
if (xml_value_set(xa, xml_operation2str(op)) < 0) /* XXX here is where op is used */
goto done;
if ((xa = xml_new("objectcreate", xdata, CX_ATTR)) == NULL)
goto done;
if (plain_patch){
/* RFC 8040 4.6. PATCH:
* If the target resource instance does not exist, the server MUST NOT create it.
*/
if (xml_value_set(xa, "false") < 0)
goto done;
}
else
if (xml_value_set(xa, "true") < 0)
goto done;
/* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data
*/
dname = xml_name(xdata);
if (api_path==NULL) {
if (strcmp(dname, NETCONF_OUTPUT_DATA)!=0){
if (netconf_bad_element_xml(&xerr, "application", dname,
"Data element does not match top-level data") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (xml_addsub(NULL, xdata) < 0)
goto done;
if (xtop) /* also xbot */
xml_free(xtop);
xtop = xdata;
xml_name_set(xtop, NETCONF_INPUT_CONFIG);
/* remove default namespace */
if ((xa = xml_find_type(xtop, NULL, "xmlns", CX_ATTR)) != NULL){
if (xml_rm(xa) < 0)
goto done;
if (xml_free(xa) < 0)
goto done;
}
}
else { /* api-path != NULL */
/* There is an api-path that defines an element in the datastore tree.
* Not top-of-tree.
*/
clicon_debug(1, "%s Comparing bottom-of api-path (%s) with top-of-data (%s)",__FUNCTION__, xml_name(xbot), dname);
/* Check same symbol in api-path as data */
if (strcmp(dname, xml_name(xbot))){
if (netconf_bad_element_xml(&xerr, "application", dname,
"Data element does not match api-path") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* If list or leaf-list, api-path keys must match data keys
* There are two cases, either the object is the list element itself,
* eg xpath:obj=a data:<obj><key>b</key></obj>
* or the object is the key element:
* eg xpath:obj=a/key data:<key>b</key>
* That is why the conditional is somewhat hairy
*/
xparent = xml_parent(xbot);
if (ybot){
/* Ensure list keys match between uri and data. That is:
* If data is on the form: -d {"a":{"k":1}} where a is list or leaf-list
* then uri-path must be ../a=1
* match_list_key() checks if this is true
*/
if (match_list_keys(ybot, xdata, xbot) < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* Ensure keys in lists are not changed. That is:
* If data is on the form: -d {"k":1} and its parent is a list "a"
* then the uri-path must be "../a=1 (you cannot change a's key)"
*/
if ((yp = yang_parent_get(ybot)) != NULL &&
yang_keyword_get(yp) == Y_LIST){
if ((ret = yang_key_match(yp, dname)) < 0)
goto done;
if (ret == 1){ /* Match: xdata is a key */
char *parbod = xml_find_body(xparent, dname);
/* Check if the key is different from the one in uri-path,
* or does not exist
*/
if (parbod == NULL || strcmp(parbod, xml_body(xdata))){
if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
}
}
if (xtop != xbot) /* Should always be true */
xml_purge(xbot);
if (xml_addsub(xparent, xdata) < 0)
goto done;
/* If restconf insert/point attributes are present, translate to netconf */
if (restconf_insert_attributes(xdata, qvec) < 0)
goto done;
/* If we already have that default namespace, remove it in child */
if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){
if (xml2ns(xparent, NULL, &namespace) < 0){
clicon_debug(1, "%s G done", __FUNCTION__);
goto done;
}
if (namespace == NULL){
clicon_log_xml(LOG_DEBUG, xparent, "%s xparent:", __FUNCTION__);
/* XXX */
}
/* Set xmlns="" default namespace attribute (if diff from default) */
if (namespace && strcmp(namespace, xml_value(xa))==0)
xml_purge(xa);
}
} /* api-path != NULL */
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
/* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "<rpc xmlns=\"%s\" username=\"%s\" xmlns:%s=\"%s\" %s>",
NETCONF_BASE_NAMESPACE,
username?username:"",
NETCONF_BASE_PREFIX,
NETCONF_BASE_NAMESPACE, /* bind nc to netconf namespace */
NETCONF_MESSAGE_ID_ATTR);
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If this is a "data" request and 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.
*/
if ((IETF_DS_NONE == ds) &&
if_feature(yspec, "ietf-netconf", "startup") &&
!clicon_option_bool(h, "CLICON_RESTCONF_STARTUP_DONTUPDATE")){
cprintf(cbx, " copystartup=\"true\"");
}
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
if (clicon_xml2cbuf(cbx, xtop, 0, 0, -1) < 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, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if ((xe = xpath_first(xret, NULL, "//ok")) != NULL &&
(attr = xml_find_value(xe, "objectexisted")) != NULL &&
strcmp(attr, "false")==0){
if (restconf_reply_send(req, 201, NULL, 0) < 0) /* Created */
goto done;
}
else
if (restconf_reply_send(req, 204, NULL, 0) < 0) /* No content */
goto done;
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xpath)
free(xpath);
if (nsc)
xml_nsctx_free(nsc);
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_write */
#ifdef YANG_PATCH
/*! Free memory after a NULL pointer check
*
* @param [in] str void pointer to memory to be freed
*
*/
static void yang_patch_free_mem(void *p)
{
if (p != NULL)
free(p);
}
/*! Return a value within XML tags
* @param [in] nsc namespace context
* @param [in] xn cxobj containing XML with the current edit
* @param [in] val cbuf to which the value will be written
* @param [in] key string containing the tag
* @retval 0 success
* @retval <0 failure
*/
static int yang_patch_get_xval(
cvec* nsc,
cxobj* xn,
cbuf* val,
const char* key
)
{
cxobj **vec = NULL;
size_t veclen = 0;
char* tmp_val = NULL;
int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key);
if (ret < 0) {
return ret;
}
cxobj *xn_tmp = NULL;
if (veclen == 1) { //veclen should always be 1
xn_tmp = vec[0];
}
if (xn_tmp != NULL) {
tmp_val = xml_body(xn_tmp);
cbuf_append_str(val, tmp_val);
}
return 0;
}
// TODO - add this to cligen repo if it is approved
/*! Truncate a cbuf
*
* @param [in] cb cligen buffer allocated by cbuf_new(), may be reallocated.
* @param [in] int pos position at which to truncate
* @retval new cbuf containing the truncated string (old buffer remains as it was)
* @retval NULL Error
*/
cbuf*
cbuf_trunc(cbuf *cb,
int pos)
{
if (pos < 0 || pos > cb->cb_strlen){
errno = EINVAL;
return NULL;
}
/* Ensure buffer is right size */
cbuf* new_buf = cbuf_new_alloc(pos + 1);
if (new_buf == NULL)
return NULL;
strncpy(new_buf->cb_buffer, cb->cb_buffer, pos);
new_buf->cb_strlen = pos;
return new_buf;
}
/*! Add square brackets after the surrounding curly brackets in JSON
* Needed, in order to modify the result of xml2json_cbuf() to be valid input
* to api_dta_post() and api_dta_write()
* @param [in] x_simple_patch a cxobj to pass to xml2json_cbuf()
* @retval new cbuf with the modified json
* @retval NULL Error
*/
static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch)
{
cbuf *json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return NULL;
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
// Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect
char *json_simple_patch_tmp = cbuf_get(cb);
int brace_count = 0;
for (int l = 0; l < strlen(json_simple_patch_tmp); l++) {
char c = json_simple_patch_tmp[l];
if (c == '{') {
brace_count++;
if (brace_count == 2) { // We've reached the second brace, insert a '[' before it
cbuf_append(json_simple_patch,(int)'[');
}
}
cbuf_append(json_simple_patch,(int)c);
}
cbuf* json_simple_patch_2 = NULL;
// Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects
for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) {
char c = cbuf_get(json_simple_patch)[l];
if (c == '}') {
// Truncate and add a string, as there is not a function to insert a char into a cbuf
json_simple_patch_2 = cbuf_trunc(json_simple_patch, l);
cbuf_append_str(json_simple_patch_2, "]}");
break;
}
}
cbuf_free(json_simple_patch);
cbuf_free(cb);
return json_simple_patch_2;
}
/*!yang_patch_strip_after_last_slash
*
* Strip /... from end of val
* so that e.g. "/interface=eth2" becomes "/"
* or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
*
* @param[in] val value to strip
* @retval new cbuf with the stripped string
* @retval NULL error
*/
static cbuf* yang_patch_strip_after_last_slash(cbuf* val)
{
cbuf *cb = cbuf_new();
cbuf* val_tmp = cbuf_new();
cbuf_append_str(val_tmp, cbuf_get(val));
int idx = cbuf_len(val_tmp);
for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) {
if (cbuf_get(val_tmp)[l] == '/') {
idx = l;
break;
}
}
if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above
return NULL;
cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1);
if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0)
return NULL;
cbuf_free(val_tmp);
cbuf_free(val_tmp_2);
return cb;
}
/*! YANG PATCH replace method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @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_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] target_val value in "target" field of edit in YANG patch
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int yang_patch_do_replace (
clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
cbuf* target_val,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch
)
{
cxobj * value_vec_tmp = NULL;
cbuf* delete_req_uri = cbuf_new();
if (delete_req_uri == NULL)
return 1;
// Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0)
return 1;
// Add the target to delete_req_uri,
// so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2"
if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0)
return 1;
// Delete the object with the old values
int ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds );
cbuf_free(delete_req_uri);
if (ret != 0)
return ret;
// Now set up for the post request.
// Strip /... from end of target val
// so that e.g. "/interface=eth2" becomes "/"
// or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/"
cbuf* post_req_uri = yang_patch_strip_after_last_slash(target_val);
// Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces"
if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri)))
return 1;
cbuf_free(post_req_uri);
// Now insert the new values into the data
// (which will include the key value and all other mandatory values)
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Convert the data to json
cbuf *json_simple_patch = cbuf_new();
if (json_simple_patch == NULL)
return 1;
xml2json_cbuf(json_simple_patch, x_simple_patch, 1);
// Send the POST request
ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
cbuf_free(json_simple_patch);
xml_free(value_vec_tmp);
return ret;
}
/*! YANG PATCH create method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @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_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
*/
static int yang_patch_do_create (
clicon_handle h,
void *req,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch
)
{
cxobj * value_vec_tmp = NULL;
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
// Send the POST request
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
char *json_simple_patch = cbuf_get(cb);
int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds );
xml_free(value_vec_tmp);
return ret;
}
/*! YANG PATCH insert method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] pi Offset, where to start pcvec
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. <ietf-interfaces:interface/>
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One"
* @param[in] point_val value in "point" field of edit in YANG patch
*/
static int yang_patch_do_insert (
clicon_handle h,
void *req,
int pi,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch,
cbuf* where_val,
char* api_path,
cbuf *point_val
)
{
cxobj * value_vec_tmp = NULL;
// Loop through the XML, and get each value
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
}
cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
// Set the insert attributes
cvec* qvec_tmp = NULL;
qvec_tmp = cvec_new(0);
if (qvec_tmp == NULL)
return 1;
cg_var *cv;
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "insert");
cv_string_set(cv, cbuf_get(where_val));
cbuf *point_str = cbuf_new();
if (point_str == NULL)
return 1;
cbuf_append_str(point_str, api_path);
cbuf_append_str(point_str, cbuf_get(point_val));
if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){
return 1;
}
cv_name_set(cv, "point");
cv_string_set(cv, cbuf_get(point_str));
// Send the POST request
int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds );
xml_free(value_vec_tmp);
cbuf_free(point_str);
cbuf_free(json_simple_patch);
return ret;
}
/*! YANG PATCH merge method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @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_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces"
* @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch
* @param[in] value_vec pointer to the "value" array of an edit in YANG patch
* @param[in] x_simple_patch pointer to XML containing module name, e.g. "<ietf-interfaces:interface/>"
* @param[in] where_val value in "where" field of edit in YANG patch
* @param[in] key_xn XML with key tag and value, e.g. "<name>Foo-One</name>"
*/
static int yang_patch_do_merge (
clicon_handle h,
void *req,
cvec *pcvec,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out,
ietf_ds_t ds,
cbuf* simple_patch_request_uri,
int value_vec_len,
cxobj** value_vec,
cxobj *x_simple_patch,
cxobj *key_xn
)
{
int ret = -1;
cxobj * value_vec_tmp = NULL;
if (key_xn != NULL)
xml_addsub(x_simple_patch, key_xn);
// Loop through the XML, create JSON from each one, and submit a simple patch
for (int k = 0; k < value_vec_len; k++) {
if (value_vec[k] != NULL) {
value_vec_tmp = xml_dup(value_vec[k]);
xml_addsub(x_simple_patch, value_vec_tmp);
}
cbuf* cb = cbuf_new();
xml2json_cbuf(cb, x_simple_patch, 1);
cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch);
if (json_simple_patch == NULL)
return 1;
xml_free(value_vec_tmp);
// Send the simple patch request
ret = api_data_write(h, req, cbuf_get(simple_patch_request_uri), pcvec, pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds );
cbuf_free(cb);
cbuf_free(json_simple_patch);
}
return ret;
}
/*! YANG PATCH method
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path0 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] media_out Output media
* Netconf: <edit-config> (nc:operation="merge")
* See RFC8072
* YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or
"remove" a resource within the target resource.
* Currently "move" not supported
*/
static int
api_data_yang_patch(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
int i;
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
cbuf *cbx = NULL;
cxobj *xtop = NULL; /* top of api-path */
cxobj *xbot = NULL; /* bottom of api-path */
yang_stmt *ybot = NULL; /* yang of xbot */
cxobj *xbot_tmp = NULL;
yang_stmt *yspec;
char *api_path;
cxobj *xret = NULL;
cxobj *xretcom = NULL; /* return from commit */
cxobj *xretdis = NULL; /* return from discard-changes */
cxobj *xerr = NULL; /* malloced must be freed */
int ret;
cvec *nsc = NULL;
yang_bind yb;
char *xpath = NULL;
cbuf *path_orig_1 = NULL;
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
api_path=api_path0;
/* strip /... from start */
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Translate yang-patch path to xpath: xpath (cbpath) and namespace context (nsc) */
char yang_patch_path[] = "/ietf-yang-patch:yang-patch";
if ((ret = api_path2xpath(yang_patch_path, yspec, &xpath, &nsc, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* Create config top-of-tree */
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
/* Translate yang-patch path to xml in the form of xtop/xbot */
xbot = xtop;
if ((ret = api_path2xml(yang_patch_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
yb = YB_MODULE;
if ((ret = clixon_json_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/*
* RFC 8072 2.1: The message-body MUST identify exactly one resource instance
*/
int nrchildren0 = 0;
cxobj *x = NULL;
if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
size_t veclen;
cxobj **vec = NULL;
while ((x = xml_child_each(xbot, x, CX_ELMNT)) != NULL){
ret = xpath_vec(x, nsc, "edit", &vec, &veclen);
if (xml_flag(x, XML_FLAG_MARK)){
xml_flag_reset(x, XML_FLAG_MARK);
continue;
}
}
path_orig_1 = cbuf_new();
if (path_orig_1 == NULL) {
goto done;
} else {
cbuf_append_str(path_orig_1, restconf_uripath(h));
}
// Loop through the edits
for (int i = 0; i < veclen; i++) {
cxobj **tmp_vec = NULL;
size_t tmp_veclen = 0;
cxobj *xn = vec[i];
clicon_log_xml(LOG_DEBUG, xn, "%s %d xn:", __FUNCTION__, __LINE__);
// Get target
cbuf *target_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, target_val, "target");
if (ret < 0) {
goto done;
}
// Get operation
cbuf *op_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, op_val, "operation");
if (ret < 0) {
goto done;
}
// Get "point" and "where" for insert operations
cbuf *point_val = NULL;
cbuf *where_val = cbuf_new();
if (strcmp(cbuf_get(op_val), "insert") == 0) {
point_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, point_val, "point");
if (ret < 0) {
goto done;
}
where_val = cbuf_new();
ret = yang_patch_get_xval(nsc, xn, where_val, "where");
if (ret < 0) {
goto done;
}
}
// Construct request URI
cbuf* simple_patch_request_uri = cbuf_new();
cbuf_append_str(simple_patch_request_uri, cbuf_get(path_orig_1));
cbuf* api_path_target = cbuf_new();
cbuf_append_str(api_path_target, api_path);
if (strcmp(cbuf_get(op_val), "merge") == 0) {
cbuf_append_str(api_path_target, cbuf_get(target_val));
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
}
if (xerr)
xml_free(xerr);
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
// Get key field
/* Translate api_path to xml in the form of xtop/xbot */
xbot_tmp = xtop;
if ((ret = api_path2xml(cbuf_get(api_path_target), yspec, xtop, YC_DATANODE, 1, &xbot_tmp, &ybot, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
char *key_node_id = xml_name(xbot_tmp);
char *path = NULL;
if ((path = restconf_param_get(h, "REQUEST_URI")) != NULL){
for (int i1 = 0; i1 <pi; i1++)
path = index(path+1, '/');
}
const char colon[2] = ":";
char *modname = strtok(&(path[1]), colon);
cxobj **key_vec = NULL;
key_vec = xml_childvec_get(xbot_tmp);
cxobj *key_xn = NULL;
if (key_vec != NULL) {
key_xn = key_vec[0];
}
// Get values (for "delete" and "remove", there are no values)
xpath_vec(xn, nsc, "value", &tmp_vec, &tmp_veclen);
key_node_id = NULL;
// Loop through the values
for (int j = 0; j < tmp_veclen; j++) {
cxobj *values_xn = tmp_vec[j];
cxobj** values_child_vec = xml_childvec_get(values_xn);
if (key_node_id == NULL)
key_node_id = xml_name(*values_child_vec);
cbuf *patch_header = cbuf_new();
if (patch_header == NULL) {
goto done;
}
cbuf_append_str(patch_header, modname);
cbuf_append_str(patch_header, ":");
cbuf_append_str(patch_header, key_node_id);
cxobj *x_simple_patch = xml_new(cbuf_get(patch_header), NULL, CX_ELMNT);
if (x_simple_patch == NULL)
goto done;
int value_vec_len = xml_child_nr(*values_child_vec);
cxobj** value_vec = xml_childvec_get(*values_child_vec);
// For "replace", delete the item and then POST it
// TODO - in an ordered list, insert it into its original position
if (strcmp(cbuf_get(op_val),"replace") == 0) {
ret = yang_patch_do_replace(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, target_val, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "create", put all the data values into a single POST request
if (strcmp(cbuf_get(op_val),"create") == 0) {
ret = yang_patch_do_create(h, req, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch);
if (ret != 0) {
goto done;
}
}
// For "insert", make a api_data_post request
if (strcmp(cbuf_get(op_val), "insert") == 0) {
ret = yang_patch_do_insert(h, req, pi, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, where_val, api_path, point_val);
if (ret != 0) {
goto done;
}
}
// For merge", make single simple patch requests for each value
if (strcmp(cbuf_get(op_val),"merge") == 0) {
ret = yang_patch_do_merge(h, req, pcvec, pi, qvec, pretty, media_out, ds, simple_patch_request_uri, value_vec_len, value_vec, x_simple_patch, key_xn);
if (ret != 0) {
goto done;
}
}
cbuf_free(patch_header);
yang_patch_free_mem((void *)x_simple_patch); // Using xml_free() causes crash
}
if ((strcmp(cbuf_get(op_val), "delete") == 0) ||
(strcmp(cbuf_get(op_val), "remove") == 0)) {
cbuf_append_str(simple_patch_request_uri, cbuf_get(target_val));
if (strcmp(cbuf_get(op_val), "delete") == 0) {
// TODO - send error
} else {
// TODO - do not send error
}
api_data_delete(h, req, cbuf_get(simple_patch_request_uri), pi, pretty, YANG_DATA_JSON, ds);
}
cbuf_free(simple_patch_request_uri);
cbuf_free(api_path_target);
cbuf_free(target_val);
cbuf_free(op_val);
cbuf_free(point_val);
cbuf_free(where_val);
}
ok:
retval = 0;
done:
cbuf_free(path_orig_1);
yang_patch_free_mem((void *)vec);
yang_patch_free_mem((void *)xpath);
if (nsc)
xml_nsctx_free(nsc);
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;
}
#endif // YANG_PATCH
/*! Generic REST PUT method
* @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] media_out Output media
* @note restconf PUT is mapped to edit-config replace.
* @see RFC8040 Sec 4.5 PUT
* @see api_data_post
* @example
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
PUT:
A request message-body MUST be present, representing the new data resource, or the server
MUST return a "400 Bad Request" status-line.
...if the PUT request creates a new resource, a "201 Created" status-line is returned.
If an existing resource is modified, a "204 No Content" status-line is returned.
* Netconf: <edit-config> (nc:operation="create/replace")
* Note RFC8040 says that if an object is created, 201 is returned, if replaced 204
* is returned. But the restconf client does not know if it is replaced or created,
* only the server knows that. Solutions:
* 1) extend the netconf <ok/> so it returns if created/replaced. But that would lead
* to extension of netconf that may hit other places.
* 2) Send a get first and see if the resource exists, and then send replace/create.
* Will always produce an extra message and the GET may potetnially waste bw.
* 3) Try to create first, if that fails (with conflict) then try replace.
* --> Best solution and applied here
*/
int
api_data_put(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
restconf_media media_in;
media_in = restconf_content_type(h);
return api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 0, ds);
}
/*! Generic REST PATCH method for plain patch
* @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] media_out Output media
* Netconf: <edit-config> (nc:operation="merge")
* See RFC8040 Sec 4.6.1
* Plain patch can be used to create or update, but not delete, a child
* resource within the target resource.
* NOTE: If the target resource instance does not exist, the server MUST NOT
* create it. (CANT BE DONE WITH NETCONF)
*/
int
api_data_patch(clicon_handle h,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
restconf_media media_in;
int ret = -1;
media_in = restconf_content_type(h);
switch (media_in){
case YANG_DATA_XML:
case YANG_DATA_JSON: /* plain patch */
ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 1, ds);
break;
case YANG_PATCH_JSON: /* RFC 8072 patch */
case YANG_PATCH_XML:
#ifdef YANG_PATCH
ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_out, ds);
#else
ret = restconf_notimplemented(h, req, pretty, media_out);
#endif
break;
break;
default:
ret = restconf_unsupported_media(h, req, pretty, media_out);
break;
}
return ret;
}
/*! Generic REST DELETE method translated to edit-config
* @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] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* See RFC 8040 Sec 4.7
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
* Netconf: <edit-config> (nc:operation="delete")
*/
int
api_data_delete(clicon_handle h,
void *req,
char *api_path,
int pi,
int pretty,
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
int i;
cxobj *xtop = NULL; /* xpath root */
cxobj *xbot = NULL;
cxobj *xa;
cbuf *cbx = NULL;
yang_stmt *y = NULL;
yang_stmt *yspec;
enum operation_type op = OP_DELETE;
cxobj *xret = NULL;
cxobj *xretcom = NULL; /* return from commmit */
cxobj *xretdis = NULL; /* return from discard */
cxobj *xerr = NULL;
char *username;
int ret;
cxobj *xe; /* xml error, no free */
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
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(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
goto done;
xbot = xtop;
if (api_path){
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0)
goto done;
if (ret == 0){ /* validation failed */
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
if ((xa = xml_new("operation", xbot, CX_ATTR)) == NULL)
goto done;
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
if (xml_namespace_change(xa, NETCONF_BASE_NAMESPACE, NETCONF_BASE_PREFIX) < 0)
goto done;
if ((cbx = cbuf_new()) == NULL)
goto done;
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
cprintf(cbx, "<rpc xmlns=\"%s\" username=\"%s\" xmlns:%s=\"%s\" %s>",
NETCONF_BASE_NAMESPACE,
username?username:"",
NETCONF_BASE_PREFIX,
NETCONF_BASE_NAMESPACE,
NETCONF_MESSAGE_ID_ATTR); /* bind nc to netconf namespace */
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If this is a "data" request and 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.
*/
if ((IETF_DS_NONE == ds) &&
if_feature(yspec, "ietf-netconf", "startup") &&
!clicon_option_bool(h, "CLICON_RESTCONF_STARTUP_DONTUPDATE")){
cprintf(cbx, " copystartup=\"true\"");
}
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
if (clicon_xml2cbuf(cbx, xtop, 0, 0, -1) < 0)
goto done;
cprintf(cbx, "</edit-config></rpc>");
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, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (restconf_reply_send(req, 204, NULL, 0) < 0)
goto done;
ok:
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
if (xret)
xml_free(xret);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}