* Stricter handling of multi-namespace handling

* This occurs in cases where there are more than one XML namespaces in a config tree, such as `augment`:ed trees.
  * Affects all parts of the system, including datastore, backend, restconf and cli.
* Invalid api-path syntax (eg non-matching yang) error changed from 412 operation-failed to 400 Bad request invalid-value, or unknown-element.
This commit is contained in:
Olof hagsand 2019-09-29 14:45:08 +02:00
parent a547b3f31d
commit d9136c8972
22 changed files with 777 additions and 236 deletions

View file

@ -141,9 +141,15 @@ xml_copy_marked(cxobj *x0,
int iskey;
yang_stmt *yt;
char *name;
char *prefix;
assert(x0 && x1);
yt = xml_spec(x0); /* can be null */
/* Copy prefix*/
if ((prefix = xml_prefix(x0)) != NULL)
if (xml_prefix_set(x1, prefix) < 0)
goto done;
/* Copy all attributes */
x = NULL;
while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) {

View file

@ -74,6 +74,7 @@
#include "clixon_json.h"
#include "clixon_nacm.h"
#include "clixon_netconf_lib.h"
#include "clixon_yang_type.h"
#include "clixon_yang_module.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_map.h"
@ -83,45 +84,6 @@
#include "clixon_datastore_read.h"
#include "clixon_datastore_tree.h"
/*! Replace all xmlns attributes in x0 with xmlns attributes in x1
* This is an embryo of code to actually check if namespace binding is canonical
* and if it is not, either return error or transform to canonical.
* "Canonical" meaning comply to the yang module prefixes.
* The current code does not really do anything useful
*/
static int
replace_xmlns(cxobj *x0,
cxobj *x1)
{
int retval = -1;
cxobj *x = NULL;
cxobj *xcopy;
int i;
for (i=0; i<xml_child_nr(x0); ){
x = xml_child_i(x0, i);
if (!isxmlns(x)){
i++;
continue;
}
xml_rm(x);
xml_free(x);
}
x = NULL;
while ((x = xml_child_each(x1, x, CX_ATTR)) != NULL) {
/* split and only add xmlns= and xmlns:x= attributes! */
if (!isxmlns(x))
continue;
if ((xcopy = xml_new(xml_name(x), x0, xml_spec(x))) == NULL)
goto done;
if (xml_copy(x, xcopy) < 0) /* recursion */
goto done;
}
retval = 0;
done:
return retval;
}
/*! Given an attribute name and its expected namespace, find its value
*
* An attribute may have a prefix(or NULL). The routine finds the associated
@ -171,6 +133,206 @@ attr_ns_value(cxobj *x,
goto done;
}
/*! Given a src node x0 and a target node x1, assign (optional) prefix and namespace
* @param[in] x0 Source XML tree
* @param[in] x1 Target XML tree
* 1. Find N=namespace(x0)
* 2. Detect if N is declared in x1 parent
* 3. If yes, assign prefix to x1
* 4. If no, create new prefix/namespace binding and assign that to x1p (x1 if x1p is root)
* 5. Add prefix to x1, if any
* 6. Ensure x1 cache is updated
* @note switch use of x0 and x1 compared to datastore text_modify
* @see xml2ns
* XXX: fail handling: if (netconf_data_missing(cbret, NULL, "Data does not exist; cannot delete resource") < 0)
goto done;
*/
static int
check_namespaces(cxobj *x0,
cxobj *x1,
cxobj *x1p)
{
int retval = -1;
char *namespace = NULL;
char *prefix0 = NULL;;
char *prefix10 = NULL; /* extra just for malloc problem */
char *prefix1 = NULL;;
cvec *nsc0 = NULL;
cvec *nsc = NULL;
cxobj *xa = NULL;
cxobj *x;
int isroot;
/* XXX: need to identify root better than hiereustics and strcmp,... */
isroot = xml_parent(x1p)==NULL &&
strcmp(xml_name(x1p), "config") == 0 &&
xml_prefix(x1p)==NULL;
/* 1. Find N=namespace(x0) */
prefix0 = xml_prefix(x0);
if (xml2ns(x0, prefix0, &namespace) < 0)
goto done;
if (namespace == NULL){
clicon_err(OE_XML, ENOENT, "No namespace found for prefix:%s",
prefix0?prefix0:"NULL");
goto done;
}
/* 2. Detect if namespace is declared in x1:s parent */
if (xml2prefix(x1p, namespace, &prefix10) == 1){
if (prefix10){
if ((prefix1 = strdup(prefix10)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else
prefix1 = NULL;
/* 3. If yes, assign prefix to x1 */
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
goto done;
/* And copy namespace context from parent to child */
if ((nsc0 = nscache_get_all(x1p)) != NULL){
if ((nsc = cvec_dup(nsc0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
nscache_replace(x1, nsc);
}
/* Just in case */
if (nscache_set(x1, prefix1, namespace) < 0)
goto done;
}
else{
/* 4. If no, create new prefix/namespace binding and assign that to x1p
* use modules own default prefix (some chance for clash)
*/
if (prefix0 == NULL && !isroot){
assert(xml_spec(x1) != NULL);
prefix0 = yang_find_myprefix(xml_spec(x1));
}
if (prefix0)
if ((prefix1 = strdup(prefix0)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
/* Add binding to x1p. We add to parent due to heurestics, so we dont
* end up in adding it to large number of siblings
*/
if (isroot)
x = x1;
else
x = x1p;
if (nscache_set(x, prefix1, namespace) < 0)
goto done;
if (x == x1p){
if ((nsc0 = nscache_get_all(x1p)) != NULL)
if ((nsc = cvec_dup(nsc0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
/* Copy x1p cache to x1 */
nscache_replace(x1, nsc);
}
/* Create xmlns attribute to x1p/x1 XXX same code v */
if (prefix1){
if ((xa = xml_new(prefix1, x, NULL)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
}
else{
if ((xa = xml_new("xmlns", x, NULL)) == NULL)
goto done;
}
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, namespace) < 0)
goto done;
xml_sort(x, NULL); /* Ensure attr is first / XXX xml_insert? */
/* 5. Add prefix to x1, if any */
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
goto done;
}
/* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */
retval = 0;
done:
if (prefix1)
free(prefix1);
return retval;
}
static int
check_identityref(cxobj *x0,
cxobj *x1,
cxobj *x1p,
char *x1bstr,
yang_stmt *y)
{
int retval = -1;
yang_stmt *yrestype = NULL;
char *prefix = NULL;
char *ns0 = NULL;
char *ns1 = NULL;
cxobj *xa;
cxobj *x;
int isroot;
/* XXX: need to identify root better than hiereustics and strcmp,... */
isroot = xml_parent(x1p)==NULL &&
strcmp(xml_name(x1p), "config") == 0 &&
xml_prefix(x1p)==NULL;
if (yang_type_get(y, NULL, &yrestype,
NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (strcmp(yang_argument_get(yrestype), "identityref") != 0)
goto ok; /* skip */
if (nodeid_split(x1bstr, &prefix, NULL) < 0)
goto done;
if (prefix == NULL)
goto ok; /* skip */
if (xml2ns(x0, prefix, &ns0) < 0)
goto done;
if (xml2ns(x1, prefix, &ns1) < 0)
goto done;
if (ns0 != NULL){
if (ns1){
if (strcmp(ns0, ns1)){
clicon_err(OE_YANG, EFAULT, "identity namespace collision: %s: %s vs %s", x1bstr, ns0, ns1);
goto done;
}
}
else{
if (isroot)
x = x1;
else
x = x1p;
if (nscache_set(x, prefix, ns0) < 0)
goto done;
/* Create xmlns attribute to x1 XXX same code ^*/
if (prefix){
if ((xa = xml_new(prefix, x, NULL)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
}
else{
if ((xa = xml_new("xmlns", x, NULL)) == NULL)
goto done;
}
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, ns0) < 0)
goto done;
xml_sort(x, NULL); /* Ensure attr is first / XXX xml_insert? */
}
}
ok:
retval = 0;
done:
return retval;
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th Datastore text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
@ -208,12 +370,12 @@ text_modify(clicon_handle h,
char *opstr = NULL;
char *x1name;
char *x1cname; /* child name */
cxobj *x0a; /* attribute */
cxobj *x1a; /* attribute */
// cxobj *x0a; /* attribute */
// cxobj *x1a; /* attribute */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
char *xns; /* namespace */
// char *xns; /* namespace */
char *x0bstr; /* mod body string */
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
@ -303,7 +465,6 @@ text_modify(clicon_handle h,
}
} /* OP_MERGE & insert */
case OP_NONE: /* fall thru */
if (x0==NULL){
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)
@ -316,32 +477,14 @@ text_modify(clicon_handle h,
copied (see changed conditional below) */
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
goto done;
changed++;
/* Copy xmlns attributes ONLY, not op/insert etc */
x1a = NULL;
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */
if (strcmp(xml_value(x1a), YANG_XML_NAMESPACE)==0 ||
strcmp(xml_value(x1a), NETCONF_BASE_NAMESPACE)==0)
continue;
#endif
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
#if 0
/* If it is key I dont want to mark it */
if ((iamkey=yang_key_match(yang_parent_get(y0), x1name)) < 0)
/* Get namespace from x1
* Check if namespace exists in x0 parent
* if not add new binding and replace in x0.
*/
if (check_namespaces(x1, x0, x0p) < 0)
goto done;
if (!iamkey && op==OP_NONE)
#else
changed++;
if (op==OP_NONE)
#endif
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
if (x1bstr){ /* empty type does not have body */
if ((x0b = xml_new("body", x0, NULL)) == NULL)
@ -349,17 +492,9 @@ text_modify(clicon_handle h,
xml_type_set(x0b, CX_BODY);
}
}
else { /* if change existing node, replace xmlns attributes
* This is only done for leaf/leaf-list now, eg terminals
* and is only an embryo of checking canonical namespace
* bindings.
* But it does catch some cornercases where a new
* namespace binding is replacing an old for eg identityref
*/
if (replace_xmlns(x0, x1) < 0)
goto done;
}
if (x1bstr){
if (check_identityref(x1, x0, x0p, x1bstr, y0) < 0)
goto done;
if ((x0b = xml_body_get(x0)) != NULL){
x0bstr = xml_value(x0b);
if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){
@ -486,6 +621,8 @@ text_modify(clicon_handle h,
}
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
// XXX if (check_namespaces(x1, x0) < 0)
// goto done;
if (xml_copy(x1, x0) < 0)
goto done;
break;
@ -505,21 +642,12 @@ text_modify(clicon_handle h,
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
goto done;
changed++;
/* Copy xmlns attributes */
x1a = NULL;
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */
if (strcmp(xml_value(x1a), YANG_XML_NAMESPACE)==0 ||
strcmp(xml_value(x1a), NETCONF_BASE_NAMESPACE)==0)
continue;
#endif
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
/* Get namespace from x1
* Check if namespace exists in x0 parent
* if not add new binding and replace in x0.
*/
if (check_namespaces(x1, x0, x0p) < 0)
goto done;
if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
}
@ -534,23 +662,30 @@ text_modify(clicon_handle h,
i = 0;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
/* Get yang spec of the child by child matching */
if ((yc = yang_find_datanode(y0, x1cname)) == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
goto done;
}
/* There is a cornercase (eg augment) of multi-namespace trees where
* the yang child has a different namespace.
* As an alternative, return in populate where this is detected first time.
*/
if (yc != xml_spec(x1c)){
clicon_err(OE_YANG, errno, "XML node %s not in namespace %s",
x1cname, yang_find_mynamespace(y0));
goto done;
}
/* See if there is a corresponding node in the base tree */
x0c = NULL;
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
#if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
#endif
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
}
/* Second pass: Loop through children of the x1 modification tree again
@ -725,14 +860,12 @@ text_modify_top(clicon_handle h,
/* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
#if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
#endif
if ((ret = text_modify(h, x0c, yc, x0, x1c, op,
username, xnacm, permit, cbret)) < 0)
goto done;

View file

@ -584,7 +584,8 @@ clicon_rpc_get(clicon_handle h,
if ((username = clicon_username_get(h)) != NULL)
cprintf(cb, " username=\"%s\"", username);
if (namespace)
cprintf(cb, " xmlns:nc=\"%s\"", NETCONF_BASE_NAMESPACE);
cprintf(cb, " xmlns:%s=\"%s\"",
NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
cprintf(cb, "><get");
/* Clixon extension, content=all,config, or nonconfig */
if (content != -1)
@ -627,6 +628,79 @@ clicon_rpc_get(clicon_handle h,
return retval;
}
int
clicon_rpc_get_nsc(clicon_handle h,
char *xpath,
cvec *nsc, /* namespace context for filter */
netconf_content content,
int32_t depth,
cxobj **xt)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xd;
char *username;
cg_var *cv = NULL;
char *prefix;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc");
if ((username = clicon_username_get(h)) != NULL)
cprintf(cb, " username=\"%s\"", username);
cprintf(cb, " xmlns:%s=\"%s\"",
NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
cprintf(cb, "><get");
/* Clixon extension, content=all,config, or nonconfig */
if (content != -1)
cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
/* Clixon extension, depth=<level> */
if (depth != -1)
cprintf(cb, " depth=\"%d\"", depth);
cprintf(cb, ">");
if (xpath && strlen(xpath)) {
cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"",
NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX,
xpath);
while ((cv = cvec_each(nsc, cv)) != NULL){
cprintf(cb, " xmlns");
if ((prefix = cv_name_get(cv)))
cprintf(cb, ":%s", prefix);
cprintf(cb, "=\"%s\"", cv_string_get(cv));
}
cprintf(cb, "/>");
}
cprintf(cb, "</get></rpc>");
if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
/* Send xml error back: first check error, then ok */
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
xd = xml_parent(xd); /* point to rpc-reply */
else if ((xd = xpath_first(xret, "/rpc-reply/data")) == NULL)
if ((xd = xml_new("data", NULL, NULL)) == NULL)
goto done;
if (xt){
if (xml_rm(xd) < 0)
goto done;
*xt = xd;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Close a (user) session
* @param[in] h CLICON handle
* @retval 0 OK

View file

@ -197,9 +197,9 @@ xml_name_set(cxobj *xn,
return 0;
}
/*! Get namespace of xnode
* @param[in] xn xml node
* @retval namespace of xml node
/*! Get prefix of xnode
* @param[in] xn xml node
* @retval prefix of xml node
*/
char*
xml_prefix(cxobj *xn)
@ -207,9 +207,9 @@ xml_prefix(cxobj *xn)
return xn->x_prefix;
}
/*! Set name space of xnode, namespace is copied
/*! Set prefix of xnode, prefix is copied
* @param[in] xn xml node
* @param[in] localname new namespace, null-terminated string, copied by function
* @param[in] localname new prefix, null-terminated string, copied by function
* @retval -1 on error with clicon-err set
* @retval 0 OK
*/
@ -230,14 +230,14 @@ xml_prefix_set(cxobj *xn,
return 0;
}
/*! Get cached namespace
/*! Get cached namespace (given prefix)
* @param[in] x XML node
* @param[in] prefix Namespace prefix, or NULL for default
* @retval ns Cached namespace
* @retval NULL No namespace found (not cached or not found)
* @note may want to distinguish between not set cache and no namespace?
*/
static char*
char*
nscache_get(cxobj *x,
char *prefix)
{
@ -246,6 +246,35 @@ nscache_get(cxobj *x,
return NULL;
}
/*! Get cached prefix (given namespace)
* @param[in] x XML node
* @param[in] namespace
* @param[out] prefix
* @retval 0 No prefix found
* @retval 1 Prefix found
*/
int
nscache_get_prefix(cxobj *x,
char *namespace,
char **prefix)
{
if (x->x_ns_cache != NULL)
return xml_nsctx_get_prefix(x->x_ns_cache, namespace, prefix);
return 0;
}
/*! Dump whole namespacet context cache of one xml node
* @param[in] x XML node
* @retval nsc Whole namespace context of x
* @retval NULL Empty nsc
* @see nscache_get For a single prefix
*/
cvec *
nscache_get_all(cxobj *x)
{
return x->x_ns_cache;
}
/*! Set cached namespace for specific namespace. Replace if necessary
* @param[in] x XML node
* @param[in] prefix Namespace prefix, or NULL for default
@ -254,7 +283,7 @@ nscache_get(cxobj *x,
* @retval -1 Error
* @see nscache_replace to replace the whole context
*/
static int
int
nscache_set(cxobj *x,
char *prefix,
char *namespace)
@ -281,7 +310,7 @@ nscache_set(cxobj *x,
*/
int
nscache_replace(cxobj *x,
cvec *nsc)
cvec *nsc)
{
int retval = -1;
@ -405,6 +434,63 @@ xmlns_set(cxobj *x,
return retval;
}
/*! Get namespace given prefix recursively
* @param[in] xn XML node
* @param[in] namespace Namespace
* @param[out] prefixp Pointer to prefix if found
* @retval -1 Error
* @retval 0 No namespace found
* @retval 1 Namespace found
*/
int
xml2prefix(cxobj *xn,
char *namespace,
char **prefixp)
{
int retval = -1;
cxobj *xa = NULL;
char *prefix = NULL;
while (xn != NULL){
if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */
goto found;
// if (xn->x_ns_cache == NULL){ /* Look in node */
xa = NULL;
while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL) {
/* xmlns=namespace */
if (strcmp("xmlns", xml_name(xa)) == 0){
if (strcmp(xml_value(xa), namespace) == 0){
clicon_debug(1, "%sA NULL %s", __FUNCTION__, namespace);
if (nscache_set(xn, NULL, namespace) < 0)
goto done;
prefix = NULL;
goto found;
}
}
/* xmlns:prefix=namespace */
else if (strcmp("xmlns", xml_prefix(xa)) == 0){
if (strcmp(xml_value(xa), namespace) == 0){
prefix = xml_name(xa);
assert(strcmp(prefix, "xmlns"));
if (nscache_set(xn, prefix, namespace) < 0)
goto done;
goto found;
}
}
}
// }
xn = xml_parent(xn);
}
retval = 0;
done:
return retval;
found:
*prefixp = prefix;
retval = 1;
goto done;
}
/*! See if xmlns:[<localname>=]<uri> exists, if so return <uri>
*
* @param[in] xn XML node
@ -982,6 +1068,7 @@ int
xml_addsub(cxobj *xp,
cxobj *xc)
{
int retval = -1;
cxobj *oldp;
int i;
char *pns = NULL; /* parent namespace */
@ -1000,13 +1087,14 @@ xml_addsub(cxobj *xp,
/* Add xc to new parent */
if (xp){
if (xml_child_append(xp, xc) < 0)
return -1;
goto done;
/* Set new parent in child */
xml_parent_set(xc, xp);
/* Ensure default namespace is not duplicated
* here only remove duplicate default namespace, there may be more */
/* 1. Get parent default namespace */
xml2ns(xp, NULL, &pns);
if (xml2ns(xp, NULL, &pns) < 0)
goto done;
/* 2. Get child default namespace */
if (pns &&
(xa = xml_find_type(xc, NULL, "xmlns", CX_ATTR)) != NULL &&
@ -1016,9 +1104,11 @@ xml_addsub(cxobj *xp,
xml_purge(xa);
}
/* clear namespace context cache of child */
nscache_clear(xp);
nscache_clear(xc);
}
return 0;
retval = 0;
done:
return retval;
}
/*! Wrap a new node between a parent xml node (xp) and all its children

View file

@ -1749,54 +1749,59 @@ yang2api_path_fmt_1(yang_stmt *ys,
cbuf *cb)
{
yang_stmt *yp; /* parent */
yang_stmt *ymod;
int i;
cvec *cvk = NULL; /* vector of index keys */
int retval = -1;
if ((yp = ys->ys_parent) == NULL){
clicon_err(OE_YANG, EINVAL, "yang expected parent %s", ys->ys_argument);
if ((yp = yang_parent_get(ys)) == NULL){
clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys));
goto done;
}
if (yp != NULL && /* XXX rm */
yp->ys_keyword != Y_MODULE &&
yp->ys_keyword != Y_SUBMODULE){
yang_keyword_get(yp) != Y_MODULE &&
yang_keyword_get(yp) != Y_SUBMODULE){
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
goto done;
if (yp->ys_keyword != Y_CHOICE && yp->ys_keyword != Y_CASE){
if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){
#if 0
/* In some cases, such as cli_show_auto, a trailing '/' should
* NOT be present if ys is a key in a list.
* But in other cases (I think most), the / should be there,
* so a patch is added in cli_show_auto instead.
*/
if (ys->ys_keyword == Y_LEAF && yp &&
yp->ys_keyword == Y_LIST &&
if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST &&
yang_key_match(yp, ys->ys_argument) == 1)
;
else
#endif
cprintf(cb, "/");
cprintf(cb, "/");
}
/* If parent namespace/module is different from child -> add child prefix */
if (ys_real_module(yp) != (ymod = ys_real_module(ys)))
cprintf(cb, "%s:", yang_argument_get(ymod));
}
else /* top symbol - mark with name prefix */
cprintf(cb, "/%s:", yp->ys_argument);
cprintf(cb, "/%s:", yang_argument_get(yp));
if (inclkey){
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "%s", ys->ys_argument);
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
cprintf(cb, "%s", yang_argument_get(ys));
}
else{
if (ys->ys_keyword == Y_LEAF && yp &&
yp->ys_keyword == Y_LIST){
if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */
if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST){
if (yang_key_match(yp, yang_argument_get(ys)) == 0)
cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */
}
else
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "%s", ys->ys_argument);
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
cprintf(cb, "%s", yang_argument_get(ys));
}
switch (ys->ys_keyword){
switch (yang_keyword_get(ys)){
case Y_LIST:
cvk = ys->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
if (cvec_len(cvk))
@ -2168,6 +2173,8 @@ xml_default(cxobj *xt,
cxobj *xb;
char *str;
int added=0;
char *namespace;
char *prefix;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
@ -2183,9 +2190,16 @@ xml_default(cxobj *xt,
assert(y->ys_cv);
if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */
if (!xml_find(xt, y->ys_argument)){
if ((xc = xml_new(y->ys_argument, NULL, y)) == NULL)
goto done;
/* assign right prefix */
if ((namespace = yang_find_mynamespace(y)) != NULL){
prefix = NULL;
if (xml2prefix(xt, namespace, &prefix))
if (xml_prefix_set(xc, prefix) < 0)
goto done;
}
xml_flag_set(xc, XML_FLAG_DEFAULT);
if ((xb = xml_new("body", xc, NULL)) == NULL)
goto done;
@ -2336,6 +2350,8 @@ xml_spec_populate(cxobj *x,
yang_stmt *ymod; /* yang module */
cxobj *xp = NULL; /* xml parent */
char *name;
char *ns = NULL; /* XML namespace of x */
char *nsy = NULL; /* Yang namespace of x */
yspec = (yang_stmt*)arg;
xp = xml_parent(x);
@ -2343,8 +2359,11 @@ xml_spec_populate(cxobj *x,
#ifdef DEBUG
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
#endif
if (xp && (yparent = xml_spec(xp)) != NULL)
if (xml2ns(x, xml_prefix(x), &ns) < 0)
goto done;
if (xp && (yparent = xml_spec(xp)) != NULL){
y = yang_find_datanode(yparent, name);
}
else if (yspec){
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
@ -2361,10 +2380,17 @@ xml_spec_populate(cxobj *x,
#endif
}
if (y) {
nsy = yang_find_mynamespace(y);
if (ns == NULL || nsy == NULL){
clicon_err(OE_XML, EFAULT, "Namespace NULL");
goto done;
}
#ifdef DEBUG
clicon_debug(1, "%s y:%s", __FUNCTION__, yang_argument_get(y));
#endif
xml_spec_set(x, y);
/* Assign spec only if namespaces match */
if (strcmp(ns, nsy) == 0)
xml_spec_set(x, y);
}
#ifdef DEBUG
else
@ -2387,13 +2413,7 @@ xml_spec_populate(cxobj *x,
* @retval 0 Invalid api_path or associated XML, netconf error xml set
* @retval -1 Fatal error, clicon_err called
*
* @note both retval -1 set clicon_err, retval 0 sets netconf xml msg
* @note Not proper namespace translation from api-path 2 xpath
* It works like this:
* Assume origin incoming path is
* "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is:
* ["www.foo.com" "restconf" "a" "b=c"]
* which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
* @code
* cbuf *xpath = cbuf_new();
* cvec *cvv = NULL;
@ -2405,7 +2425,14 @@ xml_spec_populate(cxobj *x,
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath)
* @endcode
* It works like this:
* Assume origin incoming path is
* "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is:
* ["www.foo.com" "restconf" "a" "b=c"]
* which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* @note retval -1 sets clicon_err, retval 0 sets netconf xml msg
* @note Not proper namespace translation from api-path 2 xpath!!!
* @see api_path2xml For api-path to xml tree
* @see api_path2xpath Using strings as parameters
*/
@ -2438,7 +2465,8 @@ api_path2xpath_cvv(cvec *api_path,
nodeid = cv_name_get(cv);
if (nodeid_split(nodeid, &prefix, &name) < 0)
goto done;
clicon_debug(1, "%s [%d] cvname: %s:%s", __FUNCTION__, i, prefix?prefix:"", name);
clicon_debug(1, "%s [%d] cvname: %s:%s",
__FUNCTION__, i, prefix?prefix:"", name);
if (i == offset){ /* top-node */
if (prefix == NULL){
if ((cberr = cbuf_new()) == NULL){
@ -2536,6 +2564,170 @@ api_path2xpath_cvv(cvec *api_path,
goto done;
}
/*! Temp alternative to api_path2xpath_cvv */
int
api_path2xpath_cvv2(cvec *api_path,
int offset,
yang_stmt *yspec,
cbuf *xpath,
cvec *nsc,
cxobj **xerr)
{
int retval = -1;
int i;
cg_var *cv;
char *nodeid;
char *prefix = NULL; /* api-path (module) prefix */
char *xprefix = NULL; /* xml xpath prefix */
char *name = NULL;
cvec *cvk = NULL; /* vector of index keys */
yang_stmt *y = NULL;
yang_stmt *ymod = NULL;
char *val;
cg_var *cvi;
char **valvec = NULL;
int vi;
int nvalvec;
cbuf *cberr = NULL;
char *namespace = NULL;
for (i=offset; i<cvec_len(api_path); i++){
cv = cvec_i(api_path, i);
nodeid = cv_name_get(cv);
/* api-path: prefix points to module */
if (nodeid_split(nodeid, &prefix, &name) < 0)
goto done;
clicon_debug(1, "%s [%d] cvname: %s:%s",
__FUNCTION__, i, prefix?prefix:"", name);
/* top-node must have prefix */
if (i == offset && prefix == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "'%s': Expected prefix:name", nodeid);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
ymod = NULL;
if (prefix){ /* if prefix -> get module + change namespace */
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "No such yang module: %s", prefix);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod); /* change namespace */
}
if (i == offset && ymod) /* root */
y = yang_find_datanode(ymod, name);
else
y = yang_find_datanode(y, name);
if (y == NULL){
if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
goto done;
goto fail;
}
/* Get XML/xpath prefix given namespace.
* note different from api-path prefix
*/
if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){
xprefix = yang_find_myprefix(y);
clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
/* not found, add it to nsc */
if (xml_nsctx_set(nsc, xprefix, namespace) < 0)
goto done;
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
switch (yang_keyword_get(y)){
case Y_LIST:
/* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3)
* Note that vnr can be < length of cvk, due to empty or unset values
*/
if (valvec){ /* loop, valvec may have been used before */
free(valvec);
valvec = NULL;
}
if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL)
goto done;
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
vi = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL && vi<nvalvec){
cprintf(xpath, "[");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s='%s']", cv_string_get(cvi), valvec[vi++]);
}
break;
case Y_LEAF_LIST: /* XXX: LOOP? */
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
if (val)
cprintf(xpath, "[.='%s']", val);
else
cprintf(xpath, "[.='']");
break;
default:
if (i != offset)
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
break;
}
if (val)
free(val);
}
else{
if (i != offset)
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
}
if (prefix){
free(prefix);
prefix = NULL;
}
if (name){
free(name);
name = NULL;
}
} /* for */
retval = 1; /* OK */
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cberr != NULL)
cbuf_free(cberr);
if (valvec)
free(valvec);
if (prefix)
free(prefix);
if (name)
free(name);
return retval;
fail:
retval = 0; /* Validation failed */
goto done;
}
/*! Translate from restconf api-path to xml xpath and namespace
* @param[in] api_path URI-encoded path expression" (RFC8040 3.5.3)
* @param[in] yspec Yang spec
@ -2674,7 +2866,6 @@ api_path2xml_vec(char **vec,
}
namespace = yang_find_mynamespace(ymod);
y0 = ymod;
}
y = (nodeclass==YC_SCHEMANODE)?
yang_find_schemanode(y0, name):
@ -2683,6 +2874,13 @@ api_path2xml_vec(char **vec,
clicon_err(OE_YANG, EINVAL, "api-path name: '%s', no such yang element", name);
goto fail;
}
if (prefix && namespace == NULL){
if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){
clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix);
goto fail;
}
namespace = yang_find_mynamespace(ymod);
}
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (0 && restval==NULL){

View file

@ -94,8 +94,8 @@ xml_nsctx_get(cvec *cvv,
* @note NULL is a valid prefix (default)
*/
int
xml_nsctx_get_prefix(cvec *cvv,
char *namespace,
xml_nsctx_get_prefix(cvec *cvv,
char *namespace,
char **prefix)
{
cg_var *cv = NULL;

View file

@ -338,7 +338,6 @@ xml_cmp_qsort(const void* arg1,
return xml_cmp(*(struct xml**)arg1, *(struct xml**)arg2, 1);
}
/*! Sort children of an XML node
* Assume populated by yang spec.
* @param[in] x0 XML node
@ -615,7 +614,11 @@ xml_insert2(cxobj *xp,
}
xc = xml_child_i(xp, mid);
if ((yc = xml_spec(xc)) == NULL){
clicon_err(OE_XML, 0, "No spec found %s", xml_name(xc));
if (xml_type(xc) != CX_ELMNT)
clicon_err(OE_XML, 0, "No spec found %s (wrong xml type:%d)",
xml_name(xc), xml_type(xc));
else
clicon_err(OE_XML, 0, "No spec found %s", xml_name(xc));
goto done;
}
if (yc == yn){ /* Same yang */