/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2020-2022 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 /* clixon */ #include #include "restconf_lib.h" #include "restconf_handle.h" #include "restconf_api.h" #include "restconf_err.h" #include "restconf_methods_get.h" /* Forward */ static int api_data_pagination(clixon_handle h, void *req, char *api_path, int pi, cvec *qvec, int pretty, restconf_media media_out); /*! 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] 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 * @retval 0 OK * @retval -1 Error * @code * curl -X GET 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: , * @note there is an ad-hoc method to determine json pagination request instead of regular GET */ static int api_data_get2(clixon_handle h, void *req, char *api_path, 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; 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; char *defaults = NULL; cvec *nscd = NULL; clixon_debug(CLIXON_DBG_CLIENT, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clixon_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) < 0) goto done; break; default: break; } } clixon_debug(CLIXON_DBG_CLIENT, "%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, head) < 0) goto done; cbx = NULL; ok: retval = 0; done: clixon_debug(CLIXON_DBG_CLIENT, "%s retval:%d", __FUNCTION__, retval); if (xpath) free(xpath); if (nscd) cvec_free(nscd); 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; } /*! GET Collection * * According to restconf collection draft. Lists, work in progress * @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] 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 * @retval 0 OK * @retval -1 Error * @code * curl -X GET http://localhost/restconf/data/interfaces * @endcode * A collection resource contains a set of data resources. It is used * to represent a all instances or a subset of all instances in a YANG * list or leaf-list. * @see draft-ietf-netconf-restconf-collection-00.txt */ static int api_data_pagination(clixon_handle h, void *req, char *api_path, int pi, cvec *qvec, int pretty, restconf_media media_out) { 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 = 0; int i; int ret; cvec *nsc = NULL; char *attr; /* attribute value string */ netconf_content content = CONTENT_ALL; cxobj *xtop = NULL; cxobj *xbot = NULL; cxobj *xp; cxobj *xpr = NULL; yang_stmt *y = NULL; int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */ uint32_t limit = 0; uint32_t offset = 0; char *direction; char *sort; char *where; char *ns; clixon_debug(CLIXON_DBG_CLIENT, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clixon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } /* strip /... from start */ for (i=0; i, */ int api_data_get(clixon_handle h, void *req, char *api_path, int pi, cvec *qvec, int pretty, restconf_media media_out, ietf_ds_t ds) { int retval = -1; switch (media_out){ case YANG_DATA_XML: case YANG_DATA_JSON: /* ad-hoc algorithm in get to determine if a paginated request */ if (api_data_get2(h, req, api_path, pi, qvec, pretty, media_out, 0) < 0) goto done; break; case YANG_PAGINATION_XML: if (api_data_pagination(h, req, api_path, pi, qvec, pretty, media_out) < 0) goto done; default: break; } retval = 0; done: return retval; } /*! 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] 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 * @retval 0 OK * @retval -1 Error * @code * curl -G http://localhost/restconf/operations * @endcode * @see 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(clixon_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; clixon_debug(CLIXON_DBG_CLIENT, "%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) < 0) goto done; cbx = NULL; // ok: retval = 0; done: clixon_debug(CLIXON_DBG_CLIENT, "%s retval:%d", __FUNCTION__, retval); if (cbx) cbuf_free(cbx); if (xt) xml_free(xt); return retval; }