diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index 3fc907f1dc..c2ef1cff6e 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -2376,20 +2376,14 @@ wrap_xmlfree(isc_buffer_t *buffer, void *arg) { } static isc_result_t -render_xml(uint32_t flags, const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, const char **mimetype, - isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { +render_xml(uint32_t flags, void *arg, unsigned int *retcode, + const char **retmsg, const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { unsigned char *msg = NULL; int msglen; named_server_t *server = arg; isc_result_t result; - UNUSED(url); - UNUSED(urlinfo); - UNUSED(headers); - UNUSED(querystring); - result = generatexml(server, flags, &msglen, &msg); if (result == ISC_R_SUCCESS) { @@ -2410,91 +2404,91 @@ render_xml(uint32_t flags, const char *url, isc_httpdurl_t *urlinfo, } static isc_result_t -render_xml_all(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_all(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_ALL, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_ALL, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_xml_status(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_status(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_STATUS, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_STATUS, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_xml_server(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_server(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_SERVER, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_SERVER, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_xml_zones(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_zones(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_ZONES, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_ZONES, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_xml_net(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_net(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_NET, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_NET, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_xml_tasks(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_tasks(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_TASKS, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_TASKS, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_xml_mem(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_mem(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_MEM, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_MEM, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_xml_traffic(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_xml_traffic(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_xml(STATS_XML_TRAFFIC, url, urlinfo, querystring, - headers, arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_TRAFFIC, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } #endif /* HAVE_LIBXML2 */ @@ -3320,10 +3314,9 @@ cleanup: } static isc_result_t -render_json(uint32_t flags, const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, const char **mimetype, - isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { +render_json(uint32_t flags, void *arg, unsigned int *retcode, + const char **retmsg, const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { isc_result_t result; json_object *bindstats = NULL; named_server_t *server = arg; @@ -3331,11 +3324,6 @@ render_json(uint32_t flags, const char *url, isc_httpdurl_t *urlinfo, size_t msglen = 0; char *p; - UNUSED(url); - UNUSED(urlinfo); - UNUSED(headers); - UNUSED(querystring); - result = generatejson(server, &msglen, &msg, &bindstats, flags); if (result == ISC_R_SUCCESS) { *retcode = 200; @@ -3356,156 +3344,139 @@ render_json(uint32_t flags, const char *url, isc_httpdurl_t *urlinfo, } static isc_result_t -render_json_all(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_all(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_ALL, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_ALL, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_json_status(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_status(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_STATUS, url, urlinfo, querystring, - headers, arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_STATUS, arg, retcode, retmsg, mimetype, + b, freecb, freecb_args)); } static isc_result_t -render_json_server(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_server(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_SERVER, url, urlinfo, querystring, - headers, arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_SERVER, arg, retcode, retmsg, mimetype, + b, freecb, freecb_args)); } static isc_result_t -render_json_zones(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_zones(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_ZONES, url, urlinfo, querystring, - headers, arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_ZONES, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_json_mem(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_mem(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_MEM, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_MEM, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_json_tasks(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_tasks(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_TASKS, url, urlinfo, querystring, - headers, arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_TASKS, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_json_net(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_net(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_NET, url, urlinfo, querystring, headers, - arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_NET, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); } static isc_result_t -render_json_traffic(const char *url, isc_httpdurl_t *urlinfo, - const char *querystring, const char *headers, void *arg, - unsigned int *retcode, const char **retmsg, +render_json_traffic(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, const char **mimetype, isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { - return (render_json(STATS_JSON_TRAFFIC, url, urlinfo, querystring, - headers, arg, retcode, retmsg, mimetype, b, freecb, - freecb_args)); + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_TRAFFIC, arg, retcode, retmsg, mimetype, + b, freecb, freecb_args)); } #endif /* HAVE_JSON_C */ static isc_result_t -render_xsl(const char *url, isc_httpdurl_t *urlinfo, const char *querystring, - const char *headers, void *args, unsigned int *retcode, - const char **retmsg, const char **mimetype, isc_buffer_t *b, - isc_httpdfree_t **freecb, void **freecb_args) { +render_xsl(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *args, + unsigned int *retcode, const char **retmsg, const char **mimetype, + isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { isc_result_t result; - char *_headers = NULL; - char *p; + char *p = NULL; - UNUSED(url); - UNUSED(querystring); + UNUSED(httpd); UNUSED(args); *freecb = NULL; *freecb_args = NULL; *mimetype = "text/xslt+xml"; - if (urlinfo->isstatic) { - isc_time_t when; - char *line, *saveptr; - const char *if_modified_since = "If-Modified-Since: "; - _headers = strdup(headers); + if (isc_httpdurl_isstatic(urlinfo)) { + time_t t1, t2; + const isc_time_t *when; + const isc_time_t *loadtime; - if (_headers == NULL) { + when = isc_httpd_if_modified_since(httpd); + + if (isc_time_isepoch(when)) { goto send; } - saveptr = NULL; - for (line = strtok_r(_headers, "\n", &saveptr); line; - line = strtok_r(NULL, "\n", &saveptr)) - { - if (strncasecmp(line, if_modified_since, - strlen(if_modified_since)) == 0) { - time_t t1, t2; - line += strlen(if_modified_since); - result = isc_time_parsehttptimestamp(line, - &when); - if (result != ISC_R_SUCCESS) { - goto send; - } - - result = isc_time_secondsastimet(&when, &t1); - if (result != ISC_R_SUCCESS) { - goto send; - } - - result = isc_time_secondsastimet( - &urlinfo->loadtime, &t2); - if (result != ISC_R_SUCCESS) { - goto send; - } - - if (t1 < t2) { - goto send; - } - - *retcode = 304; - *retmsg = "Not modified"; - goto end; - } + result = isc_time_secondsastimet(when, &t1); + if (result != ISC_R_SUCCESS) { + goto send; } + + loadtime = isc_httpdurl_loadtime(urlinfo); + + result = isc_time_secondsastimet(loadtime, &t2); + if (result != ISC_R_SUCCESS) { + goto send; + } + + if (t1 < t2) { + goto send; + } + + *retcode = 304; + *retmsg = "Not modified"; + goto end; } send: @@ -3515,7 +3486,6 @@ send: isc_buffer_reinit(b, p, strlen(xslmsg)); isc_buffer_add(b, strlen(xslmsg)); end: - free(_headers); return (ISC_R_SUCCESS); } diff --git a/lib/isc/httpd.c b/lib/isc/httpd.c index 2a0352d198..35575b44e9 100644 --- a/lib/isc/httpd.c +++ b/lib/isc/httpd.c @@ -13,6 +13,7 @@ /*! \file */ +#include #include #include #include @@ -26,8 +27,12 @@ #include #include #include +#include #include +#include "netmgr/netmgr-int.h" +#include "picohttpparser.h" + #ifdef HAVE_ZLIB #include #endif /* ifdef HAVE_ZLIB */ @@ -40,9 +45,13 @@ } \ } while (0) -#define HTTP_RECVLEN 4096 -#define HTTP_SENDGROW 1024 -#define HTTP_SEND_MAXLEN 10240 +/* + * Size the recv buffer to hold at maximum two full buffers from isc_nm_read(), + * so we don't have to handle the truncation. + */ +#define HTTP_RECVLEN ISC_NETMGR_TCP_RECVBUF_SIZE * 2 +#define HTTP_SENDLEN ISC_NETMGR_TCP_RECVBUF_SIZE +#define HTTP_HEADERS_NUM 10 #define HTTPD_CLOSE 0x0001 /* Got a Connection: close header */ #define HTTPD_FOUNDHOST 0x0002 /* Got a Host: header */ @@ -55,21 +64,27 @@ #define HTTPDMGR_MAGIC ISC_MAGIC('H', 'p', 'd', 'm') #define VALID_HTTPDMGR(m) ISC_MAGIC_VALID(m, HTTPDMGR_MAGIC) -/*% - * Client states. - * - * _RECV The client is waiting for data after starting a read. - * _SEND All data for a response has completed, and a reply was - * sent via a send call. - */ - -typedef enum { RECV, SEND } state_t; - /*% * HTTP methods. */ typedef enum { METHOD_UNKNOWN = 0, METHOD_GET = 1, METHOD_POST = 2 } method_t; +/*% + * HTTP urls. These are the URLs we manage, and the function to call to + * provide the data for it. We pass in the base url (so the same function + * can handle multiple requests), and a structure to fill in to return a + * result to the client. We also pass in a pointer to be filled in for + * the data cleanup function. + */ +struct isc_httpdurl { + char *url; + isc_httpdaction_t *action; + void *action_arg; + bool isstatic; + isc_time_t loadtime; + ISC_LINK(isc_httpdurl_t) link; +}; + /*% http client */ struct isc_httpd { unsigned int magic; /* HTTPD_MAGIC */ @@ -79,59 +94,21 @@ struct isc_httpd { isc_nmhandle_t *handle; /* Permanent pointer to handle */ isc_nmhandle_t *readhandle; /* Waiting for a read callback */ - isc_nmhandle_t *sendhandle; /* Waiting for a send callback */ - state_t state; int flags; /*% * Received data state. */ char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */ - uint32_t recvlen; /*%< length recv'd */ - uint32_t consume; /*%< length of last command */ - char *headers; /*%< set in process_request() */ - bool truncated; + size_t recvlen; /*%< length recv'd */ + size_t consume; /*%< length of last command */ + method_t method; - char *url; - char *querystring; - char *protocol; - - /*% - * Transmit data state. - * - * This is the data buffer we will transmit. - * - * This free function pointer is filled in by the rendering function - * we call. The free function is called after the data is transmitted - * to the client. - * - * The bufflist is the list of buffers we are currently transmitting. - * The headerbuffer is where we render our headers to. If we run out - * of space when rendering a header, we will change the size of our - * buffer. We will not free it until we are finished, and will - * allocate an additional HTTP_SENDGROW bytes per header space grow. - * - * We currently use three buffers total, one for the headers (which - * we manage), another for the client to fill in (which it manages, - * it provides the space for it, etc) -- we will pass that buffer - * structure back to the caller, who is responsible for managing the - * space it may have allocated as backing store for it. This second - * buffer is bodybuffer, and we only allocate the buffer itself, not - * the backing store. - * The third buffer is compbuffer, managed by us, that contains the - * compressed HTTP data, if compression is used. - */ - isc_buffer_t headerbuffer; - isc_buffer_t compbuffer; - isc_buffer_t *sendbuffer; - - const char *mimetype; - unsigned int retcode; - const char *retmsg; - isc_buffer_t bodybuffer; - isc_httpdfree_t *freecb; - void *freecb_arg; + int minor_version; + const char *path; + isc_url_parser_t up; + isc_time_t if_modified_since; }; struct isc_httpdmgr { @@ -154,6 +131,46 @@ struct isc_httpdmgr { isc_httpdaction_t *render_500; }; +typedef struct isc_httpd_sendreq { + isc_mem_t *mctx; + isc_httpd_t *httpd; + isc_nmhandle_t *handle; + + /*% + * Transmit data state. + * + * This is the data buffer we will transmit. + * + * This free function pointer is filled in by the rendering function + * we call. The free function is called after the data is transmitted + * to the client. + * + * We currently use three buffers total: + * + * sendbuffer - gets filled as we gather the data + * + * bodybuffer - for the client to fill in (which it manages, it provides + * the space for it, etc) -- we will pass that buffer structure back to + * the caller, who is responsible for managing the space it may have + * allocated as backing store for it. we only allocate the buffer + * itself, not the backing store. + * + * compbuffer - managed by us, that contains the compressed HTTP data, + * if compression is used. + */ + isc_buffer_t *sendbuffer; + isc_buffer_t *compbuffer; + + isc_buffer_t bodybuffer; + + const char *mimetype; + unsigned int retcode; + const char *retmsg; + isc_httpdfree_t *freecb; + void *freecb_arg; + +} isc_httpd_sendreq_t; + static isc_result_t httpd_newconn(isc_nmhandle_t *, isc_result_t, void *); static void @@ -165,19 +182,17 @@ httpd_reset(void *); static void httpd_put(void *); -static isc_result_t -httpd_addheader(isc_httpd_t *, const char *, const char *); -static isc_result_t -httpd_addheaderuint(isc_httpd_t *, const char *, int); -static isc_result_t -httpd_endheaders(isc_httpd_t *); -static isc_result_t -httpd_response(isc_httpd_t *); +static void +httpd_addheader(isc_httpd_sendreq_t *, const char *, const char *); +static void +httpd_addheaderuint(isc_httpd_sendreq_t *, const char *, int); +static void +httpd_endheaders(isc_httpd_sendreq_t *); +static void +httpd_response(isc_httpd_t *, isc_httpd_sendreq_t *); static isc_result_t -process_request(isc_httpd_t *, isc_region_t *, size_t *); -static isc_result_t -grow_headerspace(isc_httpd_t *); +process_request(isc_httpd_t *, size_t); static isc_httpdaction_t render_404; static isc_httpdaction_t render_500; @@ -194,18 +209,6 @@ httpdmgr_attach(isc_httpdmgr_t *, isc_httpdmgr_t **); static void httpdmgr_detach(isc_httpdmgr_t **); -static void -free_buffer(isc_mem_t *mctx, isc_buffer_t *buffer) { - isc_region_t r; - - isc_buffer_region(buffer, &r); - if (r.base != NULL) { - isc_mem_put(mctx, r.base, r.length); - } - - isc_buffer_initnull(buffer); -} - isc_result_t isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr, isc_httpdclientok_t *client_ok, @@ -315,317 +318,179 @@ destroy_httpdmgr(isc_httpdmgr_t *httpdmgr) { isc_mem_putanddetach(&httpdmgr->mctx, httpdmgr, sizeof(isc_httpdmgr_t)); } -#define LENGTHOK(s) (httpd->recvbuf - (s) < (int)httpd->recvlen) -#define BUFLENOK(s) (httpd->recvbuf - (s) < HTTP_RECVLEN) - -/* - * Look for the given header in headers. - * If value is specified look for it terminated with a character in eov. - * If fvalue is specified and the header was found, then *fvalue will point to - * the found header's value. - */ static bool -have_header(isc_httpd_t *httpd, const char *header, const char *value, - const char *eov, const char **fvalue) { - char *cr, *nl, *h; - size_t hlen, vlen = 0; +name_match(const struct phr_header *header, const char *match) { + size_t match_len = strlen(match); + if (match_len != header->name_len) { + return (false); + } + return (strncasecmp(header->name, match, match_len) == 0); +} - h = httpd->headers; - hlen = strlen(header); - if (value != NULL) { - INSIST(eov != NULL); - vlen = strlen(value); +static bool +value_match(const struct phr_header *header, const char *match) { + size_t match_len = strlen(match); + size_t limit; + + if (match_len > header->value_len) { + return (false); } - for (;;) { - if (strncasecmp(h, header, hlen) != 0) { - /* - * Skip to next line; - */ - cr = strchr(h, '\r'); - if (cr != NULL && cr[1] == '\n') { - cr++; - } - nl = strchr(h, '\n'); + limit = header->value_len - match_len + 1; - /* last header? */ - h = cr; - if (h == NULL || (nl != NULL && nl < h)) { - h = nl; + for (size_t i = 0; i < limit; i++) { + if (isspace(header->value[i])) { + while (i < limit && isspace(header->value[i])) { + i++; } - if (h == NULL) { - return (false); - } - h++; continue; } - /* - * Skip optional leading white space. - */ - h += hlen; - while (*h == ' ' || *h == '\t') { - h++; - } - - /* - * Set the found value. - */ - if (fvalue != NULL) { - *fvalue = h; - } - - if (value == NULL) { - return (true); - } - - /* - * Terminate token search on NULL or EOL. - */ - while (*h != 0 && *h != '\r' && *h != '\n') { - if (strncasecmp(h, value, vlen) == 0) { - if (strchr(eov, h[vlen]) != NULL) { - return (true); - /* - * Skip to next token. - */ - } - } + if (strncasecmp(&header->value[i], match, match_len) == 0) { + i += match_len; /* - * Skip to next token. + * Sanity check; f.e. for 'deflate' match only + * 'deflate[,;]', but not 'deflateyou' */ - h += strcspn(h, eov); - if (h[0] == '\r' && h[1] == '\n') { - h++; - } - if (h[0] != 0) { - h++; + if (i == header->value_len || header->value[i] == ',' || + header->value[i] == ';') + { + return (true); } } - return (false); + while (i < limit && header->value[i] != ',') { + i++; + } } + return (false); } static isc_result_t -process_request(isc_httpd_t *httpd, isc_region_t *region, size_t *buflen) { - char *s = NULL, *p = NULL, *urlend = NULL; - const char *content_length = NULL; - size_t limit = sizeof(httpd->recvbuf) - httpd->recvlen - 1; - size_t len = region->length; - int delim; - bool truncated = false; +process_request(isc_httpd_t *httpd, size_t last_len) { + int pret; + const char *method = NULL; + size_t method_len = 0; + const char *path; + size_t path_len = 0; + struct phr_header headers[HTTP_HEADERS_NUM]; + size_t num_headers; + isc_result_t result; - if (len > limit) { - len = limit; - truncated = true; + num_headers = ARRAY_SIZE(headers); + + pret = phr_parse_request(httpd->recvbuf, httpd->recvlen, &method, + &method_len, &path, &path_len, + &httpd->minor_version, headers, &num_headers, + last_len); + + if (pret == -1) { + /* Parse Error */ + return (ISC_R_UNEXPECTED); + } else if (pret == -2) { + /* Need more data */ + return (ISC_R_NOMORE); } - if (len > 0U) { - if (httpd->truncated) { - return (ISC_R_NOSPACE); - } - memmove(httpd->recvbuf + httpd->recvlen, region->base, len); - httpd->recvlen += len; - httpd->recvbuf[httpd->recvlen] = 0; - isc_region_consume(region, len); - } - if (truncated) { - httpd->truncated = true; - } - httpd->headers = NULL; - *buflen = httpd->recvlen; + INSIST(pret > 0); - /* - * If we don't find a blank line in our buffer, return that we need - * more data. - */ - s = strstr(httpd->recvbuf, "\r\n\r\n"); - delim = 2; - if (s == NULL) { - s = strstr(httpd->recvbuf, "\n\n"); - delim = 1; - if (s == NULL) { - return (httpd->truncated ? ISC_R_NOSPACE - : ISC_R_NOTFOUND); - } - httpd->consume = s + 2 - httpd->recvbuf; - } else { - httpd->consume = s + 4 - httpd->recvbuf; - } - - /* - * NULL terminate the request at the blank line. - */ - s[delim] = 0; + httpd->consume = pret; /* * Determine if this is a POST or GET method. Any other values will * cause an error to be returned. */ - if (strncmp(httpd->recvbuf, "GET ", 4) == 0) { + if (strncmp(method, "GET ", method_len) == 0) { httpd->method = METHOD_GET; - p = httpd->recvbuf + 4; - } else if (strncmp(httpd->recvbuf, "POST ", 5) == 0) { + } else if (strncmp(method, "POST ", method_len) == 0) { httpd->method = METHOD_POST; - p = httpd->recvbuf + 5; } else { return (ISC_R_RANGE); } /* - * From now on, p is the start of our buffer. + * Parse the URL */ - - /* - * Extract the URL. - */ - s = p; - while (LENGTHOK(s) && BUFLENOK(s) && - (*s != '\n' && *s != '\r' && *s != '\0' && *s != ' ')) - { - s++; + result = isc_url_parse(path, path_len, 0, &httpd->up); + if (result != ISC_R_SUCCESS) { + return (result); } - if (!LENGTHOK(s)) { - return (ISC_R_NOTFOUND); - } - if (!BUFLENOK(s)) { - return (ISC_R_NOMEMORY); - } - urlend = s; + httpd->path = path; - /* - * Make the URL relative. - */ - if (strncmp(p, "http://", 7) == 0 || strncmp(p, "https://", 8) == 0) { - /* Skip first '/' */ - while (*p != '/' && *p != 0) { - p++; - } - if (*p == 0) { - return (ISC_R_RANGE); - } - p++; - /* Skip second '/' */ - while (*p != '/' && *p != 0) { - p++; - } - if (*p == 0) { - return (ISC_R_RANGE); - } - p++; - /* Find third '/' */ - while (*p != '/' && *p != 0) { - p++; - } - if (*p == 0) { - p--; - *p = '/'; - } - } + ssize_t content_len = 0; + bool keep_alive = false; - httpd->url = p; - p = s + 1; - s = p; + isc_time_set(&httpd->if_modified_since, 0, 0); - /* - * Now, see if there is a question mark in the URL. If so, this is - * part of the query string, and we will split it from the URL. - */ - httpd->querystring = strchr(httpd->url, '?'); - if (httpd->querystring != NULL) { - *(httpd->querystring) = 0; - httpd->querystring++; + for (size_t i = 0; i < num_headers; i++) { + struct phr_header *header = &headers[i]; + + if (name_match(header, "Content-Length")) { + char *endptr; + content_len = (size_t)strtoul(header->value, &endptr, + 10); + /* Consistency check, if we consumed all numbers */ + if ((header->value + header->value_len) != endptr) { + return (ISC_R_RANGE); + } + } else if (name_match(header, "Connection")) { + if (value_match(header, "close")) { + httpd->flags |= HTTPD_CLOSE; + } else if (value_match(header, "keep-alive")) { + keep_alive = true; + } + } else if (name_match(header, "Host")) { + httpd->flags |= HTTPD_FOUNDHOST; + } else if (name_match(header, "Accept-Encoding")) { + if (value_match(header, "deflate")) { + httpd->flags |= HTTPD_ACCEPT_DEFLATE; + } + } else if (name_match(header, "If-Modified-Since")) { + char timestamp[ISC_FORMATHTTPTIMESTAMP_SIZE + 1]; + memmove(timestamp, header->value, header->value_len); + timestamp[header->value_len] = 0; + + /* Ignore the value if it can't be parsed */ + (void)isc_time_parsehttptimestamp( + timestamp, &httpd->if_modified_since); + } } /* - * Extract the HTTP/1.X protocol. We will bounce on anything but - * HTTP/1.0 or HTTP/1.1 for now. + * The Content-Length is optional in an HTTP request. + * For a GET the length must be zero. */ - while (LENGTHOK(s) && BUFLENOK(s) && - (*s != '\n' && *s != '\r' && *s != '\0')) { - s++; - } - if (!LENGTHOK(s)) { - return (ISC_R_NOTFOUND); - } - if (!BUFLENOK(s)) { - return (ISC_R_NOMEMORY); - } - /* - * Check that we have the expected eol delimiter. - */ - if (strncmp(s, delim == 1 ? "\n" : "\r\n", delim) != 0) { - return (ISC_R_RANGE); - } - *s = 0; - if ((strncmp(p, "HTTP/1.0", 8) != 0) && - (strncmp(p, "HTTP/1.1", 8) != 0)) { - return (ISC_R_RANGE); - } - httpd->protocol = p; - p = s + delim; /* skip past eol */ - s = p; - - httpd->headers = s; - - if (!have_header(httpd, "Content-Length:", NULL, NULL, &content_length)) - { - /* Require a Content-Length header for POST requests. */ - if (httpd->method == METHOD_POST) { - return (ISC_R_BADNUMBER); - } - } else { - INSIST(content_length != NULL); - - size_t clen = (size_t)strtoul(content_length, NULL, 10); - if (clen == ULONG_MAX) { - /* Invalid number in the header value. */ - return (ISC_R_BADNUMBER); - } - if (httpd->recvlen < httpd->consume + clen) { - /* The request data isn't complete yet. */ - return (ISC_R_NOTFOUND); - } - - /* Consume the request's data, which we do not use. */ - httpd->consume += clen; + if (httpd->method == METHOD_GET && content_len != 0) { + return (ISC_R_BADNUMBER); } - if (have_header(httpd, "Connection:", "close", ", \t\r\n", NULL)) { - httpd->flags |= HTTPD_CLOSE; + if (content_len == (ssize_t)ULONG_MAX) { + /* Invalid number in the header value. */ + return (ISC_R_BADNUMBER); + } + if (httpd->consume + content_len > httpd->recvlen) { + /* The request data isn't complete yet. */ + return (ISC_R_NOMORE); } - if (have_header(httpd, "Host:", NULL, NULL, NULL)) { - httpd->flags |= HTTPD_FOUNDHOST; - } + /* Consume the request's data, which we do not use. */ + httpd->consume += content_len; - if (strncmp(httpd->protocol, "HTTP/1.0", 8) == 0) { - if (have_header(httpd, "Connection:", "Keep-Alive", ", \t\r\n", - NULL)) { + switch (httpd->minor_version) { + case 0: + if (keep_alive == true) { httpd->flags |= HTTPD_KEEPALIVE; } else { httpd->flags |= HTTPD_CLOSE; } - } - - /* - * Check for Accept-Encoding: - */ -#ifdef HAVE_ZLIB - if (have_header(httpd, "Accept-Encoding:", "deflate", ";, \t\r\n", - NULL)) { - httpd->flags |= HTTPD_ACCEPT_DEFLATE; - } -#endif /* ifdef HAVE_ZLIB */ - - /* - * Standards compliance hooks here. - */ - if (strcmp(httpd->protocol, "HTTP/1.1") == 0 && - ((httpd->flags & HTTPD_FOUNDHOST) == 0)) - { - return (ISC_R_RANGE); + break; + case 1: + if ((httpd->flags & HTTPD_FOUNDHOST) == 0) { + return (ISC_R_RANGE); + } + break; + default: + return (ISC_R_UNEXPECTED); } /* @@ -635,7 +500,6 @@ process_request(isc_httpd_t *httpd, isc_region_t *region, size_t *buflen) { * the next read will overwrite this one instead of appending * to the buffer. */ - *urlend = 0; return (ISC_R_SUCCESS); } @@ -658,17 +522,46 @@ httpd_reset(void *arg) { httpd->recvbuf[0] = 0; httpd->recvlen = 0; httpd->consume = 0; - httpd->truncated = false; - httpd->headers = NULL; httpd->method = METHOD_UNKNOWN; - httpd->url = NULL; - httpd->querystring = NULL; - httpd->protocol = NULL; httpd->flags = 0; - isc_buffer_clear(&httpd->headerbuffer); - isc_buffer_clear(&httpd->compbuffer); - isc_buffer_invalidate(&httpd->bodybuffer); + httpd->minor_version = -1; + httpd->path = NULL; + httpd->up = (isc_url_parser_t){ 0 }; + isc_time_set(&httpd->if_modified_since, 0, 0); +} + +static void +isc__httpd_sendreq_free(isc_httpd_sendreq_t *req) { + /* Clean up buffers */ + + isc_buffer_free(&req->sendbuffer); + + isc_mem_putanddetach(&req->mctx, req, sizeof(*req)); +} + +static isc_httpd_sendreq_t * +isc__httpd_sendreq_new(isc_httpd_t *httpd) { + isc_httpdmgr_t *httpdmgr = httpd->mgr; + isc_httpd_sendreq_t *req; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + req = isc_mem_get(httpdmgr->mctx, sizeof(*req)); + *req = (isc_httpd_sendreq_t){ 0 }; + + isc_mem_attach(httpdmgr->mctx, &req->mctx); + + /* + * Initialize the buffer for our headers. + */ + isc_buffer_allocate(req->mctx, &req->sendbuffer, HTTP_SENDLEN); + isc_buffer_clear(req->sendbuffer); + isc_buffer_setautorealloc(req->sendbuffer, true); + + isc_buffer_initnull(&req->bodybuffer); + + return (req); } static void @@ -684,9 +577,6 @@ httpd_put(void *arg) { httpd->magic = 0; httpd->mgr = NULL; - free_buffer(mgr->mctx, &httpd->headerbuffer); - free_buffer(mgr->mctx, &httpd->compbuffer); - isc_mem_put(mgr->mctx, httpd, sizeof(*httpd)); httpdmgr_detach(&mgr); @@ -701,7 +591,6 @@ httpd_put(void *arg) { static void new_httpd(isc_httpdmgr_t *httpdmgr, isc_nmhandle_t *handle) { isc_httpd_t *httpd = NULL; - char *headerdata = NULL; REQUIRE(VALID_HTTPDMGR(httpdmgr)); @@ -719,22 +608,9 @@ new_httpd(isc_httpdmgr_t *httpdmgr, isc_nmhandle_t *handle) { INSIST(httpd->handle == handle); } - /* - * Initialize the buffer for our headers. - */ - headerdata = isc_mem_get(httpdmgr->mctx, HTTP_SENDGROW); - isc_buffer_init(&httpd->headerbuffer, headerdata, HTTP_SENDGROW); - isc_buffer_clear(&httpd->headerbuffer); - - isc_buffer_initnull(&httpd->compbuffer); - isc_buffer_clear(&httpd->compbuffer); - - isc_buffer_initnull(&httpd->bodybuffer); - ISC_LINK_INIT(httpd, link); httpd->magic = HTTPD_MAGIC; - httpd->state = RECV; LOCK(&httpdmgr->lock); ISC_LIST_APPEND(httpdmgr->running, httpd, link); @@ -773,16 +649,13 @@ httpd_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) { } static isc_result_t -render_404(const char *url, isc_httpdurl_t *urlinfo, const char *querystring, - const char *headers, void *arg, unsigned int *retcode, - const char **retmsg, const char **mimetype, isc_buffer_t *b, - isc_httpdfree_t **freecb, void **freecb_args) { +render_404(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg, + unsigned int *retcode, const char **retmsg, const char **mimetype, + isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { static char msg[] = "No such URL.\r\n"; - UNUSED(url); + UNUSED(httpd); UNUSED(urlinfo); - UNUSED(querystring); - UNUSED(headers); UNUSED(arg); *retcode = 404; @@ -797,16 +670,13 @@ render_404(const char *url, isc_httpdurl_t *urlinfo, const char *querystring, } static isc_result_t -render_500(const char *url, isc_httpdurl_t *urlinfo, const char *querystring, - const char *headers, void *arg, unsigned int *retcode, - const char **retmsg, const char **mimetype, isc_buffer_t *b, - isc_httpdfree_t **freecb, void **freecb_args) { +render_500(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg, + unsigned int *retcode, const char **retmsg, const char **mimetype, + isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) { static char msg[] = "Internal server failure.\r\n"; - UNUSED(url); + UNUSED(httpd); UNUSED(urlinfo); - UNUSED(querystring); - UNUSED(headers); UNUSED(arg); *retcode = 500; @@ -821,28 +691,6 @@ render_500(const char *url, isc_httpdurl_t *urlinfo, const char *querystring, } #ifdef HAVE_ZLIB -/*%< - * Reallocates compbuffer to size; does nothing if compbuffer is already - * larger than size. - */ -static void -alloc_compspace(isc_httpd_t *httpd, unsigned int size) { - char *newspace = NULL; - isc_region_t r; - - if (size <= isc_buffer_length(&httpd->compbuffer)) { - return; - } - - isc_buffer_region(&httpd->compbuffer, &r); - newspace = isc_mem_get(httpd->mgr->mctx, size); - isc_buffer_reinit(&httpd->compbuffer, newspace, size); - - if (r.base != NULL) { - isc_mem_put(httpd->mgr->mctx, r.base, r.length); - } -} - /*%< * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it * if necessary. @@ -857,7 +705,7 @@ alloc_compspace(isc_httpd_t *httpd, unsigned int size) { * data would be larger than input data */ static isc_result_t -httpd_compress(isc_httpd_t *httpd) { +httpd_compress(isc_httpd_sendreq_t *req) { z_stream zstr; int ret, inputlen; @@ -865,16 +713,20 @@ httpd_compress(isc_httpd_t *httpd) { * We're setting output buffer size to input size so it fails if the * compressed data size would be bigger than the input size. */ - inputlen = isc_buffer_usedlength(&httpd->bodybuffer); - alloc_compspace(httpd, inputlen); - isc_buffer_clear(&httpd->compbuffer); + inputlen = isc_buffer_usedlength(&req->bodybuffer); + if (inputlen == 0) { + return (ISC_R_FAILURE); + } + + isc_buffer_allocate(req->mctx, &req->compbuffer, inputlen); + isc_buffer_clear(req->compbuffer); zstr = (z_stream){ .total_in = inputlen, .avail_out = inputlen, .avail_in = inputlen, - .next_in = isc_buffer_base(&httpd->bodybuffer), - .next_out = isc_buffer_base(&httpd->compbuffer), + .next_in = isc_buffer_base(&req->bodybuffer), + .next_out = isc_buffer_base(req->compbuffer), }; ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION); @@ -883,9 +735,10 @@ httpd_compress(isc_httpd_t *httpd) { } deflateEnd(&zstr); if (ret == Z_STREAM_END) { - isc_buffer_add(&httpd->compbuffer, zstr.total_out); + isc_buffer_add(req->compbuffer, zstr.total_out); return (ISC_R_SUCCESS); } else { + isc_buffer_free(&req->compbuffer); return (ISC_R_FAILURE); } } @@ -897,160 +750,205 @@ httpd_request(isc_nmhandle_t *handle, isc_result_t eresult, isc_result_t result; isc_httpd_t *httpd = NULL; isc_httpdmgr_t *mgr = (isc_httpdmgr_t *)arg; - isc_buffer_t *databuffer = NULL; isc_httpdurl_t *url = NULL; isc_time_t now; isc_region_t r; bool is_compressed = false; char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; - size_t buflen = 0; + size_t limit = 0; + size_t last_len; httpd = isc_nmhandle_getdata(handle); + REQUIRE(VALID_HTTPD(httpd)); + REQUIRE(httpd->handle == handle); if (eresult != ISC_R_SUCCESS) { goto cleanup_readhandle; } - REQUIRE(httpd->state == RECV); + REQUIRE(region != NULL); - result = process_request( - httpd, region == NULL ? &(isc_region_t){ NULL, 0 } : region, - &buflen); - if (result == ISC_R_NOTFOUND) { - if (buflen < HTTP_RECVLEN - 1) { - if (region != NULL) { - /* don't unref, keep reading */ - return; - } + last_len = httpd->recvlen; - /* - * We must have been called from httpd_senddone (as - * ISC_R_NOTFOUND is not returned from netmgr) and we - * need to resume reading. - */ - isc_nm_read(httpd->readhandle, httpd_request, - httpd->mgr); - return; - } - goto cleanup_readhandle; - } else if (result != ISC_R_SUCCESS) { + if (httpd->recvlen + region->length > sizeof(httpd->recvbuf)) { + goto cleanup_readhandle; + } + + /* Store the received data into the recvbuf */ + REQUIRE(httpd->recvlen + region->length < sizeof(httpd->recvbuf)); + memmove(httpd->recvbuf + httpd->recvlen, region->base, region->length); + httpd->recvlen += region->length; + +again: + result = process_request(httpd, last_len); + + if (result == ISC_R_NOMORE) { + limit = sizeof(httpd->recvbuf) - httpd->recvlen; + if (region->length > limit) { + /* + * We need more data, but we don't have space to store + * it + */ + goto cleanup_readhandle; + } + + memmove(httpd->recvbuf + httpd->recvlen, region->base, + region->length); + + /* Just wait for more data */ + return; + } + + if (result != ISC_R_SUCCESS) { goto cleanup_readhandle; } - isc_buffer_initnull(&httpd->bodybuffer); isc_time_now(&now); isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf)); + const char *path = "/"; + size_t path_len = 1; + + if (httpd->up.field_set & (1 << ISC_UF_PATH)) { + path = &httpd->path[httpd->up.field_data[ISC_UF_PATH].off]; + path_len = httpd->up.field_data[ISC_UF_PATH].len; + } + LOCK(&mgr->lock); url = ISC_LIST_HEAD(mgr->urls); while (url != NULL) { - if (strcmp(httpd->url, url->url) == 0) { + if (strncmp(path, url->url, path_len) == 0) { break; } url = ISC_LIST_NEXT(url, link); } UNLOCK(&mgr->lock); + isc_httpd_sendreq_t *req = isc__httpd_sendreq_new(httpd); + if (url == NULL) { - result = mgr->render_404( - httpd->url, NULL, httpd->querystring, NULL, NULL, - &httpd->retcode, &httpd->retmsg, &httpd->mimetype, - &httpd->bodybuffer, &httpd->freecb, &httpd->freecb_arg); + result = mgr->render_404(httpd, NULL, NULL, &req->retcode, + &req->retmsg, &req->mimetype, + &req->bodybuffer, &req->freecb, + &req->freecb_arg); } else { - result = url->action(httpd->url, url, httpd->querystring, - httpd->headers, url->action_arg, - &httpd->retcode, &httpd->retmsg, - &httpd->mimetype, &httpd->bodybuffer, - &httpd->freecb, &httpd->freecb_arg); + result = url->action(httpd, url, url->action_arg, &req->retcode, + &req->retmsg, &req->mimetype, + &req->bodybuffer, &req->freecb, + &req->freecb_arg); } if (result != ISC_R_SUCCESS) { - result = mgr->render_500( - httpd->url, url, httpd->querystring, NULL, NULL, - &httpd->retcode, &httpd->retmsg, &httpd->mimetype, - &httpd->bodybuffer, &httpd->freecb, &httpd->freecb_arg); + result = mgr->render_500(httpd, url, NULL, &req->retcode, + &req->retmsg, &req->mimetype, + &req->bodybuffer, &req->freecb, + &req->freecb_arg); RUNTIME_CHECK(result == ISC_R_SUCCESS); } #ifdef HAVE_ZLIB if ((httpd->flags & HTTPD_ACCEPT_DEFLATE) != 0) { - result = httpd_compress(httpd); + result = httpd_compress(req); if (result == ISC_R_SUCCESS) { is_compressed = true; } } #endif /* ifdef HAVE_ZLIB */ - httpd_response(httpd); + httpd_response(httpd, req); if ((httpd->flags & HTTPD_KEEPALIVE) != 0) { - httpd_addheader(httpd, "Connection", "Keep-Alive"); + httpd_addheader(req, "Connection", "Keep-Alive"); } - httpd_addheader(httpd, "Content-Type", httpd->mimetype); - httpd_addheader(httpd, "Date", datebuf); - httpd_addheader(httpd, "Expires", datebuf); + httpd_addheader(req, "Content-Type", req->mimetype); + httpd_addheader(req, "Date", datebuf); + httpd_addheader(req, "Expires", datebuf); if (url != NULL && url->isstatic) { char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; isc_time_formathttptimestamp(&url->loadtime, loadbuf, sizeof(loadbuf)); - httpd_addheader(httpd, "Last-Modified", loadbuf); - httpd_addheader(httpd, "Cache-Control: public", NULL); + httpd_addheader(req, "Last-Modified", loadbuf); + httpd_addheader(req, "Cache-Control: public", NULL); } else { - httpd_addheader(httpd, "Last-Modified", datebuf); - httpd_addheader(httpd, "Pragma: no-cache", NULL); - httpd_addheader(httpd, "Cache-Control: no-cache", NULL); + httpd_addheader(req, "Last-Modified", datebuf); + httpd_addheader(req, "Pragma: no-cache", NULL); + httpd_addheader(req, "Cache-Control: no-cache", NULL); } - httpd_addheader(httpd, "Server: libisc", NULL); + httpd_addheader(req, "Server: libisc", NULL); if (is_compressed) { - httpd_addheader(httpd, "Content-Encoding", "deflate"); - httpd_addheaderuint(httpd, "Content-Length", - isc_buffer_usedlength(&httpd->compbuffer)); + httpd_addheader(req, "Content-Encoding", "deflate"); + httpd_addheaderuint(req, "Content-Length", + isc_buffer_usedlength(req->compbuffer)); } else { - httpd_addheaderuint(httpd, "Content-Length", - isc_buffer_usedlength(&httpd->bodybuffer)); + httpd_addheaderuint(req, "Content-Length", + isc_buffer_usedlength(&req->bodybuffer)); } - httpd_endheaders(httpd); /* done */ + httpd_endheaders(req); /* done */ /* * Append either the compressed or the non-compressed response body to * the response headers and store the result in httpd->sendbuffer. */ - isc_buffer_dup(mgr->mctx, &httpd->sendbuffer, &httpd->headerbuffer); - isc_buffer_clear(&httpd->headerbuffer); - isc_buffer_setautorealloc(httpd->sendbuffer, true); - databuffer = (is_compressed ? &httpd->compbuffer : &httpd->bodybuffer); - isc_buffer_putmem(httpd->sendbuffer, isc_buffer_base(databuffer), - isc_buffer_usedlength(databuffer)); + if (is_compressed) { + isc_buffer_putmem(req->sendbuffer, + isc_buffer_base(req->compbuffer), + isc_buffer_usedlength(req->compbuffer)); + isc_buffer_free(&req->compbuffer); + } else { + isc_buffer_putmem(req->sendbuffer, + isc_buffer_base(&req->bodybuffer), + isc_buffer_usedlength(&req->bodybuffer)); + } + + /* Free the bodybuffer */ + if (req->freecb != NULL && isc_buffer_length(&req->bodybuffer) > 0) { + req->freecb(&req->bodybuffer, req->freecb_arg); + } /* Consume the request from the recv buffer. */ - if (httpd->consume != 0U) { - INSIST(httpd->consume <= httpd->recvlen); - if (httpd->consume < httpd->recvlen) { - memmove(httpd->recvbuf, httpd->recvbuf + httpd->consume, - httpd->recvlen - httpd->consume); - } - httpd->recvlen -= httpd->consume; - httpd->consume = 0; - httpd->recvbuf[httpd->recvlen] = 0; + INSIST(httpd->consume != 0); + INSIST(httpd->consume <= httpd->recvlen); + if (httpd->consume < httpd->recvlen) { + memmove(httpd->recvbuf, httpd->recvbuf + httpd->consume, + httpd->recvlen - httpd->consume); } + httpd->recvlen -= httpd->consume; + httpd->consume = 0; + last_len = 0; + + /* + * We don't need to attach to httpd here because it gets only cleaned + * when the last handle has been detached + */ + req->httpd = httpd; /* * Determine total response size. */ - isc_buffer_usedregion(httpd->sendbuffer, &r); + isc_buffer_usedregion(req->sendbuffer, &r); - isc_nm_read_stop(httpd->handle); - httpd->state = SEND; + isc_nmhandle_attach(httpd->handle, &req->handle); + isc_nm_send(httpd->handle, &r, httpd_senddone, req); + + if ((httpd->flags & HTTPD_CLOSE) != 0) { + goto cleanup_readhandle; + } + + /* + * The request was successfully completed; + */ + if (httpd->recvlen > 0) { + goto again; + } - isc_nmhandle_attach(httpd->handle, &httpd->sendhandle); - isc_nm_send(httpd->sendhandle, &r, httpd_senddone, httpd); return; cleanup_readhandle: + isc_nm_read_stop(httpd->readhandle); isc_nmhandle_detach(&httpd->readhandle); } @@ -1083,166 +981,65 @@ isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) { httpdmgr_detach(&httpdmgr); } -static isc_result_t -grow_headerspace(isc_httpd_t *httpd) { - char *newspace = NULL; - unsigned int newlen; - isc_region_t r; +static void +httpd_response(isc_httpd_t *httpd, isc_httpd_sendreq_t *req) { + isc_result_t result; - isc_buffer_region(&httpd->headerbuffer, &r); - newlen = r.length + HTTP_SENDGROW; - if (newlen > HTTP_SEND_MAXLEN) { - return (ISC_R_NOSPACE); - } + result = isc_buffer_printf(req->sendbuffer, "HTTP/1.%u %03u %s\r\n", + httpd->minor_version, req->retcode, + req->retmsg); - newspace = isc_mem_get(httpd->mgr->mctx, newlen); - - isc_buffer_reinit(&httpd->headerbuffer, newspace, newlen); - - isc_mem_put(httpd->mgr->mctx, r.base, r.length); - - return (ISC_R_SUCCESS); + RUNTIME_CHECK(result == ISC_R_SUCCESS); } -static isc_result_t -httpd_response(isc_httpd_t *httpd) { +static void +httpd_addheader(isc_httpd_sendreq_t *req, const char *name, const char *val) { isc_result_t result; - unsigned int needlen; - - REQUIRE(VALID_HTTPD(httpd)); - - needlen = strlen(httpd->protocol) + 1; /* protocol + space */ - needlen += 3 + 1; /* room for response code, always 3 bytes */ - needlen += strlen(httpd->retmsg) + 2; /* return msg + CRLF */ - - while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { - result = grow_headerspace(httpd); - if (result != ISC_R_SUCCESS) { - return (result); - } - } - - return (isc_buffer_printf(&httpd->headerbuffer, "%s %03u %s\r\n", - httpd->protocol, httpd->retcode, - httpd->retmsg)); -} - -static isc_result_t -httpd_addheader(isc_httpd_t *httpd, const char *name, const char *val) { - isc_result_t result; - unsigned int needlen; - - REQUIRE(VALID_HTTPD(httpd)); - - needlen = strlen(name); /* name itself */ - if (val != NULL) { - needlen += 2 + strlen(val); /* : and val */ - } - needlen += 2; /* CRLF */ - - while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { - result = grow_headerspace(httpd); - if (result != ISC_R_SUCCESS) { - return (result); - } - } if (val != NULL) { - return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n", - name, val)); + result = isc_buffer_printf(req->sendbuffer, "%s: %s\r\n", name, + val); } else { - return (isc_buffer_printf(&httpd->headerbuffer, "%s\r\n", - name)); + result = isc_buffer_printf(req->sendbuffer, "%s\r\n", name); } + + RUNTIME_CHECK(result == ISC_R_SUCCESS); } -static isc_result_t -httpd_endheaders(isc_httpd_t *httpd) { +static void +httpd_endheaders(isc_httpd_sendreq_t *req) { isc_result_t result; - REQUIRE(VALID_HTTPD(httpd)); + result = isc_buffer_printf(req->sendbuffer, "\r\n"); - while (isc_buffer_availablelength(&httpd->headerbuffer) < 2) { - result = grow_headerspace(httpd); - if (result != ISC_R_SUCCESS) { - return (result); - } - } - - return (isc_buffer_printf(&httpd->headerbuffer, "\r\n")); + RUNTIME_CHECK(result == ISC_R_SUCCESS); } -static isc_result_t -httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val) { +static void +httpd_addheaderuint(isc_httpd_sendreq_t *req, const char *name, int val) { isc_result_t result; - unsigned int needlen; - char buf[sizeof "18446744073709551616"]; - REQUIRE(VALID_HTTPD(httpd)); + result = isc_buffer_printf(req->sendbuffer, "%s: %d\r\n", name, val); - snprintf(buf, sizeof(buf), "%d", val); - - needlen = strlen(name); /* name itself */ - needlen += 2 + strlen(buf); /* : and val */ - needlen += 2; /* CRLF */ - - while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { - result = grow_headerspace(httpd); - if (result != ISC_R_SUCCESS) { - return (result); - } - } - - return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n", name, - buf)); + RUNTIME_CHECK(result == ISC_R_SUCCESS); } static void httpd_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg) { - isc_httpd_t *httpd = (isc_httpd_t *)arg; + isc_httpd_sendreq_t *req = (isc_httpd_sendreq_t *)arg; + isc_httpd_t *httpd = req->httpd; REQUIRE(VALID_HTTPD(httpd)); - REQUIRE(httpd->handle == handle); - /* Clean up buffers */ - isc_buffer_free(&httpd->sendbuffer); - if (httpd->freecb != NULL && isc_buffer_length(&httpd->bodybuffer) > 0) - { - httpd->freecb(&httpd->bodybuffer, httpd->freecb_arg); + if (result != ISC_R_SUCCESS && httpd->readhandle != NULL) { + isc_nm_read_stop(httpd->readhandle); + isc_nmhandle_close(httpd->readhandle); + isc_nmhandle_detach(&httpd->readhandle); } - isc_nmhandle_detach(&httpd->sendhandle); + isc_nmhandle_detach(&handle); - if (result != ISC_R_SUCCESS) { - goto cleanup_readhandle; - } - - if ((httpd->flags & HTTPD_CLOSE) != 0) { - goto cleanup_readhandle; - } - - REQUIRE(httpd->state == SEND); - - httpd->state = RECV; - httpd->sendhandle = NULL; - - if (httpd->recvlen != 0) { - /* - * Outstanding requests still exist, start processing - * them. - */ - httpd_request(httpd->handle, ISC_R_SUCCESS, NULL, httpd->mgr); - } else if (!httpd->truncated) { - isc_nm_read(httpd->readhandle, httpd_request, httpd->mgr); - } else { - /* Truncated request, don't resume */ - goto cleanup_readhandle; - } - - return; - -cleanup_readhandle: - isc_nmhandle_detach(&httpd->readhandle); + isc__httpd_sendreq_free(req); } isc_result_t @@ -1283,3 +1080,18 @@ isc_httpd_setfinishhook(void (*fn)(void)) { UNUSED(fn); #endif /* ENABLE_AFL */ } + +bool +isc_httpdurl_isstatic(const isc_httpdurl_t *url) { + return (url->isstatic); +} + +const isc_time_t * +isc_httpdurl_loadtime(const isc_httpdurl_t *url) { + return (&url->loadtime); +} + +const isc_time_t * +isc_httpd_if_modified_since(const isc_httpd_t *httpd) { + return ((const isc_time_t *)&httpd->if_modified_since); +} diff --git a/lib/isc/include/isc/httpd.h b/lib/isc/include/isc/httpd.h index eb803230a6..1f69a4c99d 100644 --- a/lib/isc/include/isc/httpd.h +++ b/lib/isc/include/isc/httpd.h @@ -23,28 +23,20 @@ #include #include #include - -/*% - * HTTP urls. These are the URLs we manage, and the function to call to - * provide the data for it. We pass in the base url (so the same function - * can handle multiple requests), and a structure to fill in to return a - * result to the client. We also pass in a pointer to be filled in for - * the data cleanup function. - */ -struct isc_httpdurl { - char *url; - isc_httpdaction_t *action; - void *action_arg; - bool isstatic; - isc_time_t loadtime; - ISC_LINK(isc_httpdurl_t) link; -}; +#include #define HTTPD_EVENTCLASS ISC_EVENTCLASS(4300) #define HTTPD_SHUTDOWN (HTTPD_EVENTCLASS + 0x0001) #define ISC_HTTPDMGR_SHUTTINGDOWN 0x00000001 +typedef isc_result_t(isc_httpdaction_t)( + const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg, + unsigned int *retcode, const char **retmsg, const char **mimetype, + isc_buffer_t *body, isc_httpdfree_t **freecb, void **freecb_args); + +typedef bool(isc_httpdclientok_t)(const isc_sockaddr_t *, void *); + isc_result_t isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr, isc_httpdclientok_t *client_ok, @@ -60,3 +52,12 @@ isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic, void isc_httpd_setfinishhook(void (*fn)(void)); + +bool +isc_httpdurl_isstatic(const isc_httpdurl_t *url); + +const isc_time_t * +isc_httpdurl_loadtime(const isc_httpdurl_t *url); + +const isc_time_t * +isc_httpd_if_modified_since(const isc_httpd_t *httpd); diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h index 35aa615e3f..ecbbf48976 100644 --- a/lib/isc/include/isc/types.h +++ b/lib/isc/include/isc/types.h @@ -96,14 +96,6 @@ typedef struct isc_nm_http_endpoints isc_nm_http_endpoints_t; typedef void (*isc_taskaction_t)(isc_task_t *, isc_event_t *); -/* The following cannot be listed alphabetically due to forward reference */ -typedef isc_result_t(isc_httpdaction_t)( - const char *url, isc_httpdurl_t *urlinfo, const char *querystring, - const char *headers, void *arg, unsigned int *retcode, - const char **retmsg, const char **mimetype, isc_buffer_t *body, - isc_httpdfree_t **freecb, void **freecb_args); -typedef bool(isc_httpdclientok_t)(const isc_sockaddr_t *, void *); - /*% Resource */ typedef enum { isc_resource_coresize = 1, diff --git a/lib/isc/include/isc/url.h b/lib/isc/include/isc/url.h index 14361d902b..d3b935ba97 100644 --- a/lib/isc/include/isc/url.h +++ b/lib/isc/include/isc/url.h @@ -36,6 +36,7 @@ #pragma once #include +#include #include #include