Fixed: [JSON backslash string decoding/encoding not correct](https://github.com/clicon/clixon/issues/453)

Added unicode BMP support for JSON strings
Test: encoding/decoding tests for UTF-8
This commit is contained in:
Olof hagsand 2023-09-19 12:28:58 +02:00
parent 1a43a32770
commit 45f41e3e4d
12 changed files with 254 additions and 35 deletions

View file

@ -73,6 +73,7 @@ Developers may need to change their code
### Minor features ### Minor features
* JSON: Added unicode BMP support for unicode strings as part of fixing (https://github.com/clicon/clixon/issues/453)
* Example cli pipe grep command quotes vertical bar for OR function * Example cli pipe grep command quotes vertical bar for OR function
* Added: [Feature request: node's alias for CLI](https://github.com/clicon/clixon/issues/434) * Added: [Feature request: node's alias for CLI](https://github.com/clicon/clixon/issues/434)
* Note: "Skip" is for all nodes, but "Alias" is only for leafs * Note: "Skip" is for all nodes, but "Alias" is only for leafs
@ -85,7 +86,9 @@ Developers may need to change their code
### Corrected Bugs ### Corrected Bugs
* Fixed: [JSON backslash string decoding/encoding not correct](https://github.com/clicon/clixon/issues/453)
* Fixed: [CLI show config | display <format> exits over mountpoints with large YANGs](https://github.com/clicon/clixon-controller/issues/39) * Fixed: [CLI show config | display <format> exits over mountpoints with large YANGs](https://github.com/clicon/clixon-controller/issues/39)
* JSON string fixed according to RFC 8259: encoding/decoding of escape as defined in Section 8
* No need to bind for xml and json, only cli and text * No need to bind for xml and json, only cli and text
* Fixed several issues with extra-config files, including overwriting of structured sub-configs * Fixed several issues with extra-config files, including overwriting of structured sub-configs
* including `<restconf>`and m̀ <autoconf>` * including `<restconf>`and m̀ <autoconf>`

View file

@ -297,6 +297,7 @@ pipe_showas_fn(clicon_handle h,
switch (format){ switch (format){
case FORMAT_CLI: case FORMAT_CLI:
case FORMAT_TEXT: case FORMAT_TEXT:
case FORMAT_JSON:
/* Requires binding. Note binding over mountpoints can cause rpc: extra latency */ /* Requires binding. Note binding over mountpoints can cause rpc: extra latency */
if ((ret = xml_bind_yang(h, xt, YB_MODULE, yspec, &xerr)) < 0) if ((ret = xml_bind_yang(h, xt, YB_MODULE, yspec, &xerr)) < 0)
goto done; goto done;

View file

@ -33,9 +33,9 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* JSON support functions. * JSON support functions.
* JSON syntax is according to: * @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf * and RFC 7951 JSON Encoding of Data Modeled with YANG
* RFC 7951 JSON Encoding of Data Modeled with YANG * and RFC 8259 The JavaScript Object Notation (JSON) Data Interchange Format
*/ */
#ifndef _CLIXON_JSON_H #ifndef _CLIXON_JSON_H
#define _CLIXON_JSON_H #define _CLIXON_JSON_H

View file

@ -106,6 +106,7 @@ int nodeid_split(char *nodeid, char **prefix, char **id);
char *clixon_trim(char *str); char *clixon_trim(char *str);
char *clixon_trim2(char *str, char *trims); char *clixon_trim2(char *str, char *trims);
int clicon_strcmp(char *s1, char *s2); int clicon_strcmp(char *s1, char *s2);
int clixon_unicode2utf8(char *ucstr, char *utfstr, size_t utflen);
#ifndef HAVE_STRNDUP #ifndef HAVE_STRNDUP
char *clicon_strndup (const char *, size_t); char *clicon_strndup (const char *, size_t);

View file

@ -33,9 +33,9 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* JSON support functions. * JSON support functions.
* JSON syntax is according to: * @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf * and RFC 7951 JSON Encoding of Data Modeled with YANG
* RFC 7951 JSON Encoding of Data Modeled with YANG * and RFC 8259 The JavaScript Object Notation (JSON) Data Interchange Format
* XXX: The complexity of xml2json1_cbuf() mapping from internal cxobj structure to JSON output * XXX: The complexity of xml2json1_cbuf() mapping from internal cxobj structure to JSON output
* needs a rewrite due to complexity of lists/leaf-lists/null-values, etc. * needs a rewrite due to complexity of lists/leaf-lists/null-values, etc.
*/ */
@ -107,6 +107,7 @@ enum childtype{
}; };
/*! x is element and has exactly one child which in turn has none /*! x is element and has exactly one child which in turn has none
*
* remove attributes from x * remove attributes from x
* @see tleaf in clixon_xml_map.c * @see tleaf in clixon_xml_map.c
*/ */
@ -241,6 +242,7 @@ array_eval(cxobj *xprev,
} }
/*! Escape a json string as well as decode xml cdata /*! Escape a json string as well as decode xml cdata
*
* @param[out] cb cbuf (encoded) * @param[out] cb cbuf (encoded)
* @param[in] str string (unencoded) * @param[in] str string (unencoded)
*/ */
@ -255,15 +257,27 @@ json_str_escape_cdata(cbuf *cb,
len = strlen(str); len = strlen(str);
for (i=0; i<len; i++) for (i=0; i<len; i++)
switch (str[i]){ switch (str[i]){
case '\n':
cprintf(cb, "\\n");
break;
case '\"': case '\"':
cprintf(cb, "\\\""); cprintf(cb, "\\\"");
break; break;
case '\\': case '\\':
cprintf(cb, "\\\\"); cprintf(cb, "\\\\");
break; break;
case '\b':
cprintf(cb, "\\b");
break;
case '\f':
cprintf(cb, "\\f");
break;
case '\n':
cprintf(cb, "\\n");
break;
case '\r':
cprintf(cb, "\\r");
break;
case '\t':
cprintf(cb, "\\t");
break;
default: /* fall thru */ default: /* fall thru */
cprintf(cb, "%c", str[i]); cprintf(cb, "%c", str[i]);
break; break;
@ -274,6 +288,7 @@ json_str_escape_cdata(cbuf *cb,
} }
/*! Decode types from JSON to XML identityrefs /*! Decode types from JSON to XML identityrefs
*
* Assume an xml tree where prefix:name have been split into "module":"name" * Assume an xml tree where prefix:name have been split into "module":"name"
* In other words, from JSON RFC7951 to XML namespace trees * In other words, from JSON RFC7951 to XML namespace trees
* @param[in] x XML tree. Must be yang populated. * @param[in] x XML tree. Must be yang populated.
@ -433,6 +448,7 @@ json2xml_decode(cxobj *x,
} }
/*! Encode leaf/leaf_list identityref type from XML to JSON /*! Encode leaf/leaf_list identityref type from XML to JSON
*
* @param[in] x XML body node * @param[in] x XML body node
* @param[in] body body string * @param[in] body body string
* @param[in] ys Yang spec of parent * @param[in] ys Yang spec of parent
@ -497,6 +513,7 @@ xml2json_encode_identityref(cxobj *xb,
} }
/*! Encode leaf/leaf_list types from XML to JSON /*! Encode leaf/leaf_list types from XML to JSON
*
* @param[in] xb XML body * @param[in] xb XML body
* @param[in] xp XML parent * @param[in] xp XML parent
* @param[in] yp Yang spec of parent * @param[in] yp Yang spec of parent
@ -663,6 +680,7 @@ nullchild(cbuf *cb,
} }
/*! Encode json metadata /*! Encode json metadata
*
* This function could be more general, code based on two examples: * This function could be more general, code based on two examples:
* 1) ietf-list-pagination:remaining * 1) ietf-list-pagination:remaining
* { * {
@ -773,6 +791,7 @@ xml2json_encode_attr(cxobj *xa,
} }
/*! Do the actual work of translating XML to JSON /*! Do the actual work of translating XML to JSON
*
* @param[out] cb Cligen text buffer containing json on exit * @param[out] cb Cligen text buffer containing json on exit
* @param[in] x XML tree structure containing XML to translate * @param[in] x XML tree structure containing XML to translate
* @param[in] arraytype Does x occur in a array (of its parent) and how? * @param[in] arraytype Does x occur in a array (of its parent) and how?
@ -1142,6 +1161,7 @@ clixon_json2cbuf(cbuf *cb,
} }
/*! Translate a vector of xml objects to JSON Cligen buffer. /*! Translate a vector of xml objects to JSON Cligen buffer.
*
* This is done by adding a top pseudo-object, and add the vector as subs, * This is done by adding a top pseudo-object, and add the vector as subs,
* and then not printing the top pseudo-object using the 'flat' option. * and then not printing the top pseudo-object using the 'flat' option.
* @param[out] cb Cligen buffer to write to * @param[out] cb Cligen buffer to write to
@ -1224,6 +1244,7 @@ xml2json_cbuf_vec(cbuf *cb,
} }
/*! Translate from xml tree to JSON and print to file using a callback /*! Translate from xml tree to JSON and print to file using a callback
*
* @param[in] f File to print to * @param[in] f File to print to
* @param[in] xn XML tree to translate from * @param[in] xn XML tree to translate from
* @param[in] pretty Set if output is pretty-printed * @param[in] pretty Set if output is pretty-printed
@ -1280,6 +1301,7 @@ json_print(FILE *f,
} }
/*! Translate a vector of xml objects to JSON File. /*! Translate a vector of xml objects to JSON File.
*
* This is done by adding a top pseudo-object, and add the vector as subs, * This is done by adding a top pseudo-object, and add the vector as subs,
* and then not pritning the top pseudo-.object using the 'flat' option. * and then not pritning the top pseudo-.object using the 'flat' option.
* @param[out] cb Cligen buffer to write to * @param[out] cb Cligen buffer to write to
@ -1320,6 +1342,7 @@ xml2json_vec(FILE *f,
} }
/*! Translate from JSON module:name to XML default ns: xmlns="uri" recursively /*! Translate from JSON module:name to XML default ns: xmlns="uri" recursively
*
* Assume an xml tree where prefix:name have been split into "module":"name" * Assume an xml tree where prefix:name have been split into "module":"name"
* In other words, from JSON to XML namespace trees * In other words, from JSON to XML namespace trees
* *

View file

@ -32,6 +32,9 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* and RFC 7951 JSON Encoding of Data Modeled with YANG
* and RFC 8259 The JavaScript Object Notation (JSON) Data Interchange Format
*/ */
%{ %{
@ -51,6 +54,7 @@
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_string.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_xml.h" #include "clixon_xml.h"
@ -78,10 +82,12 @@ digit [0-9]
integer {digit}+ integer {digit}+
real ({digit}+[.]{digit}*)|({digit}*[.]{digit}+) real ({digit}+[.]{digit}*)|({digit}*[.]{digit}+)
exp ({integer}|{real})[eE][+-]{integer} exp ({integer}|{real})[eE][+-]{integer}
hex [A-Fa-f0-9]
%x START %x START
%s STRING %s STRING
%s ESCAPE %s ESCAPE
%s HEXDIG
%% %%
@ -103,14 +109,31 @@ exp ({integer}|{real})[eE][+-]{integer}
<START>. { return -1; } <START>. { return -1; }
<STRING>\" { BEGIN(START); return J_DQ; } <STRING>\" { BEGIN(START); return J_DQ; }
<STRING>\\ { BEGIN(ESCAPE); } <STRING>\\ { BEGIN(ESCAPE); }
<STRING>\n { _JY->jy_linenum++; <STRING>[^\"\\\b\f\n\r\t]+ { BEGIN(STRING); clixon_json_parselval.string = yytext; return J_STRING; }
clixon_json_parselval.string = strdup(yytext); <STRING>\n { return -1; }
return J_CHAR;} <STRING>. { return -1; }
<STRING>. { clixon_json_parselval.string = strdup(yytext); <ESCAPE>\" { BEGIN(STRING); clixon_json_parselval.string = yytext; return J_STRING; }
return J_CHAR;} <ESCAPE>\\ { BEGIN(STRING); clixon_json_parselval.string = yytext; return J_STRING; }
<ESCAPE>. { BEGIN(STRING); <ESCAPE>\/ { BEGIN(STRING); clixon_json_parselval.string = yytext; return J_STRING; }
clixon_json_parselval.string = strdup(yytext); <ESCAPE>b { BEGIN(STRING); clixon_json_parselval.string = "\b"; return J_STRING; }
return J_CHAR; } <ESCAPE>f { BEGIN(STRING); clixon_json_parselval.string = "\f"; return J_STRING; }
<ESCAPE>n { BEGIN(STRING); clixon_json_parselval.string = "\n"; return J_STRING; }
<ESCAPE>r { BEGIN(STRING); clixon_json_parselval.string = "\r"; return J_STRING; }
<ESCAPE>t { BEGIN(STRING); clixon_json_parselval.string = "\t"; return J_STRING; }
<ESCAPE>u { BEGIN(HEXDIG); }
<ESCAPE>\n { return -1; }
<ESCAPE>. { return -1; }
<HEXDIG>{hex}{hex}{hex}{hex} {
char buf[5] = {0, };
BEGIN(STRING);
if (clixon_unicode2utf8(yytext, buf, 5) < 0)
return -1;
strncpy(yytext, buf, 4);
clixon_json_parselval.string = yytext;
return J_STRING;
}
<HEXDIG>\n { return -1;}
<HEXDIG>. { return -1; }
%% %%

View file

@ -33,8 +33,9 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* JSON Parser * JSON Parser
* From http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf * @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* And RFC7951 JSON Encoding of Data Modeled with YANG * and RFC 7951 JSON Encoding of Data Modeled with YANG
* and RFC 8259 The JavaScript Object Notation (JSON) Data Interchange Format
Structural tokens: Structural tokens:
[ left square bracket [ left square bracket
@ -87,7 +88,7 @@ object.
%token <string> J_NULL %token <string> J_NULL
%token <string> J_EOF %token <string> J_EOF
%token <string> J_DQ %token <string> J_DQ
%token <string> J_CHAR %token <string> J_STRING
%token <string> J_NUMBER %token <string> J_NUMBER
%type <cbuf> string %type <cbuf> string
@ -151,7 +152,7 @@ void
clixon_json_parseerror(void *_jy, clixon_json_parseerror(void *_jy,
char *s) char *s)
{ {
clicon_err(OE_JSON, XMLPARSE_ERRNO, "json_parse: line %d: %s at or before: '%s'", clicon_err(OE_JSON, 0, "json_parse: line %d: %s at or before: '%s'",
_JY->jy_linenum , _JY->jy_linenum ,
s, s,
clixon_json_parsetext); clixon_json_parsetext);
@ -312,12 +313,16 @@ string : J_DQ ustring J_DQ { _PARSE_DEBUG("string->\" ustring \"");$$=$2;
; ;
/* unquoted string: can be optimized by reading whole string in lex */ /* unquoted string: can be optimized by reading whole string in lex */
ustring : ustring J_CHAR ustring : ustring J_STRING
{ {
cbuf_append_str($1,$2); $$=$1; free($2); cbuf_append_str($1,$2); $$=$1;
} }
| J_CHAR | J_STRING
{ cbuf *cb = cbuf_new(); cbuf_append_str(cb,$1); $$=cb; free($1);} {
cbuf *cb = cbuf_new();
cbuf_append_str(cb,$1);
$$=cb;
}
; ;
number : J_NUMBER { $$ = $1; } number : J_NUMBER { $$ = $1; }

View file

@ -1051,6 +1051,105 @@ clicon_strcmp(char *s1,
return strcmp(s1, s2); return strcmp(s1, s2);
} }
/*! Translate from unicode in hex form to utf-8
*
* @param[in] uc16 Unicode as 2-byte hex int
* @param[out] utf8str UTF-8 string
* @param[out] utflen Length utf string
* @retval 0 OK
* @retval -1 Error
*/
static int
clixon_unicode2utf8_one(uint16_t uc16,
char *utfstr,
size_t utflen)
{
int retval = -1;
if (utflen < 5){
clicon_err(OE_UNIX, EINVAL, "Length of utfstr is not >=4");
goto done;
}
if (uc16<0x80)
*utfstr++=uc16;
else if (uc16<0x800){
*utfstr++=192+uc16/64;
*utfstr++=128+uc16%64;
}
else if (uc16-0xd800u<0x800){
clicon_err(OE_UNIX, EINVAL, "unicode2utf error");
goto done;
}
else if (uc16<0x10000) {
*utfstr++=224+uc16/4096;
*utfstr++=128+uc16/64%64;
*utfstr++=128+uc16%64;
}
else if (uc16<0x110000) {
*utfstr++=240+uc16/262144;
*utfstr++=128+uc16/4096%64;
*utfstr++=128+uc16/64%64;
*utfstr++=128+uc16%64;
}
else{
clicon_err(OE_UNIX, EINVAL, "unicode2utf error");
goto done;
}
*utfstr++=0;
retval = 0;
done:
return retval;
}
/*! Translate unicode hexstring on the form "ABCD" to UTF-8 in string form
*
* @param[in] unicode Unicode as string of 2-byte hex codes
* @param[out] utf8 UTF-8 character string must be length >=5
* @retval 0 OK
* @retval -1 Error
*/
int
clixon_unicode2utf8(char *ucstr,
char *utfstr,
size_t utflen)
{
int retval = -1;
size_t len;
int i;
char c;
int j;
uint16_t uc16 = 0;
if (ucstr == NULL || utfstr == NULL){
clicon_err(OE_UNIX, EINVAL, "input param is NULL");
goto done;
}
if ((len = strlen(ucstr)) != 4){
clicon_err(OE_UNIX, EINVAL, "Length of ucstr is not 4");
goto done;
}
for (i=0; i<len; i++){
c = ucstr[i]&0xFF;
if ('0' <= c && c <= '9')
j = c-'0';
else if ('A' <= c && c <= 'F')
j = c+10-'A';
else if ('a' <= c && c <= 'f')
j = c+10-'a';
else{
clicon_err(OE_UNIX, 0, "no match");
goto done;
}
if (uc16 != 0)
uc16 <<= 4;
uc16 |= j;
}
if (clixon_unicode2utf8_one(uc16, utfstr, utflen) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! strndup() for systems without it, such as xBSD /*! strndup() for systems without it, such as xBSD
*/ */

View file

@ -2,6 +2,7 @@
# Test: JSON parser tests. See RFC7951 # Test: JSON parser tests. See RFC7951
# - Multi-line + pretty-print # - Multi-line + pretty-print
# - Empty values # - Empty values
# - JSON string encode/decode
# Note that members should not be quoted. See test_restconf2.sh for typed # Note that members should not be quoted. See test_restconf2.sh for typed
#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_json" #PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_json"
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
@ -138,6 +139,41 @@ JSON='{"json:c":{"s":"<![CDATA[ z > x & x < y ]]>"}}'
new "json parse cdata xml" new "json parse cdata xml"
expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON" expecteofx "$clixon_util_json -j -y $fyang" 0 "$JSON" "$JSON"
JSON='{"text":"ruled over the shores of the Hreiðsea"}'
new "json utf-8"
expecteofx "$clixon_util_json -j" 0 "$JSON" "$JSON"
new "json utf-8 xml out"
expecteofx "$clixon_util_json" 0 "$JSON" "<text>ruled over the shores of the Hreiðsea</text>"
# see https://github.com/clicon/clixon/issues/453
JSON="{\"text\":\"one
two\"}"
new "json not escaped \n expect fail"
expecteofx "$clixon_util_json -j" 255 "$JSON" 2> /dev/null
new "json not escaped \" expect fail"
expecteofx "$clixon_util_json -j" 255 "quote:\"" 2> /dev/null
JSON='{"text":"cr:\nquote:\"tab:\tend"}'
new "json escaping \n\"\t to json"
expecteofx "$clixon_util_json -j" 0 "$JSON" "$JSON"
new "json escaping \n\"\t to xml"
expecteofx "$clixon_util_json" 0 "$JSON" "quote:\"tab: end</text>"
JSON='{"text":"bmp:\u005E"}'
new "json escaping \u BMP circumflex"
#expecteofx "$clixon_util_json -j -D $DBG" 0 "$JSON" '{"text":"bmp:^"}'
JSON='{"text":"bmp:\u00F0"}'
new "json escaping \u BMP latin eth"
expecteofx "$clixon_util_json -j -D $DBG" 0 "$JSON" '{"text":"bmp:ð"}'
JSON='{"text":"bmp:\uFAIL"}'
new "json escaping unicode BMP fail"
expecteofx "$clixon_util_json -j -D $DBG" 255 "$JSON" 2> /dev/null
rm -rf $dir rm -rf $dir
new "endtest" new "endtest"

View file

@ -294,6 +294,7 @@ function testrun()
new "start restconf daemon" new "start restconf daemon"
# inline of start_restconf, cant make quotes to work # inline of start_restconf, cant make quotes to work
echo "sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG -f $cfg -R $RESTCONFIG1" echo "sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG -f $cfg -R $RESTCONFIG1"
STTYSETTINGS=$(stty -g) # reset in wait_restconf
sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG -f $cfg -R "$RESTCONFIG1" </dev/null &>/dev/null & sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG -f $cfg -R "$RESTCONFIG1" </dev/null &>/dev/null &
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err1 "expected 0" "$?" err1 "expected 0" "$?"
@ -308,14 +309,13 @@ function testrun()
else else
HVER=2 HVER=2
fi fi
new "wait restconf" new "wait restconf"
wait_restconf wait_restconf
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
echo "curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta"
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
echo "fcgi or native+http/1 or native+http/1+http/2"
new "restconf GET http/1.1" new "restconf GET http/1.1"
expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
@ -334,7 +334,7 @@ function testrun()
new "restconf GET https/2 prior-knowledge" new "restconf GET https/2 prior-knowledge"
expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
fi fi
# Wrong protocol http when https or vice versa # Wrong protocol http when https or vice versa
if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c
new "Wrong proto=https on http port, expect err 35 wrong version number" new "Wrong proto=https on http port, expect err 35 wrong version number"
@ -354,10 +354,8 @@ function testrun()
wait_restconf wait_restconf
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
echo "curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta"
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
echo "native + http/2 only"
# Important here is robustness of restconf daemon, not a meaningful reply # Important here is robustness of restconf daemon, not a meaningful reply
if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c
# http protocol mismatch can just close the socket if assumed its http/2 # http protocol mismatch can just close the socket if assumed its http/2
@ -405,10 +403,8 @@ function testrun()
wait_restconf wait_restconf
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
echo "curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta"
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
echo "fcgi or native+http/1 or native+http/1+http/2"
if [ "${WITH_RESTCONF}" = "native" ]; then # XXX does not work with nginx if [ "${WITH_RESTCONF}" = "native" ]; then # XXX does not work with nginx
new "restconf GET http/1.0 - returns 1.0" new "restconf GET http/1.0 - returns 1.0"
expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.0 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.0 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
@ -439,7 +435,6 @@ function testrun()
expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/" "400" expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/" "400"
fi fi
fi # HTTP/2 fi # HTTP/2
# Exact match # Exact match
new "restconf get restconf resource. RFC 8040 3.3 (json)" new "restconf get restconf resource. RFC 8040 3.3 (json)"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf)" 0 "HTTP/$HVER 200" '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}' expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf)" 0 "HTTP/$HVER 200" '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}'
@ -653,6 +648,24 @@ function testrun()
new "restconf Add subtree with too many keys (expected error)" new "restconf Add subtree with too many keys (expected error)"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}' expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}'
cat <<EOF > $dir/input.json
{"clixon-example:parameter":{"name":"x","value":"foo
bar"}}
EOF
new "restconf JSON escape encoding with explicit \n expect fail"
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" --data-binary @$dir/input.json $proto://$addr/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 400" "malformed-message"
JSON='{"clixon-example:parameter":[{"name":"x","value":"foo\nbar"}]}'
new "restconf JSON escape encoding"
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" --data-binary "$JSON" $proto://$addr/restconf/data/clixon-example:table)" 0 "HTTP/$HVER 201" "Location: $proto://$addr/restconf/data/clixon-example:table/parameter=x"
new "Check restconf JSON escape encoding"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf/data/clixon-example:table/parameter=x)" 0 "HTTP/$HVER 200" '{"clixon-example:parameter":\[{"name":"x","value":"foo\\nbar"}\]}'
new "Check restconf JSON escape encoding in XML"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/data/clixon-example:table/parameter=x)" 0 "HTTP/$HVER 200" "<parameter xmlns=\"urn:example:clixon\"><name>x</name><value>foo" "bar</value></parameter>"
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -212,6 +212,19 @@ EOF
) )
expecteof "$clixon_util_xml -o" 0 "$XML" '^<bk:book xmlns:bk="urn:loc.gov:books" xmlns:isbn="urn:ISBN:0-395-36341-6"><bk:title>Cheaper by the Dozen</bk:title><isbn:number>1568491379</isbn:number></bk:book>$' expecteof "$clixon_util_xml -o" 0 "$XML" '^<bk:book xmlns:bk="urn:loc.gov:books" xmlns:isbn="urn:ISBN:0-395-36341-6"><bk:title>Cheaper by the Dozen</bk:title><isbn:number>1568491379</isbn:number></bk:book>$'
XML=$(cat <<EOF
<?xml version="1.0" encoding='utf-8'?>
<text>
Theodoric the bold
chief of sea-warriors
ruled over the shores of the Hreiðsea
</text>
EOF
)
new "utf-8 string"
expecteof "$clixon_util_xml -o" 0 "$XML" "^ruled over the shores of the Hreiðsea$"
rm -rf $dir rm -rf $dir
new "endtest" new "endtest"

View file

@ -33,7 +33,9 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* JSON utility command * JSON utility command
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf * @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* and RFC 7951 JSON Encoding of Data Modeled with YANG
* and RFC 8259 The JavaScript Object Notation (JSON) Data Interchange Format
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H