1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6 Copyright (C) 2017-2019 Olof Hagsand
7 Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
8
9 This file is part of CLIXON.
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23 Alternatively, the contents of this file may be used under the terms of
24 the GNU General Public License Version 3 or later (the "GPL"),
25 in which case the provisions of the GPL are applicable instead
26 of those above. If you wish to allow use of your version of this file only
27 under the terms of the GPL, and not to allow others to
28 use your version of this file under the terms of Apache License version 2,
29 indicate your decision by deleting the provisions above and replace them with
30 the notice and other provisions required by the GPL. If you do not delete
31 the provisions above, a recipient may use your version of this file under
32 the terms of any one of the Apache License version 2 or the GPL.
33
34 ***** END LICENSE BLOCK *****
35
36 * XML support functions.
37 * @see https://www.w3.org/TR/2009/REC-xml-names-20091208
38 * An xml namespace context is a cligen variable vector containing a list of
39 * <prefix,namespace> pairs.
40 * It is encoded in a cvv as a list of string values, where the c name is the
41 * prefix and the string values are the namespace URI.
42 * The default namespace is decoded as having the name NULL
43 */
44
45 #ifdef HAVE_CONFIG_H
46 #include "clixon_config.h" /* generated by config & autoconf */
47 #endif
48
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <unistd.h>
52 #include <errno.h>
53 #include <string.h>
54 #include <limits.h>
55 #include <stdint.h>
56
57 /* cligen */
58 #include <cligen/cligen.h>
59
60 /* clixon */
61 #include "clixon_err.h"
62 #include "clixon_string.h"
63 #include "clixon_queue.h"
64 #include "clixon_hash.h"
65 #include "clixon_handle.h"
66 #include "clixon_log.h"
67 #include "clixon_options.h"
68 #include "clixon_yang.h"
69 #include "clixon_xml.h"
70 #include "clixon_yang_module.h"
71 #include "clixon_xml_nsctx.h"
72
73 /* Undefine if you want to ensure strict namespace assignment on all netconf
74 * and XML statements according to the standard RFC 6241.
75 * If defined, top-level rpc calls need not have namespaces (eg using xmlns=<ns>)
76 * since the default NETCONF namespace will be assumed. (This is not standard).
77 * See rfc6241 3.1: urn:ietf:params:xml:ns:netconf:base:1.0.
78 */
79 static int _USE_NAMESPACE_NETCONF_DEFAULT = 0;
80
81 /*! Set if use internal default namespace mechanism or not
82 *
83 * This function shouldnt really be here, it sets a local variable from the value of the
84 * option CLICON_NAMESPACE_NETCONF_DEFAULT, but the code where it is used is deep in the call
85 * stack and cannot get the clicon handle currently.
86 * @param[in] h Clicon handle
87 */
88 int
xml_nsctx_namespace_netconf_default(clicon_handle h)89 xml_nsctx_namespace_netconf_default(clicon_handle h)
90 {
91 _USE_NAMESPACE_NETCONF_DEFAULT = clicon_option_bool(h, "CLICON_NAMESPACE_NETCONF_DEFAULT");
92 return 0;
93 }
94
95 /*! Create and initialize XML namespace context
96 * @param[in] prefix Namespace prefix, or NULL for default
97 * @param[in] ns Set this namespace. If NULL create empty nsctx
98 * @retval nsc Return namespace context in form of a cvec
99 * @retval NULL Error
100 * @code
101 * cvec *nsc = NULL;
102 * if ((nsc = xml_nsctx_init(NULL, "urn:example:example")) == NULL)
103 * err;
104 * ...
105 * xml_nsctx_free(nsc);
106 * @endcode
107 * @see xml_nsctx_node Use namespace context of an existing XML node
108 * @see xml_nsctx_free Free the reutned handle
109 */
110 cvec *
xml_nsctx_init(char * prefix,char * ns)111 xml_nsctx_init(char *prefix,
112 char *ns)
113 {
114 cvec *cvv = NULL;
115
116 if ((cvv = cvec_new(0)) == NULL){
117 clicon_err(OE_XML, errno, "cvec_new");
118 goto done;
119 }
120 if (ns && xml_nsctx_add(cvv, prefix, ns) < 0)
121 goto done;
122 done:
123 return cvv;
124 }
125
126 /*! Free XML namespace context
127 * @param[in] prefix Namespace prefix, or NULL for default
128 * @param[in] namespace Cached namespace to set (assume non-null?)
129 * @retval nsc Return namespace context in form of a cvec
130 * @retval NULL Error
131 */
132 int
xml_nsctx_free(cvec * nsc)133 xml_nsctx_free(cvec *nsc)
134 {
135 cvec *cvv = (cvec*)nsc;
136
137 if (cvv)
138 cvec_free(cvv);
139 return 0;
140 }
141
142 /*! Get namespace given prefix (or NULL for default) from namespace context
143 * @param[in] cvv Namespace context
144 * @param[in] prefix Namespace prefix, or NULL for default
145 * @retval ns Cached namespace
146 * @retval NULL No namespace found (not cached or not found)
147 */
148 char*
xml_nsctx_get(cvec * cvv,char * prefix)149 xml_nsctx_get(cvec *cvv,
150 char *prefix)
151 {
152 cg_var *cv;
153
154 if ((cv = cvec_find(cvv, prefix)) != NULL)
155 return cv_string_get(cv);
156 return NULL;
157 }
158
159 /*! Reverse get prefix given namespace
160 * @param[in] cvv Namespace context
161 * @param[in] ns Namespace
162 * @param[out] prefix Prefix (direct pointer)
163 * @retval 0 No prefix found
164 * @retval 1 Prefix found
165 * @note NULL is a valid prefix (default)
166 */
167 int
xml_nsctx_get_prefix(cvec * cvv,char * ns,char ** prefix)168 xml_nsctx_get_prefix(cvec *cvv,
169 char *ns,
170 char **prefix)
171 {
172 cg_var *cv = NULL;
173 char *ns0 = NULL;
174
175 while ((cv = cvec_each(cvv, cv)) != NULL){
176 if ((ns0 = cv_string_get(cv)) != NULL &&
177 strcmp(ns0, ns) == 0){
178 if (prefix)
179 *prefix = cv_name_get(cv); /* can be NULL */
180 return 1;
181 }
182 }
183 if (prefix)
184 *prefix = NULL;
185 return 0;
186 }
187
188 /*! Set or replace namespace in namespace context
189 * @param[in] cvv Namespace context
190 * @param[in] prefix Namespace prefix, or NULL for default
191 * @param[in] ns Cached namespace to set (assume non-null?)
192 * @retval 0 OK
193 * @retval -1 Error
194 */
195 int
xml_nsctx_add(cvec * cvv,char * prefix,char * ns)196 xml_nsctx_add(cvec *cvv,
197 char *prefix,
198 char *ns)
199 {
200 int retval = -1;
201 cg_var *cv;
202
203 if ((cv = cvec_find(cvv, prefix)) != NULL) /* found, replace that */
204 cv_string_set(cv, ns);
205 else /* cvec exists, but not prefix */
206 cvec_add_string(cvv, prefix, ns);
207 retval = 0;
208 // done:
209 return retval;
210 }
211
212 static int
xml_nsctx_node1(cxobj * xn,cvec * nsc)213 xml_nsctx_node1(cxobj *xn,
214 cvec *nsc)
215 {
216 int retval = -1;
217 cxobj *xa = NULL;
218 char *pf; /* prefix */
219 char *nm; /* name */
220 char *val; /* value */
221 cxobj *xp; /* parent */
222
223 /* xmlns:t="<ns1>" prefix:xmlns, name:t
224 * xmlns="<ns2>" prefix:NULL name:xmlns
225 */
226 while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL){
227 pf = xml_prefix(xa);
228 nm = xml_name(xa);
229 if (pf == NULL){
230 if (strcmp(nm, "xmlns")==0 && /* set default namespace context */
231 xml_nsctx_get(nsc, NULL) == NULL){
232 val = xml_value(xa);
233 if (xml_nsctx_add(nsc, NULL, val) < 0)
234 goto done;
235 }
236 }
237 else
238 if (strcmp(pf, "xmlns")==0 && /* set prefixed namespace context */
239 xml_nsctx_get(nsc, nm) == NULL){
240 val = xml_value(xa);
241 if (xml_nsctx_add(nsc, nm, val) < 0)
242 goto done;
243 }
244 }
245 if ((xp = xml_parent(xn)) == NULL){
246 if (_USE_NAMESPACE_NETCONF_DEFAULT){
247 /* If not default namespace defined, use the base netconf ns as default */
248 if (xml_nsctx_get(nsc, NULL) == NULL)
249 if (xml_nsctx_add(nsc, NULL, NETCONF_BASE_NAMESPACE) < 0)
250 goto done;
251 }
252 }
253 else
254 if (xml_nsctx_node1(xp, nsc) < 0)
255 goto done;
256 retval = 0;
257 done:
258 return retval;
259 }
260
261 /*! Create and initialize XML namespace from XML node context
262 * Fully explore all prefix:namespace pairs from context of one node
263 * @param[in] xn XML node
264 * @param[out] ncp XML namespace context
265 * @retval 0 OK
266 * @retval -1 Error
267 * @code
268 * cxobj *x; // must initialize
269 * cvec *nsc = NULL;
270 * if (xml_nsctx_node(x, &nsc) < 0)
271 * err
272 * ...
273 * xml_nsctx_free(nsc)
274 * @endcode
275 * @see xml_nsctx_init
276 * @see xml_nsctx_free Free the returned handle
277 */
278 int
xml_nsctx_node(cxobj * xn,cvec ** ncp)279 xml_nsctx_node(cxobj *xn,
280 cvec **ncp)
281 {
282 int retval = -1;
283 cvec *nc = NULL;
284
285 if ((nc = cvec_new(0)) == NULL){
286 clicon_err(OE_XML, errno, "cvec_new");
287 goto done;
288 }
289 if (xml_nsctx_node1(xn, nc) < 0)
290 goto done;
291 *ncp = nc;
292 retval = 0;
293 done:
294 return retval;
295 }
296
297 /*! Create and initialize XML namespace context from Yang node
298 * Primary use is Yang path statements, eg leafrefs and others
299 * Fully explore all prefix:namespace pairs from context of one node
300 * @param[in] yn Yang statement in module tree (or module itself)
301 * @param[out] ncp XML namespace context
302 * @retval 0 OK
303 * @retval -1 Error
304 * @code
305 * yang_stmt *y; // must initialize
306 * cvec *nsc = NULL;
307 * if (xml_nsctx_yang(y, &nsc) < 0)
308 * err
309 * ...
310 * xml_nsctx_free(nsc)
311 * @endcode
312 * @see RFC7950 Sections 6.4.1 (and 9.9.2?)
313 * @note Assume yn is in a yang structure (eg has parents and belongs to a (sub)module)
314 */
315 int
xml_nsctx_yang(yang_stmt * yn,cvec ** ncp)316 xml_nsctx_yang(yang_stmt *yn,
317 cvec **ncp)
318 {
319 int retval = -1;
320 cvec *nc = NULL;
321 yang_stmt *yspec;
322 yang_stmt *ymod; /* yang main module/submodule node */
323 yang_stmt *yp; /* yang prefix node */
324 yang_stmt *ym; /* yang imported module */
325 yang_stmt *yns; /* yang namespace */
326 yang_stmt *y;
327 char *name;
328 char *namespace;
329 char *prefix;
330 char *mynamespace;
331 char *myprefix;
332
333 if ((nc = cvec_new(0)) == NULL){
334 clicon_err(OE_XML, errno, "cvec_new");
335 goto done;
336 }
337 if ((myprefix = yang_find_myprefix(yn)) == NULL){
338 clicon_err(OE_YANG, ENOENT, "My yang prefix not found");
339 goto done;
340 }
341 if ((mynamespace = yang_find_mynamespace(yn)) == NULL){
342 clicon_err(OE_YANG, ENOENT, "My yang namespace not found");
343 goto done;
344 }
345 /* Add my prefix and default namespace (from real module) */
346 if (xml_nsctx_add(nc, NULL, mynamespace) < 0)
347 goto done;
348 if (xml_nsctx_add(nc, myprefix, mynamespace) < 0)
349 goto done;
350 /* Find top-most module or sub-module and get prefixes from that */
351 if ((ymod = ys_module(yn)) == NULL){
352 clicon_err(OE_YANG, ENOENT, "My yang module not found");
353 goto done;
354 }
355 yspec = yang_parent_get(ymod); /* Assume yspec exists */
356
357 /* Iterate over module and register all import prefixes
358 */
359 y = NULL;
360 while ((y = yn_each(ymod, y)) != NULL) {
361 if (yang_keyword_get(y) == Y_IMPORT){
362 if ((name = yang_argument_get(y)) == NULL)
363 continue; /* Just skip - shouldnt happen) */
364 if ((yp = yang_find(y, Y_PREFIX, NULL)) == NULL)
365 continue;
366 if ((prefix = yang_argument_get(yp)) == NULL)
367 continue;
368 if ((ym = yang_find(yspec, Y_MODULE, name)) == NULL)
369 continue;
370 if ((yns = yang_find(ym, Y_NAMESPACE, NULL)) == NULL)
371 continue;
372 if ((namespace = yang_argument_get(yns)) == NULL)
373 continue;
374 if (xml_nsctx_add(nc, prefix, namespace) < 0)
375 goto done;
376 }
377 }
378 *ncp = nc;
379 retval = 0;
380 done:
381 return retval;
382 }
383
384 /*! Create and initialize XML namespace context from Yang spec
385 *
386 * That is, create a "canonical" XML namespace mapping from all loaded yang
387 * modules which are children of the yang specification.
388 * Also add netconf base namespace: nc , urn:ietf:params:xml:ns:netconf:base:1.0
389 * Fully explore all prefix:namespace pairs of all yang modules
390 * @param[in] yspec Yang spec
391 * @param[out] ncp XML namespace context
392 * @retval 0 OK
393 * @retval -1 Error
394 * @code
395 * cvec *nsc = NULL;
396 * yang_stmt *yspec = clicon_dbspec_yang(h);
397 * if (xml_nsctx_yangspec(yspec, &nsc) < 0)
398 * goto done;
399 * ...
400 * cvec_free(nsc);
401 * @endcode
402 */
403 int
xml_nsctx_yangspec(yang_stmt * yspec,cvec ** ncp)404 xml_nsctx_yangspec(yang_stmt *yspec,
405 cvec **ncp)
406 {
407 int retval = -1;
408 cvec *nc = NULL;
409 yang_stmt *ymod = NULL;
410 yang_stmt *yprefix;
411 yang_stmt *ynamespace;
412
413 if ((nc = cvec_new(0)) == NULL){
414 clicon_err(OE_XML, errno, "cvec_new");
415 goto done;
416 }
417 ymod = NULL;
418 while ((ymod = yn_each(yspec, ymod)) != NULL){
419 if (yang_keyword_get(ymod) != Y_MODULE)
420 continue;
421 if ((yprefix = yang_find(ymod, Y_PREFIX, NULL)) == NULL)
422 continue;
423 if ((ynamespace = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL)
424 continue;
425 if (xml_nsctx_add(nc, yang_argument_get(yprefix), yang_argument_get(ynamespace)) < 0)
426 goto done;
427 }
428 /* Add base netconf namespace as default and "nc" prefix */
429 if (xml_nsctx_add(nc, NULL, NETCONF_BASE_NAMESPACE) < 0)
430 goto done;
431 if (xml_nsctx_add(nc, NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE) < 0)
432 goto done;
433 *ncp = nc;
434 retval = 0;
435 done:
436 return retval;
437 }
438
439 /*! Given an xml tree return URI namespace recursively : default or localname given
440 *
441 * Given an XML tree and a prefix (or NULL) return URI namespace.
442 * @param[in] x XML tree
443 * @param[in] prefix prefix/ns localname. If NULL then return default.
444 * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree
445 * @retval 0 OK
446 * @retval -1 Error
447 * @code
448 * if (xml2ns(xt, NULL, &namespace) < 0)
449 * err;
450 * @endcode
451 * @see xmlns_set cache is set
452 * @note, this function uses a cache.
453 */
454 int
xml2ns(cxobj * x,char * prefix,char ** namespace)455 xml2ns(cxobj *x,
456 char *prefix,
457 char **namespace)
458 {
459 int retval = -1;
460 char *ns = NULL;
461 cxobj *xp;
462
463 if ((ns = nscache_get(x, prefix)) != NULL)
464 goto ok;
465 if (prefix != NULL) /* xmlns:<prefix>="<uri>" */
466 ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR);
467 else{ /* xmlns="<uri>" */
468 ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR);
469 }
470 /* namespace not found, try parent */
471 if (ns == NULL){
472 if ((xp = xml_parent(x)) != NULL){
473 if (xml2ns(xp, prefix, &ns) < 0)
474 goto done;
475 }
476 /* If no parent, return default namespace if defined */
477 else if (_USE_NAMESPACE_NETCONF_DEFAULT){
478 if (prefix == NULL)
479 ns = NETCONF_BASE_NAMESPACE;
480 else
481 ns = NULL;
482 }
483 }
484 /* Set default namespace cache (since code is at this point,
485 * no cache was found
486 * If not, this is devastating when populating deep yang structures
487 */
488 if (ns &&
489 xml_child_nr(x) > 1 && /* Dont set cache if few children: if 1 child typically a body */
490 nscache_set(x, prefix, ns) < 0)
491 goto done;
492 ok:
493 if (namespace)
494 *namespace = ns;
495 retval = 0;
496 done:
497 return retval;
498 }
499
500 /*! Recursively check prefix / namespaces (and populate ns cache)
501 * @retval 1 OK
502 * @retval 0 (Some) prefix not found
503 * @retval -1 Error
504 */
505 int
xml2ns_recurse(cxobj * xt)506 xml2ns_recurse(cxobj *xt)
507 {
508 int retval = -1;
509 cxobj *x;
510 char *prefix;
511 char *namespace;
512
513 x = NULL;
514 while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
515 if ((prefix = xml_prefix(x)) != NULL){
516 namespace = NULL;
517 if (xml2ns(x, prefix, &namespace) < 0)
518 goto done;
519 if (namespace == NULL){
520 clicon_err(OE_XML, ENOENT, "No namespace associated with %s:%s", prefix, xml_name(x));
521 goto done;
522 }
523 }
524 if (xml2ns_recurse(x) < 0)
525 goto done;
526 }
527 retval = 0;
528 done:
529 return retval;
530 }
531
532 /*! Add a namespace attribute to an XML node, either default or specific prefix
533 * @param[in] x XML tree
534 * @param[in] prefix prefix/ns localname. If NULL then set default xmlns
535 * @param[in] ns URI namespace (or NULL). Will be copied
536 * @retval 0 OK
537 * @retval -1 Error
538 * @see xml2ns
539 */
540 int
xmlns_set(cxobj * x,char * prefix,char * ns)541 xmlns_set(cxobj *x,
542 char *prefix,
543 char *ns)
544 {
545 int retval = -1;
546 cxobj *xa;
547
548 if (prefix != NULL){ /* xmlns:<prefix>="<uri>" */
549 if ((xa = xml_new(prefix, x, CX_ATTR)) == NULL)
550 goto done;
551 if (xml_prefix_set(xa, "xmlns") < 0)
552 goto done;
553 }
554 else{ /* xmlns="<uri>" */
555 if ((xa = xml_new("xmlns", x, CX_ATTR)) == NULL)
556 goto done;
557 }
558 if (xml_value_set(xa, ns) < 0)
559 goto done;
560 /* (re)set namespace cache (as used in xml2ns) */
561 if (ns && nscache_set(x, prefix, ns) < 0)
562 goto done;
563 retval = 0;
564 done:
565 return retval;
566 }
567
568 /*! Get prefix of given namespace recursively
569 * @param[in] xn XML node
570 * @param[in] namespace Namespace
571 * @param[out] prefixp Pointer to prefix if found
572 * @retval -1 Error
573 * @retval 0 No namespace found
574 * @retval 1 Namespace found, prefix returned in prefixp
575 */
576 int
xml2prefix(cxobj * xn,char * namespace,char ** prefixp)577 xml2prefix(cxobj *xn,
578 char *namespace,
579 char **prefixp)
580 {
581 int retval = -1;
582 cxobj *xa = NULL;
583 cxobj *xp;
584 char *prefix = NULL;
585 char *xaprefix;
586 int ret;
587
588 if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */
589 goto found;
590 xa = NULL;
591 while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL) {
592 /* xmlns=namespace */
593 if (strcmp("xmlns", xml_name(xa)) == 0){
594 if (strcmp(xml_value(xa), namespace) == 0){
595 if (nscache_set(xn, NULL, namespace) < 0)
596 goto done;
597 prefix = NULL; /* Maybe should set all caches in ns:s children? */
598 goto found;
599 }
600 }
601 /* xmlns:prefix=namespace */
602 else if ((xaprefix=xml_prefix(xa)) != NULL &&
603 strcmp("xmlns", xaprefix) == 0){
604 if (strcmp(xml_value(xa), namespace) == 0){
605 prefix = xml_name(xa);
606 if (nscache_set(xn, prefix, namespace) < 0)
607 goto done;
608 goto found;
609 }
610 }
611 }
612 if ((xp = xml_parent(xn)) != NULL){
613 if ((ret = xml2prefix(xp, namespace, &prefix)) < 0)
614 goto done;
615 if (ret == 1){
616 if (nscache_set(xn, prefix, namespace) < 0)
617 goto done;
618 goto found;
619 }
620 }
621 retval = 0;
622 done:
623 return retval;
624 found:
625 if (prefixp)
626 *prefixp = prefix;
627 retval = 1;
628 goto done;
629 }
630
631