/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** * Restconf method implementation for operations get and data get and head */ #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 /* cligen */ #include /* clicon */ #include #include "restconf_lib.h" #include "restconf_handle.h" #include "restconf_api.h" #include "restconf_err.h" #include "restconf_methods_get.h" /*! Generic GET (both HEAD and GET) * According to restconf * @param[in] h Clixon handle * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * @param[in] head If 1 is HEAD, otherwise GET * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode * See RFC8040 Sec 4.2 and 4.3 * XXX: cant find a way to use Accept request field to choose Content-Type * I would like to support both xml and json. * Request may contain * Accept: application/yang.data+json,application/yang.data+xml * Response contains one of: * Content-Type: application/yang-data+xml * Content-Type: application/yang-data+json * NOTE: If a retrieval request for a data resource representing a YANG leaf- * list or list object identifies more than one instance, and XML * encoding is used in the response, then an error response containing a * "400 Bad Request" status-line MUST be returned by the server. * Netconf: , */ static int api_data_get2(clicon_handle h, void *req, char *api_path, cvec *pcvec, /* XXX remove? */ int pi, cvec *qvec, int pretty, restconf_media media_out, int head) { int retval = -1; char *xpath = NULL; cbuf *cbx = NULL; yang_stmt *yspec; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced */ cxobj *xe = NULL; /* not malloced */ cxobj **xvec = NULL; size_t xlen; int i; cxobj *x; int ret; char *namespace = NULL; cvec *nsc = NULL; char *attr; /* attribute value string */ netconf_content content = CONTENT_ALL; int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */ cxobj *xtop = NULL; cxobj *xbot = NULL; yang_stmt *y = NULL; clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } /* strip /... from start */ for (i=0; i0 * Out: {"example:x": {"0"}} */ if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0) goto done; break; default: break; } } clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) goto done; if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) goto done; if (restconf_reply_send(req, 200, cbx) < 0) goto done; ok: retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xpath) free(xpath); if (nsc) xml_nsctx_free(nsc); if (xtop) xml_free(xtop); if (cbx) cbuf_free(cbx); if (xret) xml_free(xret); if (xerr) xml_free(xerr); if (xvec) free(xvec); return retval; } /*! REST HEAD method * @param[in] h Clixon handle * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * * The HEAD method is sent by the client to retrieve just the header fields * that would be returned for the comparable GET method, without the * response message-body. * Relation to netconf: none */ int api_data_head(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi, cvec *qvec, int pretty, restconf_media media_out, ietf_ds_t ds) { return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 1); } /*! REST GET method * According to restconf * @param[in] h Clixon handle * @param[in] req Generic Www handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] media_out Output media * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode * XXX: cant find a way to use Accept request field to choose Content-Type * I would like to support both xml and json. * Request may contain * Accept: application/yang.data+json,application/yang.data+xml * Response contains one of: * Content-Type: application/yang-data+xml * Content-Type: application/yang-data+json * NOTE: If a retrieval request for a data resource representing a YANG leaf- * list or list object identifies more than one instance, and XML * encoding is used in the response, then an error response containing a * "400 Bad Request" status-line MUST be returned by the server. * Netconf: , */ int api_data_get(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi, cvec *qvec, int pretty, restconf_media media_out, ietf_ds_t ds) { return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 0); } /*! GET restconf/operations resource * @param[in] h Clixon handle * @param[in] req Generic Www handle * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @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 * * @code * curl -G http://localhost/restconf/operations * @endcode * RFC8040 Sec 3.3.2: * This optional resource is a container that provides access to the * data-model-specific RPC operations supported by the server. The * server MAY omit this resource if no data-model-specific RPC * operations are advertised. * From ietf-restconf.yang: * In XML, the YANG module namespace identifies the module: * * In JSON, the YANG module name identifies the module: * { 'ietf-system:system-restart' : [null] } */ int api_operations_get(clicon_handle h, void *req, char *path, int pi, cvec *qvec, char *data, int pretty, restconf_media media_out) { int retval = -1; yang_stmt *yspec; yang_stmt *ymod; /* yang module */ yang_stmt *yc; char *namespace; cbuf *cbx = NULL; cxobj *xt = NULL; int i; clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); if ((cbx = cbuf_new()) == NULL) goto done; switch (media_out){ case YANG_DATA_XML: cprintf(cbx, ""); break; case YANG_DATA_JSON: if (pretty) cprintf(cbx, "{\"operations\": {\n"); else cprintf(cbx, "{\"operations\":{"); break; default: break; } ymod = NULL; i = 0; while ((ymod = yn_each(yspec, ymod)) != NULL) { namespace = yang_find_mynamespace(ymod); yc = NULL; while ((yc = yn_each(ymod, yc)) != NULL) { if (yang_keyword_get(yc) != Y_RPC) continue; switch (media_out){ case YANG_DATA_XML: cprintf(cbx, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace); break; case YANG_DATA_JSON: if (i++){ cprintf(cbx, ","); if (pretty) cprintf(cbx, "\n\t"); } if (pretty) cprintf(cbx, "\"%s:%s\": [null]", yang_argument_get(ymod), yang_argument_get(yc)); else cprintf(cbx, "\"%s:%s\":[null]", yang_argument_get(ymod), yang_argument_get(yc)); break; default: break; } } } switch (media_out){ case YANG_DATA_XML: cprintf(cbx, ""); break; case YANG_DATA_JSON: if (pretty) cprintf(cbx, "}\n}"); else cprintf(cbx, "}}"); break; default: break; } if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0) goto done; if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) goto done; if (restconf_reply_send(req, 200, cbx) < 0) goto done; // ok: retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (cbx) cbuf_free(cbx); if (xt) xml_free(xt); return retval; }