diff --git a/CHANGES b/CHANGES index 1e19cbab24..a8b6a2c6bd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +5994. [func] Refactor the isc_httpd implementation used in the + statistics channel. [GL !6879] + 5993. [cleanup] Store dns_name_t attributes as boolean members of the structure. Remove DNS_NAMEATTR_* macros. Fix latent attribute handling bug in RBT. [GL !6902] 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/bin/tests/system/statschannel/send64k.pl b/bin/tests/system/statschannel/send64k.pl deleted file mode 100644 index 4174b5a3a0..0000000000 --- a/bin/tests/system/statschannel/send64k.pl +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/perl -# -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# SPDX-License-Identifier: MPL-2.0 -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -# -# Send a file to a given address and port using TCP. Used for -# configuring the test server in ans.pl. -# - -use IO::File; -use IO::Socket; - -@ARGV == 2 or die "usage: send.pl host port\n"; - -my $host = shift @ARGV; -my $port = shift @ARGV; - -my $sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, - Proto => "tcp",) or die "$!"; -#send the file -while ($n = read(STDIN, $buf, 64000)) { - $sock->syswrite($buf, $n); -} - -#get the response with with a 15 second timeout -my $rin; -my $rout; -my $n; -do { - $rin = ''; - vec($rin, fileno($sock), 1) = 1; - $n = select($rout = $rin, undef, undef, 15); - $n = $sock->sysread($buf, 64000) if ($n > 0); - print STDOUT $buf if ($n > 0); -} while ($n > 0); - -$sock->close; diff --git a/bin/tests/system/statschannel/tests.sh b/bin/tests/system/statschannel/tests.sh index 1d0cff0589..ec899e1b3d 100644 --- a/bin/tests/system/statschannel/tests.sh +++ b/bin/tests/system/statschannel/tests.sh @@ -388,8 +388,8 @@ Host: 10.53.0.3:${EXTRAPORT1} Connection: close EOF - lines=$(grep "^HTTP/1.1" nc.out$n | wc -l) - test $lines = 2 || ret=1 + lines=$(grep -c "^HTTP/1.1" nc.out$n) + test "$lines" = 2 || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) @@ -415,8 +415,8 @@ Connection: close {} EOF - lines=$(grep "^HTTP/1.1" nc.out$n | wc -l) - test $lines = 2 || ret=1 + lines=$(grep -c "^HTTP/1.1" nc.out$n) + test "$lines" = 2 || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) @@ -424,29 +424,54 @@ else echo_i "skipping test as nc not found" fi +echo_i "Check HTTP with more than 10 headers ($n)" +ret=0 +i=0 +# build input stream. +printf 'GET /xml/v3/status HTTP/1.1\r\nHost: 10.53.0.3\r\n\r\n' > send.in$n +printf 'GET /xml/v3/status HTTP/1.1\r\nHost: 10.53.0.3\r\n' >> send.in$n + +while test $i -lt 11 +do +printf 'X-Bloat: VGhlIG1vc3QgY29tbW9uIHJlYXNvbiBmb3IgYmxvYXRpbmcgaXMgaGF2aW5nIGEgbG90IG9mIGdhcyBpbiB5b3VyIGd1dC4gCg==\r\n' >> send.in$n +i=$((i+1)) +done +printf '\r\n' >> send.in$n + +# send the requests then wait for named to close the socket. +time1=$($PERL -e 'print time(), "\n";') +${NC} 10.53.0.3 ${EXTRAPORT1} < send.in$n > send.out$n +time2=$($PERL -e 'print time(), "\n";') +test $((time2 - time1)) -lt 5 || ret=1 +# we expect 1 request to be processed. +lines=$(grep -c "^HTTP/1.1" send.out$n) +test $lines = 1 || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +n=$((n + 1)) + echo_i "Check HTTP/1.1 pipelined with truncated stream ($n)" ret=0 i=0 # build input stream. -cp /dev/null send.in$n -while test $i -lt 500 +printf 'GET /xml/v3/status HTTP/1.1\r\nHost: 10.53.0.3\r\n\r\n' > send.in$n +printf 'GET /xml/v3/status HTTP/1.1\r\nHost: 10.53.0.3\r\nX-Bloat:' >> send.in$n +while test $i -lt 5000 do -cat >> send.in$n << EOF -GET /xml/v3/status HTTP/1.1 -Host: 10.53.0.3 - -EOF +printf '%s' "VGhlIG1vc3QgY29tbW9uIHJlYXNvbiBmb3IgYmxvYXRpbmcgaXMgaGF2aW5nIGEgbG90IG9mIGdhcyBpbiB5b3VyIGd1dC4gCg==" >> send.in$n i=$((i+1)) done +printf '\r\n' >> send.in$n +printf '\r\n' >> send.in$n # send the requests then wait for named to close the socket. time1=$($PERL -e 'print time(), "\n";') -${PERL} send64k.pl 10.53.0.3 ${EXTRAPORT1} < send.in$n > send.out$n +${NC} 10.53.0.3 ${EXTRAPORT1} < send.in$n > send.out$n time2=$($PERL -e 'print time(), "\n";') test $((time2 - time1)) -lt 5 || ret=1 -# we expect 91 of the 500 requests to be processed. -lines=$(grep "^HTTP/1.1" send.out$n | wc -l) -test $lines = 91 || ret=1 +# we expect 1 request to be processed. +lines=$(grep -c "^HTTP/1.1" send.out$n) +test $lines = 1 || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am index 8e1b71f98e..e8ad720d03 100644 --- a/lib/isc/Makefile.am +++ b/lib/isc/Makefile.am @@ -177,6 +177,8 @@ libisc_la_SOURCES = \ os.c \ os_p.h \ parseint.c \ + picohttpparser.c \ + picohttpparser.h \ portset.c \ quota.c \ radix.c \ 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 diff --git a/lib/isc/picohttpparser.c b/lib/isc/picohttpparser.c new file mode 100644 index 0000000000..95c0d6f511 --- /dev/null +++ b/lib/isc/picohttpparser.c @@ -0,0 +1,722 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * SPDX-License-Identifier: MIT + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#ifdef __SSE4_2__ +#ifdef _MSC_VER +#include +#else +#include +#endif +#endif +#include "picohttpparser.h" + +#if __GNUC__ >= 3 +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#ifdef _MSC_VER +#define ALIGNED(n) _declspec(align(n)) +#else +#define ALIGNED(n) __attribute__((aligned(n))) +#endif + +#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) + +#define CHECK_EOF() \ + if (buf == buf_end) { \ + *ret = -2; \ + return NULL; \ + } + +#define EXPECT_CHAR_NO_CHECK(ch) \ + if (*buf++ != ch) { \ + *ret = -1; \ + return NULL; \ + } + +#define EXPECT_CHAR(ch) \ + CHECK_EOF(); \ + EXPECT_CHAR_NO_CHECK(ch); + +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char *tok_start = buf; \ + static const char ALIGNED(16) \ + ranges2[16] = "\000\040\177\177"; \ + int found2; \ + buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || \ + *buf == '\177') { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) + +static const char *token_char_map = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +static const char * +findchar_fast(const char *buf, const char *buf_end, const char *ranges, + size_t ranges_size, int *found) { + *found = 0; +#if __SSE4_2__ + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((const __m128i *)buf); + int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, + _SIDD_LEAST_SIGNIFICANT | + _SIDD_CMP_RANGES | + _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } +#else + /* suppress unused parameter warning */ + (void)buf_end; + (void)ranges; + (void)ranges_size; +#endif + return buf; +} + +static const char * +get_token_to_eol(const char *buf, const char *buf_end, const char **token, + size_t *token_len, int *ret) { + const char *token_start = buf; + +#ifdef __SSE4_2__ + static const char ALIGNED(16) + ranges1[16] = "\0\010" /* allow HT */ + "\012\037" /* allow SP and up to but not including + DEL */ + "\177\177"; /* allow chars w. MSB set */ + int found; + buf = findchar_fast(buf, buf_end, ranges1, 6, &found); + if (found) + goto FOUND_CTL; +#else + /* find non-printable char within the next 8 bytes, this is the hottest + * code; manually inlined */ + while (likely(buf_end - buf >= 8)) { +#define DOIT() \ + do { \ + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ + goto NonPrintable; \ + ++buf; \ + } while (0) + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); +#undef DOIT + continue; + NonPrintable: + if ((likely((unsigned char)*buf < '\040') && + likely(*buf != '\011')) || + unlikely(*buf == '\177')) + { + goto FOUND_CTL; + } + ++buf; + } +#endif + for (;; ++buf) { + CHECK_EOF(); + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { + if ((likely((unsigned char)*buf < '\040') && + likely(*buf != '\011')) || + unlikely(*buf == '\177')) + { + goto FOUND_CTL; + } + } + } +FOUND_CTL: + if (likely(*buf == '\015')) { + ++buf; + EXPECT_CHAR('\012'); + *token_len = buf - 2 - token_start; + } else if (*buf == '\012') { + *token_len = buf - token_start; + ++buf; + } else { + *ret = -1; + return NULL; + } + *token = token_start; + + return buf; +} + +static const char * +is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) { + int ret_cnt = 0; + buf = last_len < 3 ? buf : buf + last_len - 3; + + while (1) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + CHECK_EOF(); + EXPECT_CHAR('\012'); + ++ret_cnt; + } else if (*buf == '\012') { + ++buf; + ++ret_cnt; + } else { + ++buf; + ret_cnt = 0; + } + if (ret_cnt == 2) { + return buf; + } + } + + *ret = -2; + return NULL; +} + +#define PARSE_INT(valp_, mul_) \ + if (*buf < '0' || '9' < *buf) { \ + buf++; \ + *ret = -1; \ + return NULL; \ + } \ + *(valp_) = (mul_) * (*buf++ - '0'); + +#define PARSE_INT_3(valp_) \ + do { \ + int res_ = 0; \ + PARSE_INT(&res_, 100) \ + *valp_ = res_; \ + PARSE_INT(&res_, 10) \ + *valp_ += res_; \ + PARSE_INT(&res_, 1) \ + *valp_ += res_; \ + } while (0) + +/* returned pointer is always within [buf, buf_end), or null */ +static const char * +parse_token(const char *buf, const char *buf_end, const char **token, + size_t *token_len, char next_char, int *ret) { + /* We use pcmpestri to detect non-token characters. This instruction can + * take no more than eight character ranges (8*2*8=128 bits that is the + * size of a SSE register). Due to this restriction, characters `|` and + * `~` are handled in the slow loop. */ + static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up + to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\xff"; /* 0x7b-0xff */ + const char *buf_start = buf; + int found; + buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == next_char) { + break; + } else if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; + } + ++buf; + CHECK_EOF(); + } + *token = buf_start; + *token_len = buf - buf_start; + return buf; +} + +/* returned pointer is always within [buf, buf_end), or null */ +static const char * +parse_http_version(const char *buf, const char *buf_end, int *minor_version, + int *ret) { + /* we want at least [HTTP/1.] to try to parse */ + if (buf_end - buf < 9) { + *ret = -2; + return NULL; + } + EXPECT_CHAR_NO_CHECK('H'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('P'); + EXPECT_CHAR_NO_CHECK('/'); + EXPECT_CHAR_NO_CHECK('1'); + EXPECT_CHAR_NO_CHECK('.'); + PARSE_INT(minor_version, 1); + return buf; +} + +static const char * +parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, + size_t *num_headers, size_t max_headers, int *ret) { + for (;; ++*num_headers) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + break; + } else if (*buf == '\012') { + ++buf; + break; + } + if (*num_headers == max_headers) { + *ret = -1; + return NULL; + } + if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { + /* parsing name, but do not discard SP before colon, see + * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html + */ + if ((buf = parse_token(buf, buf_end, + &headers[*num_headers].name, + &headers[*num_headers].name_len, + ':', ret)) == NULL) + { + return NULL; + } + if (headers[*num_headers].name_len == 0) { + *ret = -1; + return NULL; + } + ++buf; + for (;; ++buf) { + CHECK_EOF(); + if (!(*buf == ' ' || *buf == '\t')) { + break; + } + } + } else { + headers[*num_headers].name = NULL; + headers[*num_headers].name_len = 0; + } + const char *value; + size_t value_len; + if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, + ret)) == NULL) { + return NULL; + } + /* remove trailing SPs and HTABs */ + const char *value_end = value + value_len; + for (; value_end != value; --value_end) { + const char c = *(value_end - 1); + if (!(c == ' ' || c == '\t')) { + break; + } + } + headers[*num_headers].value = value; + headers[*num_headers].value_len = value_end - value; + } + return buf; +} + +static const char * +parse_request(const char *buf, const char *buf_end, const char **method, + size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, + size_t *num_headers, size_t max_headers, int *ret) { + /* skip first empty line (some clients add CRLF after POST content) */ + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } + + /* parse request line */ + if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == + NULL) { + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + ADVANCE_TOKEN(*path, *path_len); + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + if (*method_len == 0 || *path_len == 0) { + *ret = -1; + return NULL; + } + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == + NULL) { + return NULL; + } + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } else { + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, + ret); +} + +int +phr_parse_request(const char *buf_start, size_t len, const char **method, + size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, + size_t *num_headers, size_t last_len) { + const char *buf = buf_start, *buf_end = buf_start + len; + size_t max_headers = *num_headers; + int r = -1; + + *method = NULL; + *method_len = 0; + *path = NULL; + *path_len = 0; + *minor_version = -1; + *num_headers = 0; + + /* if last_len != 0, check if the request is complete (a fast + countermeasure againt slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_request(buf, buf_end, method, method_len, path, + path_len, minor_version, headers, num_headers, + max_headers, &r)) == NULL) + { + return r; + } + + return (int)(buf - buf_start); +} + +static const char * +parse_response(const char *buf, const char *buf_end, int *minor_version, + int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) { + /* parse "HTTP/1.x" */ + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == + NULL) { + return NULL; + } + /* skip space */ + if (*buf != ' ') { + *ret = -1; + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ + if (buf_end - buf < 4) { + *ret = -2; + return NULL; + } + PARSE_INT_3(status); + + /* get message including preceding space */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + return NULL; + } + if (*msg_len == 0) { + /* ok */ + } else if (**msg == ' ') { + /* Remove preceding space. Successful return from + * `get_token_to_eol` guarantees that we would hit something + * other than SP before running past the end of the given + * buffer. */ + do { + ++*msg; + --*msg_len; + } while (**msg == ' '); + } else { + /* garbage found after status code */ + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, + ret); +} + +int +phr_parse_response(const char *buf_start, size_t len, int *minor_version, + int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, + size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *minor_version = -1; + *status = 0; + *msg = NULL; + *msg_len = 0; + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast + countermeasure against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_response(buf, buf_end, minor_version, status, msg, + msg_len, headers, num_headers, max_headers, + &r)) == NULL) + { + return r; + } + + return (int)(buf - buf_start); +} + +int +phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, + size_t *num_headers, size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast + countermeasure against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_headers(buf, buf_end, headers, num_headers, + max_headers, &r)) == NULL) + { + return r; + } + + return (int)(buf - buf_start); +} + +enum { + CHUNKED_IN_CHUNK_SIZE, + CHUNKED_IN_CHUNK_EXT, + CHUNKED_IN_CHUNK_DATA, + CHUNKED_IN_CHUNK_CRLF, + CHUNKED_IN_TRAILERS_LINE_HEAD, + CHUNKED_IN_TRAILERS_LINE_MIDDLE +}; + +static int +decode_hex(int ch) { + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else if ('A' <= ch && ch <= 'F') { + return ch - 'A' + 0xa; + } else if ('a' <= ch && ch <= 'f') { + return ch - 'a' + 0xa; + } else { + return -1; + } +} + +ssize_t +phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, + size_t *_bufsz) { + size_t dst = 0, src = 0, bufsz = *_bufsz; + ssize_t ret = -2; /* incomplete */ + + while (1) { + switch (decoder->_state) { + case CHUNKED_IN_CHUNK_SIZE: + for (;; ++src) { + int v; + if (src == bufsz) + goto Exit; + if ((v = decode_hex(buf[src])) == -1) { + if (decoder->_hex_count == 0) { + ret = -1; + goto Exit; + } + break; + } + if (decoder->_hex_count == sizeof(size_t) * 2) { + ret = -1; + goto Exit; + } + decoder->bytes_left_in_chunk = + decoder->bytes_left_in_chunk * 16 + v; + ++decoder->_hex_count; + } + decoder->_hex_count = 0; + decoder->_state = CHUNKED_IN_CHUNK_EXT; + /* fallthru */ + case CHUNKED_IN_CHUNK_EXT: + /* RFC 7230 A.2 "Line folding in chunk extensions is + * disallowed" */ + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + if (decoder->bytes_left_in_chunk == 0) { + if (decoder->consume_trailer) { + decoder->_state = + CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + } else { + goto Complete; + } + } + decoder->_state = CHUNKED_IN_CHUNK_DATA; + /* fallthru */ + case CHUNKED_IN_CHUNK_DATA: { + size_t avail = bufsz - src; + if (avail < decoder->bytes_left_in_chunk) { + if (dst != src) + memmove(buf + dst, buf + src, avail); + src += avail; + dst += avail; + decoder->bytes_left_in_chunk -= avail; + goto Exit; + } + if (dst != src) + memmove(buf + dst, buf + src, + decoder->bytes_left_in_chunk); + src += decoder->bytes_left_in_chunk; + dst += decoder->bytes_left_in_chunk; + decoder->bytes_left_in_chunk = 0; + decoder->_state = CHUNKED_IN_CHUNK_CRLF; + } + /* fallthru */ + case CHUNKED_IN_CHUNK_CRLF: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src] != '\012') { + ret = -1; + goto Exit; + } + ++src; + decoder->_state = CHUNKED_IN_CHUNK_SIZE; + break; + case CHUNKED_IN_TRAILERS_LINE_HEAD: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src++] == '\012') + goto Complete; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; + /* fallthru */ + case CHUNKED_IN_TRAILERS_LINE_MIDDLE: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + default: + assert(!"decoder is corrupt"); + } + } + +Complete: + ret = bufsz - src; +Exit: + if (dst != src) + memmove(buf + dst, buf + src, bufsz - src); + *_bufsz = dst; + return ret; +} + +int +phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) { + return decoder->_state == CHUNKED_IN_CHUNK_DATA; +} + +#undef CHECK_EOF +#undef EXPECT_CHAR +#undef ADVANCE_TOKEN diff --git a/lib/isc/picohttpparser.h b/lib/isc/picohttpparser.h new file mode 100644 index 0000000000..0657cb29c4 --- /dev/null +++ b/lib/isc/picohttpparser.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * SPDX-License-Identifier: MIT + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef picohttpparser_h +#define picohttpparser_h + +#include + +#ifdef _MSC_VER +#define ssize_t intptr_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* contains name and value of a header (name == NULL if is a continuing line + * of a multiline header */ +struct phr_header { + const char *name; + size_t name_len; + const char *value; + size_t value_len; +}; + +/* returns number of bytes consumed if successful, -2 if request is partial, + * -1 if failed */ +int +phr_parse_request(const char *buf, size_t len, const char **method, + size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, + size_t *num_headers, size_t last_len); + +/* ditto */ +int +phr_parse_response(const char *_buf, size_t len, int *minor_version, + int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, + size_t last_len); + +/* ditto */ +int +phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, + size_t *num_headers, size_t last_len); + +/* should be zero-filled before start */ +struct phr_chunked_decoder { + size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ + char consume_trailer; /* if trailing headers should be consumed */ + char _hex_count; + char _state; +}; + +/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- + * encoding headers. When the function returns without an error, bufsz is + * updated to the length of the decoded data available. Applications should + * repeatedly call the function while it returns -2 (incomplete) every time + * supplying newly arrived data. If the end of the chunked-encoded data is + * found, the function returns a non-negative number indicating the number of + * octets left undecoded, that starts from the offset returned by `*bufsz`. + * Returns -1 on error. + */ +ssize_t +phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, + size_t *bufsz); + +/* returns if the chunked decoder is in middle of chunked data */ +int +phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); + +#ifdef __cplusplus +} +#endif + +#endif