* Most tests work with HTTP/2 support using nghttp2

* except non-ssl http/1->/2 upgrade
* Restconf: ensure HEAD method works everywhere GET does.
This commit is contained in:
Olof hagsand 2021-06-08 20:53:43 +02:00
parent b680e3c5ac
commit 84f5762ab5
59 changed files with 1683 additions and 1107 deletions

View file

@ -54,6 +54,8 @@
#include <arpa/inet.h>
#include <sys/resource.h>
#include <openssl/ssl.h>
/* cligen */
#include <cligen/cligen.h>
@ -61,13 +63,12 @@
#include <clixon/clixon.h>
#ifdef HAVE_LIBEVHTP
/* evhtp */
#include <event2/buffer.h> /* evbuffer */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#include <evhtp/sslutils.h> /* XXX inline this / use SSL directly */
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2 /* To get restconf_native.h include files right */
@ -81,7 +82,9 @@
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#ifdef HAVE_LIBEVHTP
#include "restconf_evhtp.h" /* evhtp http/1 */
#endif
#ifdef HAVE_LIBEVHTP
static char*
@ -264,15 +267,19 @@ evhtp_params_set(clicon_handle h,
goto fail;
}
clicon_debug(1, "%s conn->ssl:%d", __FUNCTION__, req->conn->ssl?1:0);
/* Slightly awkward way of taking cert subject and CN and add it to restconf parameters
* instead of accessing it directly */
if ((ssl = req->conn->ssl) != NULL){
if (restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
/* SSL subject fields, eg CN (Common Name) , can add more here? */
if ((subject = (char*)htp_sslutil_subject_tostr(req->conn->ssl)) != NULL){
if (ssl_x509_name_oneline(req->conn->ssl, &subject) < 0)
goto done;
if (subject != NULL) {
if (uri_str2cvec(subject, '/', '=', 1, &cvv) < 0)
goto done;
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
if (restconf_param_set(h, "SSL_CN", cn) < 0)
if (restconf_param_set(h, "SSL_CN", cn) < 0) /* Can be used by callback */
goto done;
}
}
@ -298,6 +305,111 @@ evhtp_params_set(clicon_handle h,
goto done;
}
/*! We got -1 back from lower layers, create a 500 Internal Server error
* Catch all on fatal error. This does not terminate the process but closes request
* stream
*/
static int
evhtp_internal_error(evhtp_request_t *req)
{
if (strlen(clicon_err_reason) &&
req->buffer_out){
evbuffer_add_printf(req->buffer_out, "%s", clicon_err_reason);
evhtp_send_reply(req, EVHTP_RES_500);
}
clicon_err_reset();
return 0;
}
/*! Send reply
* @see htp__create_reply_
*/
static int
native_send_reply(restconf_conn *rc,
restconf_stream_data *sd,
evhtp_request_t *req)
{
int retval = -1;
cg_var *cv;
int minor;
int major;
switch (req->proto) {
case EVHTP_PROTO_10:
if (req->flags & EVHTP_REQ_FLAG_KEEPALIVE) {
/* protocol is HTTP/1.0 and clients wants to keep established */
if (restconf_reply_header(sd, "Connection", "keep-alive") < 0)
goto done;
}
major = htparser_get_major(req->conn->parser); /* XXX Look origin */
minor = htparser_get_minor(req->conn->parser);
break;
case EVHTP_PROTO_11:
if (!(req->flags & EVHTP_REQ_FLAG_KEEPALIVE)) {
/* protocol is HTTP/1.1 but client wanted to close */
if (restconf_reply_header(sd, "Connection", "keep-alive") < 0)
goto done;
}
major = htparser_get_major(req->conn->parser);
minor = htparser_get_minor(req->conn->parser);
break;
default:
/* this sometimes happens when a response is made but paused before
* the method has been parsed */
major = 1;
minor = 0;
break;
}
cprintf(sd->sd_outp_buf, "HTTP/%u.%u %u %s\r\n",
major,
minor,
sd->sd_code,
restconf_code2reason(sd->sd_code));
/* Loop over headers */
cv = NULL;
while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL)
cprintf(sd->sd_outp_buf, "%s: %s\r\n", cv_name_get(cv), cv_string_get(cv));
cprintf(sd->sd_outp_buf, "\r\n");
// cvec_reset(rc->rc_outp_hdrs); /* Is now done in restconf_connection but can be done here */
retval = 0;
done:
return retval;
}
/*!
*/
static int
restconf_evhtp_reply(restconf_conn *rc,
restconf_stream_data *sd,
evhtp_request_t *req)
{
int retval = -1;
req->status = sd->sd_code;
req->flags |= EVHTP_REQ_FLAG_FINISHED; /* Signal to evhtp to read next request */
/* If body, add a content-length header
* A server MUST NOT send a Content-Length header field in any response
* with a status code of 1xx (Informational) or 204 (No Content). A
* server MUST NOT send a Content-Length header field in any 2xx
* (Successful) response to a CONNECT request (Section 4.3.6 of
* [RFC7231]).
*/
if (restconf_reply_header(sd, "Content-Length", "%d",
(sd->sd_body!=NULL)?cbuf_len(sd->sd_body):0) < 0)
goto done;
/* Create reply and write headers */
if (native_send_reply(rc, sd, req) < 0)
goto done;
/* Write a body */
if (sd->sd_body){
cbuf_append_str(sd->sd_outp_buf, cbuf_get(sd->sd_body));
}
retval = 0;
done:
return retval;
}
/*! Callback for each incoming http request for path /
*
* This are all messages except /.well-known, Registered with evhtp_set_cb
@ -314,16 +426,35 @@ void
restconf_path_root(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
cvec *qvec = NULL;
int retval = -1;
clicon_handle h;
int ret;
cvec *qvec = NULL;
evhtp_connection_t *evconn;
restconf_conn *rc;
restconf_stream_data *sd;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
evhtp_internal_error(req);
goto done;
}
/* evhtp connect struct */
if ((evconn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
evhtp_internal_error(req);
goto done;
}
/* get clixon request connect pointer from generic evhtp application pointer */
rc = evconn->arg;
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "No stream_data");
evhtp_internal_error(req);
goto done;
}
sd->sd_req = req;
sd->sd_proto = (req->proto == EVHTP_PROTO_10)?HTTP_10:HTTP_11;
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
@ -332,27 +463,55 @@ restconf_path_root(evhtp_request_t *req,
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
evhtp_internal_error(req);
goto done;
}
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, qvec)) < 0)
/* Get indata
*/
{
size_t len;
unsigned char *buf;
len = evbuffer_get_length(req->buffer_in);
if (len > 0){
if ((buf = evbuffer_pullup(req->buffer_in, len)) == NULL){
clicon_err(OE_CFG, errno, "evbuffer_pullup");
goto done;
}
/* Note the pullup may not be null-terminated */
cbuf_append_buf(sd->sd_indata, buf, len);
}
}
/* set fcgi-like paramaters (ignore query vector)
* ret = 0 means an error has already been sent
*/
if ((ret = evhtp_params_set(h, req, qvec)) < 0){
evhtp_internal_error(req);
goto done;
}
if (ret == 1){
/* call generic function */
if (api_root_restconf(h, req, qvec) < 0)
goto done;
if (api_root_restconf(h, sd, qvec) < 0){
evhtp_internal_error(req);
goto done;
}
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
/* Clear input request parameters from this request */
if (restconf_param_del_all(h) < 0){
evhtp_internal_error(req);
goto done;
}
/* All parameters for sending a reply are here
*/
if (sd->sd_code){
if (restconf_evhtp_reply(rc, sd, req) < 0){
evhtp_internal_error(req);
goto done;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
if (qvec)
cvec_free(qvec);
return; /* void */
@ -368,37 +527,67 @@ void
restconf_path_wellknown(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
int retval = -1;
clicon_handle h;
int ret;
evhtp_connection_t *evconn;
restconf_conn *rc;
restconf_stream_data *sd;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
evhtp_internal_error(req);
goto done;
}
/* evhtp connect struct */
if ((evconn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
evhtp_internal_error(req);
goto done;
}
/* get clixon request connect pointer from generic evhtp application pointer */
rc = evconn->arg;
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "No stream_data");
evhtp_internal_error(req);
goto done;
}
sd->sd_req = req;
sd->sd_proto = (req->proto == EVHTP_PROTO_10)?HTTP_10:HTTP_11;
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, NULL)) < 0)
if ((ret = evhtp_params_set(h, req, NULL)) < 0){
evhtp_internal_error(req);
goto done;
}
if (ret == 1){
/* call generic function */
if (api_well_known(h, req) < 0)
if (api_well_known(h, sd) < 0){
evhtp_internal_error(req);
goto done;
}
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
/* Clear input request parameters from this request */
if (restconf_param_del_all(h) < 0){
evhtp_internal_error(req);
goto done;
}
/* All parameters for sending a reply are here
*/
if (sd->sd_code){
if (restconf_evhtp_reply(rc, sd, req) < 0){
evhtp_internal_error(req);
goto done;
}
}
retval = 0;
done:
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return; /* void */
}
#endif /* HAVE_LIBEVHTP */