mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
handling of long headers (#1222)
This commit is contained in:
@@ -14,9 +14,7 @@ feature.
|
||||
For http/1.1-compliance:
|
||||
========================
|
||||
connection.c:
|
||||
- send proper error code back if headers are too long
|
||||
(currently, we just close the connection) (#1222, ARCH, TEST)
|
||||
- support chunked requests from clients (#1260, TEST, TEST)
|
||||
- support chunked requests from clients (#1260, ARCH, TEST)
|
||||
- send proper error code back if client forgot the "Host" header (#1264, TRIV)
|
||||
- automatically add MHD_HTTP_HEADER_DATE if client "forgot" to add one (#1261, TRIV)
|
||||
- automatically drop body from responses to "HEAD" requests (#1262, TRIV)
|
||||
|
||||
@@ -35,7 +35,8 @@ check_PROGRAMS = \
|
||||
daemontest_put \
|
||||
daemontest_get11 \
|
||||
daemontest_post11 \
|
||||
daemontest_put11
|
||||
daemontest_put11 \
|
||||
daemontest_long_header
|
||||
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
@@ -74,11 +75,16 @@ daemontest_post11_LDADD = \
|
||||
$(top_builddir)/src/daemon/libmicrohttpd.la \
|
||||
@LIBCURL@
|
||||
|
||||
|
||||
daemontest_put11_SOURCES = \
|
||||
daemontest_put.c
|
||||
daemontest_put11_LDADD = \
|
||||
$(top_builddir)/src/daemon/libmicrohttpd.la \
|
||||
@LIBCURL@
|
||||
|
||||
daemontest_long_header_SOURCES = \
|
||||
daemontest_long_header.c
|
||||
daemontest_long_header_LDADD = \
|
||||
$(top_builddir)/src/daemon/libmicrohttpd.la \
|
||||
@LIBCURL@
|
||||
|
||||
endif
|
||||
+50
-19
@@ -40,6 +40,12 @@
|
||||
*/
|
||||
#define HTTP_100_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n"
|
||||
|
||||
/**
|
||||
* Response used when the request (http header) is too big to
|
||||
* be processed.
|
||||
*/
|
||||
#define REQUEST_TOO_BIG ""
|
||||
|
||||
/**
|
||||
* Get all of the headers from the request.
|
||||
*
|
||||
@@ -146,16 +152,14 @@ MHD_connection_get_fdset(struct MHD_Connection * connection,
|
||||
fd = connection->socket_fd;
|
||||
if (fd == -1)
|
||||
return MHD_YES;
|
||||
if ( (connection->read_close == 0) &&
|
||||
if ( (connection->read_close == MHD_NO) &&
|
||||
( (connection->headersReceived == 0) ||
|
||||
(connection->readLoc < connection->read_buffer_size) ) ) {
|
||||
FD_SET(fd, read_fd_set);
|
||||
if (fd > *max_fd)
|
||||
*max_fd = fd;
|
||||
} else {
|
||||
|
||||
|
||||
if ( (connection->read_close == 0) &&
|
||||
if ( (connection->read_close == MHD_NO) &&
|
||||
( (connection->headersReceived == 1) &&
|
||||
(connection->post_processed == MHD_NO) &&
|
||||
(connection->readLoc == connection->read_buffer_size) ) ) {
|
||||
@@ -188,17 +192,32 @@ MHD_connection_get_fdset(struct MHD_Connection * connection,
|
||||
|
||||
/**
|
||||
* We ran out of memory processing the
|
||||
* header. Handle it properly.
|
||||
* header. Handle it properly by stopping to read data
|
||||
* and sending a HTTP 413 or HTTP 414 response.
|
||||
*
|
||||
* @param status_code the response code to send (413 or 414)
|
||||
*/
|
||||
static void
|
||||
MHD_excessive_header_handler(struct MHD_Connection * connection) {
|
||||
MHD_excessive_data_handler(struct MHD_Connection * connection,
|
||||
unsigned int status_code) {
|
||||
struct MHD_Response * response;
|
||||
|
||||
/* die, header far too long to be reasonable;
|
||||
FIXME: send proper response to client
|
||||
(stop reading, queue proper response) */
|
||||
connection->read_close = MHD_YES;
|
||||
connection->headersReceived = MHD_YES;
|
||||
connection->bodyReceived = MHD_YES;
|
||||
MHD_DLOG(connection->daemon,
|
||||
"Received excessively long header line, closing connection.\n");
|
||||
CLOSE(connection->socket_fd);
|
||||
connection->socket_fd = -1;
|
||||
"Received excessively long header, closing connection.\n");
|
||||
response = MHD_create_response_from_data(strlen(REQUEST_TOO_BIG),
|
||||
REQUEST_TOO_BIG,
|
||||
MHD_NO,
|
||||
MHD_NO);
|
||||
MHD_queue_response(connection,
|
||||
status_code,
|
||||
response);
|
||||
MHD_destroy_response(response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,7 +249,10 @@ MHD_get_next_header_line(struct MHD_Connection * connection) {
|
||||
connection->read_buffer_size,
|
||||
connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE);
|
||||
if (rbuf == NULL) {
|
||||
MHD_excessive_header_handler(connection);
|
||||
MHD_excessive_data_handler(connection,
|
||||
(connection->url != NULL)
|
||||
? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE
|
||||
: MHD_HTTP_REQUEST_URI_TOO_LONG);
|
||||
} else {
|
||||
connection->read_buffer_size = connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE;
|
||||
connection->read_buffer = rbuf;
|
||||
@@ -265,7 +287,8 @@ MHD_connection_add_header(struct MHD_Connection * connection,
|
||||
if (hdr == NULL) {
|
||||
MHD_DLOG(connection->daemon,
|
||||
"Not enough memory to allocate header record!\n");
|
||||
MHD_excessive_header_handler(connection);
|
||||
MHD_excessive_data_handler(connection,
|
||||
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
||||
return MHD_NO;
|
||||
}
|
||||
hdr->next = connection->headers_received;
|
||||
@@ -360,7 +383,8 @@ MHD_parse_cookie_header(struct MHD_Connection * connection) {
|
||||
if (cpy == NULL) {
|
||||
MHD_DLOG(connection->daemon,
|
||||
"Not enough memory to parse cookies!\n");
|
||||
MHD_excessive_header_handler(connection);
|
||||
MHD_excessive_data_handler(connection,
|
||||
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
||||
return MHD_NO;
|
||||
}
|
||||
memcpy(cpy,
|
||||
@@ -483,7 +507,8 @@ MHD_parse_connection_headers(struct MHD_Connection * connection) {
|
||||
strlen(last)+1,
|
||||
strlen(line) + strlen(last) + 1);
|
||||
if (last == NULL) {
|
||||
MHD_excessive_header_handler(connection);
|
||||
MHD_excessive_data_handler(connection,
|
||||
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
||||
break;
|
||||
}
|
||||
tmp = line;
|
||||
@@ -620,8 +645,10 @@ MHD_test_post_data(struct MHD_Connection * connection) {
|
||||
const char * encoding;
|
||||
void * buf;
|
||||
|
||||
if (0 != strcasecmp(connection->method,
|
||||
MHD_HTTP_METHOD_POST))
|
||||
if ( (connection->method == NULL) ||
|
||||
(connection->response != NULL) ||
|
||||
(0 != strcasecmp(connection->method,
|
||||
MHD_HTTP_METHOD_POST)) )
|
||||
return MHD_NO;
|
||||
encoding = MHD_lookup_connection_value(connection,
|
||||
MHD_HEADER_KIND,
|
||||
@@ -711,6 +738,8 @@ MHD_call_connection_handler(struct MHD_Connection * connection) {
|
||||
struct MHD_Access_Handler * ah;
|
||||
unsigned int processed;
|
||||
|
||||
if (connection->response != NULL)
|
||||
return; /* already queued a response */
|
||||
if (connection->headersReceived == 0)
|
||||
abort(); /* bad timing... */
|
||||
ah = MHD_find_access_handler(connection);
|
||||
@@ -778,7 +807,8 @@ MHD_connection_handle_read(struct MHD_Connection * connection) {
|
||||
if (tmp == NULL) {
|
||||
MHD_DLOG(connection->daemon,
|
||||
"Not enough memory for reading headers!\n");
|
||||
MHD_excessive_header_handler(connection);
|
||||
MHD_excessive_data_handler(connection,
|
||||
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
||||
return MHD_NO;
|
||||
}
|
||||
connection->read_buffer = tmp;
|
||||
@@ -824,8 +854,9 @@ MHD_connection_handle_read(struct MHD_Connection * connection) {
|
||||
(connection->uploadSize == connection->readLoc) )
|
||||
if (MHD_NO == MHD_parse_post_data(connection))
|
||||
connection->post_processed = MHD_NO;
|
||||
if ( (connection->post_processed == MHD_NO) ||
|
||||
(connection->read_buffer_size == connection->readLoc) )
|
||||
if ( ( (connection->post_processed == MHD_NO) ||
|
||||
(connection->read_buffer_size == connection->readLoc) ) &&
|
||||
(connection->method != NULL) )
|
||||
MHD_call_connection_handler(connection);
|
||||
}
|
||||
return MHD_YES;
|
||||
@@ -1065,7 +1096,7 @@ MHD_connection_handle_write(struct MHD_Connection * connection) {
|
||||
connection->messagePos = 0;
|
||||
connection->method = NULL;
|
||||
connection->url = NULL;
|
||||
if ( (connection->read_close != 0) ||
|
||||
if ( (connection->read_close == MHD_YES) ||
|
||||
(0 != strcasecmp(MHD_HTTP_VERSION_1_1,
|
||||
connection->version)) ) {
|
||||
/* closed for reading => close for good! */
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
This file is part of libmicrohttpd
|
||||
(C) 2007 Christian Grothoff
|
||||
|
||||
libmicrohttpd is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation; either version 2, or (at your
|
||||
option) any later version.
|
||||
|
||||
libmicrohttpd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with libmicrohttpd; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file daemontest_long_header.c
|
||||
* @brief Testcase for libmicrohttpd handling of very long headers
|
||||
* @author Christian Grothoff
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <curl/curl.h>
|
||||
#include <microhttpd.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* We will set the memory available per connection to
|
||||
* half of this value, so the actual value does not have
|
||||
* to be big at all...
|
||||
*/
|
||||
#define VERY_LONG (1024*10)
|
||||
|
||||
static int oneone;
|
||||
|
||||
static int apc_all(void * cls,
|
||||
const struct sockaddr * addr,
|
||||
socklen_t addrlen) {
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
struct CBC {
|
||||
char * buf;
|
||||
size_t pos;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static size_t copyBuffer(void * ptr,
|
||||
size_t size,
|
||||
size_t nmemb,
|
||||
void * ctx) {
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static int ahc_echo(void * cls,
|
||||
struct MHD_Connection * connection,
|
||||
const char * url,
|
||||
const char * method,
|
||||
const char * version,
|
||||
const char * upload_data,
|
||||
unsigned int * upload_data_size) {
|
||||
const char * me = cls;
|
||||
struct MHD_Response * response;
|
||||
int ret;
|
||||
|
||||
if (0 != strcmp(me, method))
|
||||
return MHD_NO; /* unexpected method */
|
||||
response = MHD_create_response_from_data(strlen(url),
|
||||
(void*) url,
|
||||
MHD_NO,
|
||||
MHD_YES);
|
||||
ret = MHD_queue_response(connection,
|
||||
MHD_HTTP_OK,
|
||||
response);
|
||||
MHD_destroy_response(response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int testLongUrlGet() {
|
||||
struct MHD_Daemon * d;
|
||||
CURL * c;
|
||||
char buf[2048];
|
||||
struct CBC cbc;
|
||||
char * url;
|
||||
long code;
|
||||
|
||||
cbc.buf = buf;
|
||||
cbc.size = 2048;
|
||||
cbc.pos = 0;
|
||||
d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */,
|
||||
1080,
|
||||
&apc_all,
|
||||
NULL,
|
||||
&ahc_echo,
|
||||
"GET",
|
||||
MHD_OPTION_CONNECTION_MEMORY_LIMIT,
|
||||
VERY_LONG / 2,
|
||||
MHD_OPTION_END);
|
||||
if (d == NULL)
|
||||
return 1;
|
||||
c = curl_easy_init();
|
||||
url = malloc(VERY_LONG);
|
||||
memset(url,
|
||||
'a',
|
||||
VERY_LONG);
|
||||
url[VERY_LONG-1] = '\0';
|
||||
memcpy(url,
|
||||
"http://localhost:1080/",
|
||||
strlen("http://localhost:1080/"));
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_URL,
|
||||
url);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
©Buffer);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_WRITEDATA,
|
||||
&cbc);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_FAILONERROR,
|
||||
1);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_TIMEOUT,
|
||||
2L);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_CONNECTTIMEOUT,
|
||||
2L);
|
||||
if (oneone)
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_HTTP_VERSION,
|
||||
CURL_HTTP_VERSION_1_1);
|
||||
else
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_HTTP_VERSION,
|
||||
CURL_HTTP_VERSION_1_0);
|
||||
// NOTE: use of CONNECTTIMEOUT without also
|
||||
// setting NOSIGNAL results in really weird
|
||||
// crashes on my system!
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_NOSIGNAL,
|
||||
1);
|
||||
if (CURLE_OK == curl_easy_perform(c)) {
|
||||
curl_easy_cleanup(c);
|
||||
MHD_stop_daemon(d);
|
||||
free(url);
|
||||
return 2;
|
||||
}
|
||||
if (CURLE_OK != curl_easy_getinfo(c,
|
||||
CURLINFO_RESPONSE_CODE,
|
||||
&code)) {
|
||||
curl_easy_cleanup(c);
|
||||
MHD_stop_daemon(d);
|
||||
free(url);
|
||||
return 4;
|
||||
}
|
||||
curl_easy_cleanup(c);
|
||||
MHD_stop_daemon(d);
|
||||
free(url);
|
||||
if (code != MHD_HTTP_REQUEST_URI_TOO_LONG)
|
||||
return 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int testLongHeaderGet() {
|
||||
struct MHD_Daemon * d;
|
||||
CURL * c;
|
||||
char buf[2048];
|
||||
struct CBC cbc;
|
||||
char * url;
|
||||
long code;
|
||||
struct curl_slist * header = NULL;
|
||||
|
||||
cbc.buf = buf;
|
||||
cbc.size = 2048;
|
||||
cbc.pos = 0;
|
||||
d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */,
|
||||
1080,
|
||||
&apc_all,
|
||||
NULL,
|
||||
&ahc_echo,
|
||||
"GET",
|
||||
MHD_OPTION_CONNECTION_MEMORY_LIMIT,
|
||||
VERY_LONG / 2,
|
||||
MHD_OPTION_END);
|
||||
if (d == NULL)
|
||||
return 16;
|
||||
c = curl_easy_init();
|
||||
url = malloc(VERY_LONG);
|
||||
memset(url,
|
||||
'a',
|
||||
VERY_LONG);
|
||||
url[VERY_LONG-1] = '\0';
|
||||
url[VERY_LONG/2] = ':';
|
||||
url[VERY_LONG/2+1] = ':';
|
||||
header = curl_slist_append(header,
|
||||
url);
|
||||
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_HTTPHEADER,
|
||||
header);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_URL,
|
||||
"http://localhost:1080/hello_world");
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
©Buffer);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_WRITEDATA,
|
||||
&cbc);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_FAILONERROR,
|
||||
1);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_TIMEOUT,
|
||||
2L);
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_CONNECTTIMEOUT,
|
||||
2L);
|
||||
if (oneone)
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_HTTP_VERSION,
|
||||
CURL_HTTP_VERSION_1_1);
|
||||
else
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_HTTP_VERSION,
|
||||
CURL_HTTP_VERSION_1_0);
|
||||
// NOTE: use of CONNECTTIMEOUT without also
|
||||
// setting NOSIGNAL results in really weird
|
||||
// crashes on my system!
|
||||
curl_easy_setopt(c,
|
||||
CURLOPT_NOSIGNAL,
|
||||
1);
|
||||
if (CURLE_OK == curl_easy_perform(c)) {
|
||||
curl_easy_cleanup(c);
|
||||
MHD_stop_daemon(d);
|
||||
curl_slist_free_all(header);
|
||||
free(url);
|
||||
return 32;
|
||||
}
|
||||
if (CURLE_OK != curl_easy_getinfo(c,
|
||||
CURLINFO_RESPONSE_CODE,
|
||||
&code)) {
|
||||
curl_slist_free_all(header);
|
||||
curl_easy_cleanup(c);
|
||||
MHD_stop_daemon(d);
|
||||
free(url);
|
||||
return 64;
|
||||
}
|
||||
curl_slist_free_all(header);
|
||||
curl_easy_cleanup(c);
|
||||
MHD_stop_daemon(d);
|
||||
free(url);
|
||||
if (code != MHD_HTTP_REQUEST_ENTITY_TOO_LARGE)
|
||||
return 128;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main(int argc,
|
||||
char * const * argv) {
|
||||
unsigned int errorCount = 0;
|
||||
|
||||
oneone = NULL != strstr(argv[0], "11");
|
||||
if (0 != curl_global_init(CURL_GLOBAL_WIN32))
|
||||
return 2;
|
||||
errorCount += testLongUrlGet();
|
||||
errorCount += testLongHeaderGet();
|
||||
if (errorCount != 0)
|
||||
fprintf(stderr,
|
||||
"Error (code: %u)\n",
|
||||
errorCount);
|
||||
curl_global_cleanup();
|
||||
return errorCount != 0; /* 0 == pass */
|
||||
}
|
||||
Reference in New Issue
Block a user