diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e0a84d3..fd54b4bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
### Major New features
* Restconf RFC 8040 increased feature compliance
+ * RESTCONF PATCH (plain patch) is supported according to RFC 8040 4.6.1
* RESTCONF "insert" and "point" query parameters supported
* Applies to ordered-by-user leaf and leaf-lists
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in
index b2936775..2e9629ac 100644
--- a/apps/restconf/Makefile.in
+++ b/apps/restconf/Makefile.in
@@ -78,7 +78,6 @@ APPSRC = restconf_main.c
APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c
-APPSRC += restconf_methods_patch.c
APPSRC += restconf_stream.c
APPOBJ = $(APPSRC:.c=.o)
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index c340b724..dd255f9f 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -171,6 +171,21 @@ restconf_media_int2str(restconf_media media)
return clicon_int2str(http_media_map, media);
}
+/*! Return media_in from Content-Type, -1 if not found or unrecognized
+ */
+restconf_media
+restconf_content_type(FCGX_Request *r)
+{
+ char *str;
+ restconf_media m;
+
+ if ((str = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp)) == NULL)
+ return -1;
+ if ((m = restconf_media_str2int(str)) == -1)
+ return -1;
+ return m;
+}
+
/*! HTTP error 400
* @param[in] r Fastcgi request handle
*/
@@ -179,9 +194,9 @@ restconf_badrequest(FCGX_Request *r)
{
char *path;
- clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
- FCGX_FPrintF(r->out, "Status: 400\r\n"); /* 400 bad request */
+ FCGX_SetExitStatus(400, r->out);
+ FCGX_FPrintF(r->out, "Status: 400 Bad Request\r\n"); /* 400 bad request */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "
Clixon Bad request/h1>\n");
FCGX_FPrintF(r->out, "The requested URL %s or data is in some way badly formed.\n",
@@ -197,9 +212,9 @@ restconf_unauthorized(FCGX_Request *r)
{
char *path;
- clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
- FCGX_FPrintF(r->out, "Status: 401\r\n"); /* 401 unauthorized */
+ FCGX_SetExitStatus(401, r->out);
+ FCGX_FPrintF(r->out, "Status: 401 Unauthorized\r\n"); /* 401 unauthorized */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "access-denied\n");
FCGX_FPrintF(r->out, "The requested URL %s was unauthorized.\n", path);
@@ -214,9 +229,9 @@ restconf_forbidden(FCGX_Request *r)
{
char *path;
- clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
- FCGX_FPrintF(r->out, "Status: 403\r\n"); /* 403 forbidden */
+ FCGX_SetExitStatus(403, r->out);
+ FCGX_FPrintF(r->out, "Status: 403 Forbidden\r\n"); /* 403 forbidden */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "Forbidden
\n");
FCGX_FPrintF(r->out, "The requested URL %s was forbidden.\n", path);
@@ -231,10 +246,9 @@ restconf_notfound(FCGX_Request *r)
{
char *path;
- clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
- FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */
-
+ FCGX_SetExitStatus(404, r->out);
+ FCGX_FPrintF(r->out, "Status: 404 Not Found\r\n"); /* 404 not found */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "Not Found
\n");
FCGX_FPrintF(r->out, "Not Found\n");
@@ -251,9 +265,9 @@ restconf_notacceptable(FCGX_Request *r)
{
char *path;
- clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
- FCGX_FPrintF(r->out, "Status: 406\r\n"); /* 406 not acceptible */
+ FCGX_SetExitStatus(406, r->out);
+ FCGX_FPrintF(r->out, "Status: 406 Not Acceptable\r\n"); /* 406 not acceptible */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "Not Acceptable
\n");
@@ -269,8 +283,8 @@ restconf_notacceptable(FCGX_Request *r)
int
restconf_conflict(FCGX_Request *r)
{
- clicon_debug(1, "%s", __FUNCTION__);
- FCGX_FPrintF(r->out, "Status: 409\r\n"); /* 409 Conflict */
+ FCGX_SetExitStatus(409, r->out);
+ FCGX_FPrintF(r->out, "Status: 409 Conflict\r\n"); /* 409 Conflict */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "Data resource already exists
\n");
return 0;
@@ -282,7 +296,6 @@ restconf_conflict(FCGX_Request *r)
int
restconf_unsupported_media(FCGX_Request *r)
{
- clicon_debug(1, "%s", __FUNCTION__);
FCGX_SetExitStatus(415, r->out);
FCGX_FPrintF(r->out, "Status: 415 Unsupported Media Type\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
@@ -300,7 +313,7 @@ restconf_internal_server_error(FCGX_Request *r)
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
- FCGX_FPrintF(r->out, "Status: 500\r\n"); /* 500 internal server error */
+ FCGX_FPrintF(r->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "Internal server error when accessing %s
\n", path);
return 0;
@@ -313,7 +326,7 @@ int
restconf_notimplemented(FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
- FCGX_FPrintF(r->out, "Status: 501\r\n");
+ FCGX_FPrintF(r->out, "Status: 501 Not Implemented\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "Not Implemented/h1>\n");
return 0;
diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h
index 436b5004..316a7ddc 100644
--- a/apps/restconf/restconf_lib.h
+++ b/apps/restconf/restconf_lib.h
@@ -62,7 +62,7 @@ const char *restconf_code2reason(int code);
const restconf_media restconf_media_str2int(char *media);
const char *restconf_media_int2str(restconf_media media);
-
+restconf_media restconf_content_type(FCGX_Request *r);
int restconf_badrequest(FCGX_Request *r);
int restconf_unauthorized(FCGX_Request *r);
int restconf_forbidden(FCGX_Request *r);
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index 8fb809cd..1adf6cc2 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -80,7 +80,6 @@
#include "restconf_methods.h"
#include "restconf_methods_get.h"
#include "restconf_methods_post.h"
-#include "restconf_methods_patch.h"
#include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */
@@ -113,7 +112,6 @@ api_data(clicon_handle h,
cvec *qvec,
char *data,
int pretty,
- restconf_media media_in,
restconf_media media_out)
{
int retval = -1;
@@ -129,11 +127,11 @@ api_data(clicon_handle h,
else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, r, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "POST")==0)
- retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
+ retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PUT")==0)
- retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
+ retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PATCH")==0)
- retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, pretty, media_in, media_out);
+ retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, r, api_path, pi, pretty, media_out);
else
@@ -150,7 +148,6 @@ api_data(clicon_handle h,
* @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] media_in Input media media
* @param[in] media_out Output media
*/
static int
@@ -162,7 +159,6 @@ api_operations(clicon_handle h,
cvec *qvec,
char *data,
int pretty,
- restconf_media media_in,
restconf_media media_out)
{
int retval = -1;
@@ -175,7 +171,7 @@ api_operations(clicon_handle h,
retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "POST")==0)
retval = api_operations_post(h, r, path, pcvec, pi, qvec, data,
- pretty, media_in, media_out);
+ pretty, media_out);
else
retval = restconf_notfound(r);
return retval;
@@ -334,7 +330,6 @@ api_restconf(clicon_handle h,
char *data;
int authenticated = 0;
char *media_str = NULL;
- restconf_media media_in = YANG_DATA_JSON; /* XXX defaults should be set in methods, here is too generic */
restconf_media media_out = YANG_DATA_JSON;
int pretty;
cbuf *cbret = NULL;
@@ -345,19 +340,7 @@ api_restconf(clicon_handle h,
path = restconf_uripath(r);
query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
- /* Get media for input (Content-Type)
- * This is for methods that have input, such as PUT/POST, etc
- */
- if ((media_str = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp)) == NULL){
- retval = restconf_unsupported_media(r);
- goto done;
- }
- else if ((media_in = restconf_media_str2int(media_str)) == -1){
- clicon_debug(1, "%s Content_Type: %s (unsupported)", __FUNCTION__, media_str);
- retval = restconf_unsupported_media(r);
- goto done;
- }
- clicon_debug(1, "%s CONTENT_TYPE: %s %s", __FUNCTION__, media_str, restconf_media_int2str(media_in));
+
/* Get media for output (proactive negotiation) RFC7231 by using
* Accept:. This is for methods that have output, such as GET,
* operation POST, etc
@@ -444,12 +427,12 @@ api_restconf(clicon_handle h,
}
else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */
if (api_data(h, r, path, pcvec, 2, qvec, data,
- pretty, media_in, media_out) < 0)
+ pretty, media_out) < 0)
goto done;
}
else if (strcmp(method, "operations") == 0){ /* rpc */
if (api_operations(h, r, path, pcvec, 2, qvec, data,
- pretty, media_in, media_out) < 0)
+ pretty, media_out) < 0)
goto done;
}
else if (strcmp(method, "test") == 0)
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index 299e3341..200ea022 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -215,53 +215,24 @@ match_list_keys(yang_stmt *y,
return retval;
}
-/*! Generic REST PUT method
- * @param[in] h CLIXON handle
- * @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
- * @param[in] pcvec Vector of path ie DOCUMENT_URI element
- * @param[in] pi Offset, where to start pcvec
- * @param[in] qvec Vector of query string (QUERY_STRING)
- * @param[in] data Stream input data
- * @param[in] pretty Set to 1 for pretty-printed xml/json output
- * @param[in] media_in Input media media
- * @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: (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 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,
- FCGX_Request *r,
- char *api_path0,
- cvec *pcvec,
- int pi,
- cvec *qvec,
- char *data,
- int pretty,
- restconf_media media_in,
- restconf_media media_out)
+/*! 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)
+ */
+static int
+api_data_write(clicon_handle h,
+ FCGX_Request *r,
+ char *api_path0,
+ cvec *pcvec,
+ int pi,
+ cvec *qvec,
+ char *data,
+ int pretty,
+ restconf_media media_in,
+ restconf_media media_out,
+ int plain_patch)
{
int retval = -1;
enum operation_type op;
@@ -282,13 +253,15 @@ api_data_put(clicon_handle h,
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;
+ cxobj *xerr = NULL; /* malloced must be freed */
+ cxobj *xe; /* direct pointer into tree, dont free */
char *username;
int ret;
- char *namespace0;
+ char *namespace = NULL;
char *dname;
int nullspec = 0;
+ char *xpath = NULL;
+ cbuf *cbpath = NULL;
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
@@ -299,6 +272,84 @@ api_data_put(clicon_handle h,
api_path=api_path0;
for (i=0; i", NETCONF_BASE_NAMESPACE);
+ cprintf(cbx, ">");
+ if (namespace)
+ cprintf(cbx, "",
+ xpath, namespace);
+ else /* If xpath != /, this will probably yield an error later */
+ cprintf(cbx, "", xpath);
+ cprintf(cbx, "");
+ xret = NULL;
+ if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0){
+ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
+ goto done;
+ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
+ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
+ goto done;
+ }
+ if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
+ goto done;
+ goto ok;
+ }
+#if 0
+ if (debug){
+ cbuf *ccc=cbuf_new();
+ if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0)
+ goto done;
+ clicon_debug(1, "%s XRET: %s", __FUNCTION__, cbuf_get(ccc));
+ cbuf_free(ccc);
+ }
+#endif
+ if ((xe = xpath_first(xret, "/rpc-reply/data")) == NULL ||
+ xml_child_nr(xe) == 0){ /* Object does not exist */
+ if (plain_patch){ /* If the target resource instance does not exist, the server MUST NOT create it. */
+ restconf_badrequest(r);
+ goto ok;
+ }
+ else
+ op = OP_CREATE;
+ }
+ else{
+ if (plain_patch)
+ op = OP_MERGE;
+ else
+ op = OP_REPLACE;
+ }
+ if (xret){
+ xml_free(xret);
+ xret = NULL;
+ }
+
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done;
@@ -322,6 +373,20 @@ api_data_put(clicon_handle h,
goto ok;
}
}
+ /* 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 ((xe = xpath_first(xerr, "rpc-error")) == NULL){
+ clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
+ goto done;
+ }
+ if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
+ goto done;
+ goto ok;
+ }
/* Parse input data as json or xml into xml */
switch (media_in){
@@ -365,13 +430,18 @@ api_data_put(clicon_handle h,
goto done;
goto ok;
}
+ break;
+ default:
+ restconf_unsupported_media(r);
+ goto ok;
+ break;
} /* switch media_in */
/* The message-body MUST contain exactly one instance of the
* expected data resource.
*/
if (xml_child_nr(xdata0) != 1){
- if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
+ if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
@@ -411,15 +481,17 @@ api_data_put(clicon_handle h,
}
}
- /* Add operation create as attribute. If that fails with Conflict, then try
- "replace" */
+ /* Add operation create as attribute. If that fails with Conflict, then
+ * try "replace" (see comment in function header)
+ */
if ((xa = xml_new("operation", xdata, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
xml_prefix_set(xa, NETCONF_BASE_PREFIX);
- op = OP_CREATE;
+
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
+
/* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data
*/
@@ -529,21 +601,17 @@ api_data_put(clicon_handle h,
/* 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, &namespace0) < 0)
+ if (xml2ns(xparent, NULL, &namespace) < 0)
goto done;
/* Set xmlns="" default namespace attribute (if diff from default) */
- if (strcmp(namespace0, xml_value(xa))==0)
+ if (strcmp(namespace, xml_value(xa))==0)
xml_purge(xa);
}
-
}
- /* Create text buffer for transfer to backend */
- if ((cbx = cbuf_new()) == NULL)
- goto done;
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
- again:
+ cbuf_reset(cbx);
cprintf(cbx, "",
username?username:"",
NETCONF_BASE_PREFIX,
@@ -557,30 +625,9 @@ api_data_put(clicon_handle h,
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done;
if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
- /* If the error is not data-exists, then return error now
- * OR we have run again with replace
- */
- if (xpath_first(xe, ".[error-tag=\"data-exists\"]") == NULL ||
- op == OP_REPLACE){
- if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
- goto done;
- goto ok;
- }
- /* If it is data-exists, then set operator to replace and try again */
- if (xret){
- xml_free(xret);
- xret = NULL;
- }
- if ((xa = xml_find_type(xdata, NULL, "operation", CX_ATTR)) == NULL){
- clicon_err(OE_XML, ENOENT, "operation attr not found (shouldnt happen)");
- goto done;
- }
- op = OP_REPLACE;
- if (xml_value_set(xa, xml_operation2str(op)) < 0)
- goto done;
- cbuf_reset(cbx);
- clicon_debug(1, "%s Failed with create, trying replace",__FUNCTION__);
- goto again;
+ if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
+ goto done;
+ goto ok;
}
cbuf_reset(cbx);
/* commit/discard should be done automaticaly by the system, therefore
@@ -640,6 +687,8 @@ api_data_put(clicon_handle h,
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
+ if (cbpath)
+ cbuf_reset(cbpath);
if (xret)
xml_free(xret);
if (xerr)
@@ -657,6 +706,103 @@ api_data_put(clicon_handle h,
return retval;
} /* api_data_put */
+
+/*! Generic REST PUT method
+ * @param[in] h CLIXON handle
+ * @param[in] r Fastcgi request handle
+ * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
+ * @param[in] pcvec Vector of path ie DOCUMENT_URI element
+ * @param[in] pi Offset, where to start pcvec
+ * @param[in] qvec Vector of query string (QUERY_STRING)
+ * @param[in] data Stream input data
+ * @param[in] pretty Set to 1 for pretty-printed xml/json output
+ * @param[in] 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: (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 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,
+ FCGX_Request *r,
+ char *api_path0,
+ cvec *pcvec,
+ int pi,
+ cvec *qvec,
+ char *data,
+ int pretty,
+ restconf_media media_out)
+{
+ restconf_media media_in;
+
+ media_in = restconf_content_type(r);
+ return api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
+ media_in, media_out, 0);
+}
+
+/*! Generic REST PATCH method for plain patch
+ * @param[in] h CLIXON handle
+ * @param[in] r Fastcgi request handle
+ * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
+ * @param[in] pcvec Vector of path ie DOCUMENT_URI element
+ * @param[in] pi Offset, where to start pcvec
+ * @param[in] qvec Vector of query string (QUERY_STRING)
+ * @param[in] data Stream input data
+ * @param[in] pretty Set to 1 for pretty-printed xml/json output
+ * @param[in] media_out Output media
+ * Netconf: (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,
+ FCGX_Request *r,
+ char *api_path0,
+ cvec *pcvec,
+ int pi,
+ cvec *qvec,
+ char *data,
+ int pretty,
+ restconf_media media_out)
+{
+ restconf_media media_in;
+ int ret;
+
+ media_in = restconf_content_type(r);
+ if (media_in == YANG_DATA_XML || media_in == YANG_DATA_JSON){
+ /* plain patch */
+ ret = api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
+ media_in, media_out, 1);
+ }
+ else{ /* Other patches are NYI */
+ ret = restconf_notimplemented(r);
+ }
+ return ret;
+}
+
/*! Generic REST DELETE method translated to edit-config
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h
index a1859fb1..922ee2f9 100644
--- a/apps/restconf/restconf_methods.h
+++ b/apps/restconf/restconf_methods.h
@@ -45,8 +45,13 @@ int api_data_options(clicon_handle h, FCGX_Request *r);
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data,
- int pretty,
- restconf_media media_in, restconf_media media_out);
+ int pretty, restconf_media media_out);
+
+int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
+ cvec *pcvec, int pi,
+ cvec *qvec, char *data, int pretty,
+ restconf_media media_out);
+
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
int pretty, restconf_media media_out);
diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c
index ecd2cd1a..d8108185 100644
--- a/apps/restconf/restconf_methods_get.c
+++ b/apps/restconf/restconf_methods_get.c
@@ -116,7 +116,10 @@ api_data_get2(clicon_handle h,
cvec *nsc = NULL;
clicon_debug(1, "%s", __FUNCTION__);
- yspec = clicon_dbspec_yang(h);
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
+ goto done;
+ }
if ((cbpath = cbuf_new()) == NULL)
goto done;
cprintf(cbpath, "/");
diff --git a/apps/restconf/restconf_methods_patch.c b/apps/restconf/restconf_methods_patch.c
deleted file mode 100644
index 48338198..00000000
--- a/apps/restconf/restconf_methods_patch.c
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- *
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright (C) 2009-2019 Olof Hagsand
-
- This file is part of CLIXON.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- Alternatively, the contents of this file may be used under the terms of
- the GNU General Public License Version 3 or later (the "GPL"),
- in which case the provisions of the GPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of the GPL, and not to allow others to
- use your version of this file under the terms of Apache License version 2,
- indicate your decision by deleting the provisions above and replace them with
- the notice and other provisions required by the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the Apache License version 2 or the GPL.
-
- ***** END LICENSE BLOCK *****
-
- */
-
-/*
- * 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=/metric= PUT data:enable=
- * api/test
- +----------------------------+--------------------------------------+
- | 100 Continue | POST accepted, 201 should follow |
- | 200 OK | Success with response message-body |
- | 201 Created | POST to create a resource success |
- | 204 No Content | Success without response message- |
- | | body |
- | 304 Not Modified | Conditional operation not done |
- | 400 Bad Request | Invalid request message |
- | 401 Unauthorized | Client cannot be authenticated |
- | 403 Forbidden | Access to resource denied |
- | 404 Not Found | Resource target or resource node not |
- | | found |
- | 405 Method Not Allowed | Method not allowed for target |
- | | resource |
- | 409 Conflict | Resource or lock in use |
- | 412 Precondition Failed | Conditional method is false |
- | 413 Request Entity Too | too-big error |
- | Large | |
- | 414 Request-URI Too Large | too-big error |
- | 415 Unsupported Media Type | non RESTCONF media type |
- | 500 Internal Server Error | operation-failed |
- | 501 Not Implemented | unknown-operation |
- | 503 Service Unavailable | Recoverable server error |
- +----------------------------+--------------------------------------+
-Mapping netconf error-tag -> status code
- +-------------------------+-------------+
- | | status code |
- +-------------------------+-------------+
- | in-use | 409 |
- | invalid-value | 400 |
- | too-big | 413 |
- | missing-attribute | 400 |
- | bad-attribute | 400 |
- | unknown-attribute | 400 |
- | bad-element | 400 |
- | unknown-element | 400 |
- | unknown-namespace | 400 |
- | access-denied | 403 |
- | lock-denied | 409 |
- | resource-denied | 409 |
- | rollback-failed | 500 |
- | data-exists | 409 |
- | data-missing | 409 |
- | operation-not-supported | 501 |
- | operation-failed | 500 |
- | partial-operation | 500 |
- | malformed-message | 400 |
- +-------------------------+-------------+
-
- * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
- */
-
-#ifdef HAVE_CONFIG_H
-#include "clixon_config.h" /* generated by config & autoconf */
-#endif
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-/* cligen */
-#include
-
-/* clicon */
-#include
-
-#include /* Need to be after clixon_xml-h due to attribute format */
-
-#include "restconf_lib.h"
-#include "restconf_methods_patch.h"
-
-
-/*! Generic REST PATCH method for plain patch
- * @param[in] h CLIXON handle
- * @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
- * @param[in] pcvec Vector of path ie DOCUMENT_URI element
- * @param[in] pi Offset, where to start pcvec
- * @param[in] qvec Vector of query string (QUERY_STRING)
- * @param[in] data Stream input data
- * @param[in] pretty Set to 1 for pretty-printed xml/json output
- * @param[in] media_out Output media
- * Netconf: (nc:operation="merge")
- * See RFC8040 Sec 4.6.1
- */
-int
-api_data_patch(clicon_handle h,
- FCGX_Request *r,
- char *api_path0,
- cvec *pcvec,
- int pi,
- cvec *qvec,
- char *data,
- int pretty,
- restconf_media media_in,
- restconf_media media_out)
-{
- int retval = -1;
- yang_stmt *yspec;
- // yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */
- cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
- int i;
- char *api_path;
- cxobj *xtop = NULL; /* top of api-path */
- cxobj *xbot = NULL; /* bottom of api-path */
- yang_stmt *ybot = NULL; /* yang of xbot */
- cxobj *xerr = NULL; /* malloced must be freed */
- cxobj *xe;
- int ret;
-
- 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;
- for (i=0; i $fjukebox
module example-jukebox {
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index c266f627..068c6f56 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -202,8 +202,8 @@ expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '
new "Add leaf description using POST"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
-new "Add nothing using POST"
-expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":" on line 1: syntax error at or before:'
+new "Add nothing using POST (expect fail)"
+expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
new "restconf Check description added"
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}}
diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh
index 88e78b28..e14e6442 100755
--- a/test/test_restconf_jukebox.sh
+++ b/test/test_restconf_jukebox.sh
@@ -9,13 +9,26 @@ APPNAME=example
cfg=$dir/conf.xml
fjukebox=$dir/example-jukebox.yang
+# A "system" module as defined in B.2.4
+cat < $dir/example-system.yang
+ module example-system {
+ namespace "http://example.com/ns/example-system";
+ prefix "ex";
+ container system {
+ leaf enable-jukebox-streaming {
+ type boolean;
+ }
+ }
+ }
+EOF
+
# example
cat < $cfg
$cfg
/usr/local/share/clixon
$IETFRFC
- $fjukebox
+ $dir
false
/usr/local/var/$APPNAME/$APPNAME.sock
/usr/local/lib/$APPNAME/backend
@@ -52,102 +65,120 @@ wait_backend
wait_restconf
new "B.1.1. Retrieve the Top-Level API Resource root"
-expectpart "$(curl -s -i -X GET -H 'Accept: application/xrd+xml' http://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "" "" ""
+expectpart "$(curl -si -X GET -H 'Accept: application/xrd+xml' http://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "" "" ""
d='{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}'
new "B.1.1. Retrieve the Top-Level API Resource /restconf json"
-expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d"
+expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d"
new "B.1.1. Retrieve the Top-Level API Resource /restconf xml (not in RFC)"
-expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '2016-06-21'
+expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '2016-06-21'
# This just catches the header and the jukebox module, the RFC has foo and bar which
# seems wrong to recreate
new "B.1.2. Retrieve the Server Module Information"
-expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":' '"module":\[{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"}'
+expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":' '"module":\[{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"}'
new "B.1.3. Retrieve the Server Capability Information"
-expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' 'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit'
+expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' 'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit'
new "B.2.1. Create New Data Resources (artist+json)"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters"
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters"
new "B.2.1. Create New Data Resources (album+xml)"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light"
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light"
new "B.2.1. Add Data Resources again (conflict - not in RFC)"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 409 Conflict"
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d 'Wasting Light2011')" 0 "HTTP/1.1 409 Conflict"
-new "4.5. PUT replace content"
-expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content"
+
+new "B.2.2. Added genre"
+# Here the jbox identity is added
+expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content"
new "4.5. PUT replace content (xml encoding)"
-expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d 'Wasting Lightjbox:alternative2011')" 0 "HTTP/1.1 204 No Content"
+expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d 'Wasting Lightjbox:alternative2011')" 0 "HTTP/1.1 204 No Content"
new "4.5. PUT create new identity"
-expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"
-new "restconf DELETE whole datastore"
-expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
+new "4.5. Check jukebox content: 1 Clash and 1 Foo fighters album"
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Calling1979Foo FightersWasting Lightjbox:alternative2011'
+
+# First use of PATCH
+new "B.2.2. Detect Datastore Resource Entity-Tag Change (XXX if-unmodified)"
+expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light/genre -d '{"example-jukebox:genre":"example-jukebox:alternative"}')" 0 'HTTP/1.1 204 No Content'
+
+new "B.2.3. Edit a Datastore Resource (Add 1 Foo fighter and Nick cave album)"
+expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d 'trueFoo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 0 'HTTP/1.1 204 No Content'
+
+new "B.2.3. Check patch system"
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'true'
+
+new "B.2.3. Check jukebox: 1 Clash, 2 Foo Fighters, 1 Nick Cave"
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'ClashLondon Calling1979Foo FightersOne by One2012Wasting Lightalternative2011Nick Cave and the Bad SeedsTender Prey1988'
new "B.2.4. Replace a Datastore Resource"
-expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 0 "HTTP/1.1 204 No Content"
+
+new "B.2.4. Check replace"
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988'
new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert" Parameter'
JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1'
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1'
new 'B.3.4. "insert" Parameter first (RFC example says after)'
JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0'
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0'
new 'B.3.4. "insert" Parameter check order'
RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]"
-expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)'
JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
new 'B.3.5. "point" check order (0,2,1)'
RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]2/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]"
-expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new 'B.3.5. "point" Parameter 3 after 2 (using PUT)'
JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}"
-expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created"
new 'B.3.5. "point" check order (0,2,3,1)'
RES="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]2/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]3/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Something else'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]"
-expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3'
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3'
new 'B.3.4. "insert/point" leaf-list 2 first'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2'
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2'
new 'B.3.4. "insert/point" leaf-list 1 last'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
#new 'B.3.4. "insert/point" move leaf-list 1 last'
#- restconf cannot move a leaf-list(list?) item
-#expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
+#expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
new 'B.3.5. "insert/point" leaf-list check order (2,3,1)'
-expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '231'
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '231'
new 'B.3.5. "point" Parameter leaf-list 4 before 3'
-expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4'
+expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4'
new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
-expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431'
+expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431'
if false; then # NYI