From 0cacf72380222e704cff8c05b329ba6bac41f631 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 8 Aug 2007 08:07:49 +0000 Subject: updates --- src/daemon/Makefile.am | 20 +- src/daemon/connection.c | 483 ++++++++++++++++++++++++++++++------------- src/daemon/daemon.c | 65 ++++-- src/daemon/daemontest_post.c | 91 ++++---- src/daemon/daemontest_put.c | 36 +++- src/daemon/internal.h | 87 +++++++- src/daemon/memorypool.c | 180 ++++++++++++++++ src/daemon/memorypool.h | 87 ++++++++ 8 files changed, 829 insertions(+), 220 deletions(-) create mode 100644 src/daemon/memorypool.c create mode 100644 src/daemon/memorypool.h (limited to 'src/daemon') diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index 5ce824cc..e0174f40 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -11,6 +11,7 @@ libmicrohttpd_la_SOURCES = \ connection.c connection.h \ daemon.c \ internal.c internal.h \ + memorypool.c memorypool.h \ plibc.h \ response.c response.h @@ -31,7 +32,9 @@ check_PROGRAMS = \ daemontest \ daemontest_get \ daemontest_post \ - daemontest_put + daemontest_put \ + daemontest_post11 \ + daemontest_put11 TESTS = $(check_PROGRAMS) @@ -46,18 +49,29 @@ daemontest_get_LDADD = \ $(top_builddir)/src/daemon/libmicrohttpd.la \ @LIBCURL@ - daemontest_post_SOURCES = \ daemontest_post.c daemontest_post_LDADD = \ $(top_builddir)/src/daemon/libmicrohttpd.la \ @LIBCURL@ - daemontest_put_SOURCES = \ daemontest_put.c daemontest_put_LDADD = \ $(top_builddir)/src/daemon/libmicrohttpd.la \ @LIBCURL@ +daemontest_post11_SOURCES = \ + daemontest_post.c +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@ + endif \ No newline at end of file diff --git a/src/daemon/connection.c b/src/daemon/connection.c index 21b7872c..bb3d8990 100644 --- a/src/daemon/connection.c +++ b/src/daemon/connection.c @@ -27,8 +27,14 @@ #include "internal.h" #include "connection.h" +#include "memorypool.h" #include "response.h" +/** + * Size by which MHD usually tries to increment read/write buffers. + */ +#define MHD_BUF_INC_SIZE 2048 + /** * Get all of the headers from the request. @@ -40,9 +46,9 @@ */ int MHD_get_connection_values(struct MHD_Connection * connection, - enum MHD_ValueKind kind, - MHD_KeyValueIterator iterator, - void * iterator_cls) { + enum MHD_ValueKind kind, + MHD_KeyValueIterator iterator, + void * iterator_cls) { int ret; struct MHD_HTTP_Header * pos; @@ -131,41 +137,77 @@ MHD_connection_get_fdset(struct MHD_Connection * connection, fd_set * except_fd_set, int * max_fd) { int fd; + void * buf; fd = connection->socket_fd; if (fd == -1) return MHD_YES; if ( (connection->read_close == 0) && ( (connection->headersReceived == 0) || - (connection->readLoc < connection->read_buffer_size) ) ) + (connection->readLoc < connection->read_buffer_size) ) ) { FD_SET(fd, read_fd_set); - if (connection->response != NULL) + if (fd > *max_fd) + *max_fd = fd; + } else { + + + if ( (connection->read_close == 0) && + ( (connection->headersReceived == 1) && + (connection->post_processed == MHD_NO) && + (connection->readLoc == connection->read_buffer_size) ) ) { + /* try growing the read buffer, just in case */ + buf = MHD_pool_reallocate(connection->pool, + connection->read_buffer, + connection->read_buffer_size, + connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE); + if (buf != NULL) { + /* we can actually grow the buffer, do it! */ + connection->read_buffer = buf; + connection->read_buffer_size = connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE; + FD_SET(fd, read_fd_set); + if (fd > *max_fd) + *max_fd = fd; + } + } + } + if (connection->response != NULL) { FD_SET(fd, write_fd_set); - if ( (fd > *max_fd) && - ( (connection->headersReceived == 0) || - (connection->readLoc < connection->read_buffer_size) || - (connection->response != NULL) ) ) - *max_fd = fd; + if (fd > *max_fd) + *max_fd = fd; + } return MHD_YES; } /** - * Parse a single line of the HTTP header. Remove it - * from the read buffer. If the current line does not + * We ran out of memory processing the + * header. Handle it properly. + */ +static void +MHD_excessive_header_handler(struct MHD_Connection * connection) { + /* die, header far too long to be reasonable; + FIXME: send proper response to client + (stop reading, queue proper response) */ + MHD_DLOG(connection->daemon, + "Received excessively long header line, closing connection.\n"); + CLOSE(connection->socket_fd); + connection->socket_fd = -1; +} + +/** + * Parse a single line of the HTTP header. Advance + * read_buffer (!) appropriately. If the current line does not * fit, consider growing the buffer. If the line is * far too long, close the connection. If no line is * found (incomplete, buffer too small, line too long), - * return NULL. Otherwise return a copy of the line. + * return NULL. Otherwise return a pointer to the line. */ static char * MHD_get_next_header_line(struct MHD_Connection * connection) { char * rbuf; size_t pos; - size_t start; if (connection->readLoc == 0) return NULL; - start = 0; pos = 0; rbuf = connection->read_buffer; while ( (pos < connection->readLoc - 1) && @@ -175,56 +217,55 @@ MHD_get_next_header_line(struct MHD_Connection * connection) { if (pos == connection->readLoc - 1) { /* not found, consider growing... */ if (connection->readLoc == connection->read_buffer_size) { - /* grow buffer to read larger header or die... */ - if (connection->read_buffer_size < 4 * MHD_MAX_BUF_SIZE) { - rbuf = malloc(connection->read_buffer_size * 2); - memcpy(rbuf, - connection->read_buffer, - connection->readLoc); - free(connection->read_buffer); - connection->read_buffer = rbuf; - connection->read_buffer_size *= 2; + rbuf = MHD_pool_reallocate(connection->pool, + connection->read_buffer, + connection->read_buffer_size, + connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE); + if (rbuf == NULL) { + MHD_excessive_header_handler(connection); } else { - /* die, header far too long to be reasonable */ - MHD_DLOG(connection->daemon, - "Received excessively long header line (>%u), closing connection.\n", - 4 * MHD_MAX_BUF_SIZE); - CLOSE(connection->socket_fd); - connection->socket_fd = -1; + connection->read_buffer_size = connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE; + connection->read_buffer = rbuf; } } return NULL; } /* found, check if we have proper CRLF */ - rbuf = malloc(pos + 1); - memcpy(rbuf, - connection->read_buffer, - pos); - rbuf[pos] = '\0'; - if ( (connection->read_buffer[pos] == '\r') && - (connection->read_buffer[pos+1] == '\n') ) - pos++; /* skip both r and n */ - pos++; - memmove(connection->read_buffer, - &connection->read_buffer[pos], - connection->readLoc - pos); + if ( (rbuf[pos] == '\r') && + (rbuf[pos+1] == '\n') ) + rbuf[pos++] = '\0'; /* skip both r and n */ + rbuf[pos++] = '\0'; + connection->read_buffer += pos; + connection->read_buffer_size -= pos; connection->readLoc -= pos; return rbuf; } -static void +/** + * @return MHD_NO on failure (out of memory), MHD_YES for success + */ +static int MHD_connection_add_header(struct MHD_Connection * connection, - const char * key, - const char * value, - enum MHD_ValueKind kind) { + char * key, + char * value, + enum MHD_ValueKind kind) { struct MHD_HTTP_Header * hdr; - hdr = malloc(sizeof(struct MHD_HTTP_Header)); + hdr = MHD_pool_allocate(connection->pool, + sizeof(struct MHD_HTTP_Header), + MHD_YES); + if (hdr == NULL) { + MHD_DLOG(connection->daemon, + "Not enough memory to allocate header record!\n"); + MHD_excessive_header_handler(connection); + return MHD_NO; + } hdr->next = connection->headers_received; - hdr->header = strdup(key); - hdr->value = strdup(value); + hdr->header = key; + hdr->value = value; hdr->kind = kind; connection->headers_received = hdr; + return MHD_YES; } /** @@ -253,7 +294,10 @@ MHD_http_unescape(char * val) { } } -static void +/** + * @return MHD_NO on failure (out of memory), MHD_YES for success + */ +static int parse_arguments(enum MHD_ValueKind kind, struct MHD_Connection * connection, char * args) { @@ -263,7 +307,7 @@ parse_arguments(enum MHD_ValueKind kind, while (args != NULL) { equals = strstr(args, "="); if (equals == NULL) - return; /* invalid, ignore */ + return MHD_NO; /* invalid, ignore */ equals[0] = '\0'; equals++; amper = strstr(equals, "&"); @@ -273,18 +317,22 @@ parse_arguments(enum MHD_ValueKind kind, } MHD_http_unescape(args); MHD_http_unescape(equals); - MHD_connection_add_header(connection, - args, - equals, - kind); + if (MHD_NO == MHD_connection_add_header(connection, + args, + equals, + kind)) + return MHD_NO; args = amper; } + return MHD_YES; } /** * Parse the cookie header (see RFC 2109). + * + * @return MHD_YES for success, MHD_NO for failure (malformed, out of memory) */ -static void +static int MHD_parse_cookie_header(struct MHD_Connection * connection) { const char * hdr; char * cpy; @@ -294,11 +342,22 @@ MHD_parse_cookie_header(struct MHD_Connection * connection) { int quotes; hdr = MHD_lookup_connection_value(connection, - MHD_HEADER_KIND, - "Cookie"); + MHD_HEADER_KIND, + "Cookie"); if (hdr == NULL) - return; - cpy = strdup(hdr); + return MHD_YES; + cpy = MHD_pool_allocate(connection->pool, + strlen(hdr)+1, + MHD_YES); + if (cpy == NULL) { + MHD_DLOG(connection->daemon, + "Not enough memory to parse cookies!\n"); + MHD_excessive_header_handler(connection); + return MHD_NO; + } + memcpy(cpy, + hdr, + strlen(hdr)+1); pos = cpy; while (pos != NULL) { equals = strstr(pos, "="); @@ -328,13 +387,14 @@ MHD_parse_cookie_header(struct MHD_Connection * connection) { equals[strlen(equals)-1] = '\0'; equals++; } - MHD_connection_add_header(connection, - pos, - equals, - MHD_COOKIE_KIND); + if (MHD_NO == MHD_connection_add_header(connection, + pos, + equals, + MHD_COOKIE_KIND)) + return MHD_NO; pos = semicolon; } - free(cpy); + return MHD_YES; } /** @@ -355,7 +415,7 @@ parse_initial_message_line(struct MHD_Connection * connection, if (uri == NULL) return MHD_NO; /* serious error */ uri[0] = '\0'; - connection->method = strdup(line); + connection->method = line; uri++; while (uri[0] == ' ') uri++; @@ -372,11 +432,11 @@ parse_initial_message_line(struct MHD_Connection * connection, connection, args); } - connection->url = strdup(uri); + connection->url = uri; if (httpVersion == NULL) - connection->version = strdup(""); + connection->version = ""; else - connection->version = strdup(httpVersion); + connection->version = httpVersion; return MHD_YES; } @@ -398,6 +458,7 @@ MHD_parse_connection_headers(struct MHD_Connection * connection) { char * colon; char * tmp; const char * clen; + const char * end; unsigned long long cval; if (connection->bodyReceived == 1) @@ -409,51 +470,38 @@ MHD_parse_connection_headers(struct MHD_Connection * connection) { (line[0] == '\t') ) { /* value was continued on the next line, see http://www.jmarshall.com/easy/http/ */ - if ( (strlen(line) + strlen(last) > - 4 * MHD_MAX_BUF_SIZE) ) { - free(line); - free(last); - last = NULL; - MHD_DLOG(connection->daemon, - "Received excessively long header line (>%u), closing connection.\n", - 4 * MHD_MAX_BUF_SIZE); - CLOSE(connection->socket_fd); - connection->socket_fd = -1; + last = MHD_pool_reallocate(connection->pool, + last, + strlen(last)+1, + strlen(line) + strlen(last) + 1); + if (last == NULL) { + MHD_excessive_header_handler(connection); break; } - tmp = malloc(strlen(line) + strlen(last) + 1); - strcpy(tmp, last); - free(last); - last = tmp; tmp = line; while ( (tmp[0] == ' ') || (tmp[0] == '\t') ) tmp++; /* skip whitespace at start of 2nd line */ - strcat(last, tmp); - free(line); + strcat(last, tmp); continue; /* possibly more than 2 lines... */ } else { - MHD_connection_add_header(connection, - last, - colon, - MHD_HEADER_KIND); - free(last); + if (MHD_NO == MHD_connection_add_header(connection, + last, + colon, + MHD_HEADER_KIND)) + return; last = NULL; } } if (connection->url == NULL) { /* line must be request line (first line of header) */ if (MHD_NO == parse_initial_message_line(connection, - line)) { - free(line); - goto DIE; - } - free(line); + line)) + goto DIE; continue; } /* check if this is the end of the header */ if (strlen(line) == 0) { - free(line); /* end of header */ connection->headersReceived = 1; clen = MHD_lookup_connection_value(connection, @@ -483,6 +531,17 @@ MHD_parse_connection_headers(struct MHD_Connection * connection) { connection->bodyReceived = 0; } } + end = MHD_lookup_connection_value(connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONNECTION); + if ( (end != NULL) && + (0 == strcasecmp(end, + "close")) ) { + /* other side explicitly requested + that we close the connection after + this request */ + connection->read_close = MHD_YES; + } break; } /* line should be normal header line, find colon */ @@ -507,13 +566,12 @@ MHD_parse_connection_headers(struct MHD_Connection * connection) { with a space...) */ last = line; } - if (last != NULL) { - MHD_connection_add_header(connection, - last, - colon, - MHD_HEADER_KIND); - free(last); - } + if ( (last != NULL) && + (MHD_NO == MHD_connection_add_header(connection, + last, + colon, + MHD_HEADER_KIND)) ) + return; /* error */ MHD_parse_cookie_header(connection); return; DIE: @@ -539,6 +597,103 @@ MHD_find_access_handler(struct MHD_Connection * connection) { return &connection->daemon->default_handler; } +/** + * Test if we are able to process the POST data. + * This depends on available memory (enough to load + * all of the POST data into the pool) and the + * content encoding of the POST data. And of course, + * this requires that the request is actually a + * POST request. + * + * @return MHD_YES if so + */ +static int +MHD_test_post_data(struct MHD_Connection * connection) { + const char * encoding; + void * buf; + + if (0 != strcasecmp(connection->method, + MHD_HTTP_METHOD_POST)) + return MHD_NO; + encoding = MHD_lookup_connection_value(connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_TYPE); + if (encoding == NULL) + return MHD_NO; + if ( (0 == strcasecmp(MHD_HTTP_POST_ENCODING_FORM_URLENCODED, + encoding)) && + (connection->uploadSize != -1) ) { + buf = MHD_pool_reallocate(connection->pool, + connection->read_buffer, + connection->read_buffer_size, + connection->uploadSize + 1); + if (buf == NULL) + return MHD_NO; + connection->read_buffer_size = connection->uploadSize + 1; + connection->read_buffer = buf; + return MHD_YES; + } + return MHD_NO; +} + +/** + * Process the POST data here (adding to headers). + * + * Needs to first check POST encoding and then do + * the right thing (TM). The POST data is in the + * connection's post_data buffer between the postPos + * and postLoc offsets. The POST message maybe + * incomplete. The existing buffer (allocated from + * the pool) can be used and modified but must then + * be properly removed from the struct. + * + * @return MHD_YES on success, MHD_NO on error (i.e. out of + * memory). + */ +static int +MHD_parse_post_data(struct MHD_Connection * connection) { + const char * encoding; + int ret; + + encoding = MHD_lookup_connection_value(connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_TYPE); + if (encoding == NULL) + return MHD_NO; + if (0 == strcasecmp(MHD_HTTP_POST_ENCODING_FORM_URLENCODED, + encoding)) { + ret = parse_arguments(MHD_POSTDATA_KIND, + connection, + connection->read_buffer); + /* invalidate read buffer for other uses -- + in particular, do not give it to the + client; if this were to be needed, we would + have to make a copy, which would double memory + requirements */ + connection->read_buffer_size = 0; + connection->readLoc = 0; + connection->uploadSize = 0; + connection->read_buffer = NULL; + return ret; + } + if (0 == strcasecmp(MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, + encoding)) { + /* this code should never been reached right now, + since the test_post_data function would already + return MHD_NO; code is here only for future + extensions... */ + /* see http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4 */ + MHD_DLOG(connection->daemon, + "Unsupported multipart encoding of POST data specified, not processing POST data.\n"); + return MHD_NO; + } + /* this should never be reached, just here for + error checking */ + MHD_DLOG(connection->daemon, + "Unknown encoding of POST data specified, not processing POST data.\n"); + return MHD_NO; +} + /** * Call the handler of the application for this * connection. @@ -552,9 +707,6 @@ MHD_call_connection_handler(struct MHD_Connection * connection) { abort(); /* bad timing... */ ah = MHD_find_access_handler(connection); processed = connection->readLoc; - /* FIXME: in case of POST, we need to - process the POST data here as well - (adding to the header list! */ if (MHD_NO == ah->dh(ah->dh_cls, connection, connection->url, @@ -564,7 +716,7 @@ MHD_call_connection_handler(struct MHD_Connection * connection) { &processed)) { /* serios internal error, close connection */ MHD_DLOG(connection->daemon, - "Internal application error, closing connection."); + "Internal application error, closing connection.\n"); CLOSE(connection->socket_fd); connection->socket_fd = -1; return; @@ -583,7 +735,6 @@ MHD_call_connection_handler(struct MHD_Connection * connection) { connection->bodyReceived = 1; connection->readLoc = 0; connection->read_buffer_size = 0; - free(connection->read_buffer); connection->read_buffer = NULL; } } @@ -600,17 +751,30 @@ MHD_connection_handle_read(struct MHD_Connection * connection) { int bytes_read; void * tmp; + if (connection->pool == NULL) + connection->pool = MHD_pool_create(connection->daemon->pool_size); + if (connection->pool == NULL) { + MHD_DLOG(connection->daemon, + "Failed to create memory pool!\n"); + CLOSE(connection->socket_fd); + connection->socket_fd = -1; + return MHD_NO; + } if ( (connection->readLoc >= connection->read_buffer_size) && (connection->headersReceived == 0) ) { /* need to grow read buffer */ - tmp = malloc(connection->read_buffer_size * 2 + MHD_MAX_BUF_SIZE); - memcpy(tmp, - connection->read_buffer, - connection->read_buffer_size); - connection->read_buffer_size = connection->read_buffer_size * 2 + MHD_MAX_BUF_SIZE; - if (connection->read_buffer != NULL) - free(connection->read_buffer); + tmp = MHD_pool_reallocate(connection->pool, + connection->read_buffer, + connection->read_buffer_size, + connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE); + if (tmp == NULL) { + MHD_DLOG(connection->daemon, + "Not enough memory for reading headers!\n"); + MHD_excessive_header_handler(connection); + return MHD_NO; + } connection->read_buffer = tmp; + connection->read_buffer_size = connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE; } if (connection->readLoc >= connection->read_buffer_size) { MHD_DLOG(connection->daemon, @@ -634,16 +798,28 @@ MHD_connection_handle_read(struct MHD_Connection * connection) { } if (bytes_read == 0) { /* other side closed connection */ + connection->read_close = MHD_YES; if (connection->readLoc > 0) MHD_call_connection_handler(connection); shutdown(connection->socket_fd, SHUT_RD); return MHD_YES; } connection->readLoc += bytes_read; - if (connection->headersReceived == 0) + if (connection->headersReceived == 0) { MHD_parse_connection_headers(connection); - if (connection->headersReceived == 1) - MHD_call_connection_handler(connection); + if (connection->headersReceived == 1) { + connection->post_processed = MHD_test_post_data(connection); + } + } + if (connection->headersReceived == 1) { + if ( (connection->post_processed == MHD_YES) && + (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) ) + MHD_call_connection_handler(connection); + } return MHD_YES; } @@ -666,9 +842,9 @@ MHD_add_extra_headers(struct MHD_Connection * connection) { } else if (NULL == MHD_get_response_header(connection->response, MHD_HTTP_HEADER_CONTENT_LENGTH)) { _REAL_SNPRINTF(buf, - 128, - "%llu", - (unsigned long long) connection->response->total_size); + 128, + "%llu", + (unsigned long long) connection->response->total_size); MHD_add_response_header(connection->response, MHD_HTTP_HEADER_CONTENT_LENGTH, buf); @@ -680,7 +856,7 @@ MHD_add_extra_headers(struct MHD_Connection * connection) { * fill it with all of the headers from the * HTTPd's response. */ -static void +static int MHD_build_header_response(struct MHD_Connection * connection) { size_t size; size_t off; @@ -702,7 +878,14 @@ MHD_build_header_response(struct MHD_Connection * connection) { pos = pos->next; } /* produce data */ - data = malloc(size + 1); + data = MHD_pool_allocate(connection->pool, + size + 1, + MHD_YES); + if (data == NULL) { + MHD_DLOG(connection->daemon, + "Not enough memory for write!\n"); + return MHD_NO; + } memcpy(data, code, off); @@ -721,7 +904,10 @@ MHD_build_header_response(struct MHD_Connection * connection) { if (off != size) abort(); connection->write_buffer = data; - connection->write_buffer_size = size; + connection->writeLoc = size; + connection->writePos = 0; + connection->write_buffer_size = size + 1; + return MHD_YES; } /** @@ -743,11 +929,16 @@ MHD_connection_handle_write(struct MHD_Connection * connection) { return MHD_NO; } if (! connection->headersSent) { - if (connection->write_buffer == NULL) - MHD_build_header_response(connection); + if ( (connection->write_buffer == NULL) && + (MHD_NO == MHD_build_header_response(connection)) ) { + /* oops - close! */ + CLOSE(connection->socket_fd); + connection->socket_fd = -1; + return MHD_NO; + } ret = SEND(connection->socket_fd, - &connection->write_buffer[connection->writeLoc], - connection->write_buffer_size - connection->writeLoc, + &connection->write_buffer[connection->writePos], + connection->writeLoc - connection->writePos, 0); if (ret < 0) { if (errno == EINTR) @@ -759,13 +950,17 @@ MHD_connection_handle_write(struct MHD_Connection * connection) { connection->socket_fd = -1; return MHD_YES; } - connection->writeLoc += ret; - if (connection->writeLoc == connection->write_buffer_size) { + connection->writePos += ret; + if (connection->writeLoc == connection->writePos) { connection->writeLoc = 0; - free(connection->write_buffer); + connection->writePos = 0; + connection->headersSent = 1; + MHD_pool_reallocate(connection->pool, + connection->write_buffer, + connection->write_buffer_size, + 0); connection->write_buffer = NULL; connection->write_buffer_size = 0; - connection->headersSent = 1; } return MHD_YES; } @@ -781,13 +976,13 @@ MHD_connection_handle_write(struct MHD_Connection * connection) { if (response->data_size == 0) { if (response->data != NULL) free(response->data); - response->data = malloc(MHD_MAX_BUF_SIZE); - response->data_size = MHD_MAX_BUF_SIZE; + response->data = malloc(MHD_BUF_INC_SIZE); + response->data_size = MHD_BUF_INC_SIZE; } ret = response->crc(response->crc_cls, connection->messagePos, response->data, - MAX(MHD_MAX_BUF_SIZE, + MAX(MHD_BUF_INC_SIZE, response->data_size - connection->messagePos)); if (ret == -1) { /* end of message, signal other side by closing! */ @@ -838,22 +1033,26 @@ MHD_connection_handle_write(struct MHD_Connection * connection) { connection->headersSent = 0; connection->bodyReceived = 0; connection->messagePos = 0; - free(connection->method); connection->method = NULL; - free(connection->url); connection->url = NULL; - free(connection->write_buffer); - connection->write_buffer = NULL; - connection->write_buffer_size = 0; if ( (connection->read_close != 0) || (0 != strcasecmp(MHD_HTTP_VERSION_1_1, connection->version)) ) { /* closed for reading => close for good! */ - CLOSE(connection->socket_fd); + if (connection->socket_fd != -1) + CLOSE(connection->socket_fd); connection->socket_fd = -1; } - free(connection->version); connection->version = NULL; + connection->read_buffer = NULL; + connection->write_buffer = NULL; + connection->read_buffer_size = 0; + connection->readLoc = 0; + connection->write_buffer_size = 0; + connection->writePos = 0; + connection->writeLoc = 0; + MHD_pool_destroy(connection->pool); + connection->pool = NULL; } return MHD_YES; } diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c index 39abee6a..48ec9219 100644 --- a/src/daemon/daemon.c +++ b/src/daemon/daemon.c @@ -28,9 +28,17 @@ #include "internal.h" #include "response.h" #include "connection.h" +#include "memorypool.h" -#define MHD_MAX_CONNECTIONS FD_SETSIZE -4 +/** + * Default connection limit. + */ +#define MHD_MAX_CONNECTIONS_DEFAULT FD_SETSIZE -4 +/** + * Default memory allowed per connection. + */ +#define MHD_POOL_SIZE_DEFAULT (1024 * 1024) /** * Register an access handler for all URIs beginning with uri_prefix. @@ -229,6 +237,13 @@ MHD_accept_connection(struct MHD_Daemon * daemon) { MHD_DLOG(daemon, "Error accepting connection: %s\n", STRERROR(errno)); + if (s != -1) + CLOSE(s); /* just in case */ + return MHD_NO; + } + if (daemon->max_connections == 0) { + /* above connection limit - reject */ + CLOSE(s); return MHD_NO; } if (MHD_NO == daemon->apc(daemon->apc_cls, @@ -241,7 +256,13 @@ MHD_accept_connection(struct MHD_Daemon * daemon) { memset(connection, 0, sizeof(struct MHD_Connection)); + connection->pool = NULL; connection->addr = malloc(addrlen); + if (connection->addr == NULL) { + CLOSE(s); + free(connection); + return MHD_NO; + } memcpy(connection->addr, addr, addrlen); @@ -258,11 +279,13 @@ MHD_accept_connection(struct MHD_Daemon * daemon) { STRERROR(errno)); free(connection->addr); CLOSE(s); + free(connection->addr); free(connection); return MHD_NO; } connection->next = daemon->connections; daemon->connections = connection; + daemon->max_connections--; return MHD_YES; } @@ -281,7 +304,6 @@ static void MHD_cleanup_connections(struct MHD_Daemon * daemon) { struct MHD_Connection * pos; struct MHD_Connection * prev; - struct MHD_HTTP_Header * hpos; void * unused; pos = daemon->connections; @@ -296,25 +318,12 @@ MHD_cleanup_connections(struct MHD_Daemon * daemon) { pthread_kill(pos->pid, SIGALRM); pthread_join(pos->pid, &unused); } - free(pos->addr); - if (pos->url != NULL) - free(pos->url); - if (pos->method != NULL) - free(pos->method); - if (pos->write_buffer != NULL) - free(pos->write_buffer); - if (pos->read_buffer != NULL) - free(pos->read_buffer); - while (pos->headers_received != NULL) { - hpos = pos->headers_received; - pos->headers_received = hpos->next; - free(hpos->header); - free(hpos->value); - free(hpos); - } if (pos->response != NULL) MHD_destroy_response(pos->response); + MHD_pool_destroy(pos->pool); + free(pos->addr); free(pos); + daemon->max_connections++; if (prev == NULL) pos = daemon->connections; else @@ -474,6 +483,8 @@ MHD_start_daemon(unsigned int options, struct sockaddr_in6 servaddr6; const struct sockaddr * servaddr; socklen_t addrlen; + va_list ap; + enum MHD_OPTION opt; if ((options & MHD_USE_SSL) != 0) return NULL; @@ -549,6 +560,24 @@ MHD_start_daemon(unsigned int options, retVal->default_handler.dh_cls = dh_cls; retVal->default_handler.uri_prefix = ""; retVal->default_handler.next = NULL; + retVal->max_connections = MHD_MAX_CONNECTIONS_DEFAULT; + retVal->pool_size = MHD_POOL_SIZE_DEFAULT; + va_start(ap, dh_cls); + while (MHD_OPTION_END != (opt = va_arg(ap, enum MHD_OPTION))) { + switch (opt) { + case MHD_OPTION_CONNECTION_MEMORY_LIMIT: + retVal->pool_size = va_arg(ap, unsigned int); + break; + case MHD_OPTION_CONNECTION_LIMIT: + retVal->max_connections = va_arg(ap, unsigned int); + break; + default: + fprintf(stderr, + "Invalid MHD_OPTION argument! (Did you terminate the list with MHD_OPTION_END?)\n"); + abort(); + } + } + va_end(ap); if ( ( (0 != (options & MHD_USE_THREAD_PER_CONNECTION)) || (0 != (options & MHD_USE_SELECT_INTERNALLY)) ) && (0 != pthread_create(&retVal->pid, diff --git a/src/daemon/daemontest_post.c b/src/daemon/daemontest_post.c index 9f5f6c6f..3c69dadc 100644 --- a/src/daemon/daemontest_post.c +++ b/src/daemon/daemontest_post.c @@ -39,6 +39,7 @@ #define POST_DATA "name=daniel&project=curl" +static int oneone; static int apc_all(void * cls, const struct sockaddr * addr, @@ -76,40 +77,36 @@ static int ahc_echo(void * cls, unsigned int * upload_data_size) { struct MHD_Response * response; int ret; + const char * r1; + const char * r2; if (0 != strcmp("POST", method)) { printf("METHOD: %s\n", method); return MHD_NO; /* unexpected method */ } - if ( (*upload_data_size < 24) && - (*upload_data_size > 0) ) - return MHD_YES; /* continue */ - if (*upload_data_size == 24) { - *upload_data_size = 0; - if ( (0 != strcmp("daniel", - MHD_lookup_connection_value(connection, - MHD_POSTDATA_KIND, - "name"))) || - (0 != strcmp("curl", - MHD_lookup_connection_value(connection, - MHD_POSTDATA_KIND, - "project"))) ) { - printf("POST DATA not processed correctly!\n"); - return MHD_NO; - } - - return MHD_YES; /* continue */ + r1 = MHD_lookup_connection_value(connection, + MHD_POSTDATA_KIND, + "name"); + r2 = MHD_lookup_connection_value(connection, + MHD_POSTDATA_KIND, + "project"); + if ( (r1 != NULL) && + (r2 != NULL) && + (0 == strcmp("daniel", + r1)) && + (0 == strcmp("curl", + r2)) ) { + 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 MHD_YES; /* done */ } - /* FIXME: check connection headers... */ - 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; + return MHD_YES; } @@ -118,7 +115,7 @@ static int testInternalPost() { CURL * c; char buf[2048]; struct CBC cbc; - + cbc.buf = buf; cbc.size = 2048; cbc.pos = 0; @@ -156,9 +153,14 @@ static int testInternalPost() { curl_easy_setopt(c, CURLOPT_TIMEOUT, 2L); - curl_easy_setopt(c, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_0); + 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); curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 2L); @@ -233,9 +235,14 @@ static int testMultithreadedPost() { curl_easy_setopt(c, CURLOPT_TIMEOUT, 2L); - curl_easy_setopt(c, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_0); + 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); curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 2L); @@ -321,9 +328,14 @@ static int testExternalPost() { curl_easy_setopt(c, CURLOPT_TIMEOUT, 5L); - curl_easy_setopt(c, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_0); + 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); curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 5L); @@ -429,11 +441,12 @@ 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 += testInternalPost(); errorCount += testMultithreadedPost(); - errorCount += testExternalPost(); + errorCount += testExternalPost(); if (errorCount != 0) fprintf(stderr, "Error (code: %u)\n", diff --git a/src/daemon/daemontest_put.c b/src/daemon/daemontest_put.c index ca64688b..dfcccb0a 100644 --- a/src/daemon/daemontest_put.c +++ b/src/daemon/daemontest_put.c @@ -32,6 +32,8 @@ #include #include +static int oneone; + static int apc_all(void * cls, const struct sockaddr * addr, socklen_t addrlen) { @@ -164,9 +166,14 @@ static int testInternalPut() { curl_easy_setopt(c, CURLOPT_TIMEOUT, 15L); - curl_easy_setopt(c, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_0); + 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); curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 15L); @@ -246,9 +253,14 @@ static int testMultithreadedPut() { curl_easy_setopt(c, CURLOPT_TIMEOUT, 15L); - curl_easy_setopt(c, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_0); + 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); curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 15L); @@ -339,9 +351,14 @@ static int testExternalPut() { curl_easy_setopt(c, CURLOPT_TIMEOUT, 15L); - curl_easy_setopt(c, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_0); + 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); curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 15L); @@ -447,6 +464,7 @@ 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 += testInternalPut(); diff --git a/src/daemon/internal.h b/src/daemon/internal.h index 101acfdd..efadb33a 100644 --- a/src/daemon/internal.h +++ b/src/daemon/internal.h @@ -37,6 +37,7 @@ #include #include #include +#include #include "config.h" #include "plibc.h" @@ -49,8 +50,6 @@ #include -#define MHD_MAX_BUF_SIZE 2048 - #define MAX(a,b) ((a)<(b)) ? (b) : (a) @@ -157,41 +156,72 @@ struct MHD_Response { struct MHD_Connection { + + /** + * This is a linked list. + */ struct MHD_Connection * next; + /** + * Reference to the MHD_Daemon struct. + */ struct MHD_Daemon * daemon; + /** + * Linked list of parsed headers. + */ struct MHD_HTTP_Header * headers_received; + /** + * Response to transmit (initially NULL). + */ struct MHD_Response * response; /** - * Request method. Should be GET/POST/etc. + * The memory pool is created whenever we first read + * from the TCP stream and destroyed at the end of + * each request (and re-created for the next request). + * In the meantime, this pointer is NULL. The + * pool is used for all connection-related data + * except for the response (which maybe shared between + * connections) and the IP address (which persists + * across individual requests). + */ + struct MemoryPool * pool; + + /** + * Request method. Should be GET/POST/etc. Allocated + * in pool. */ char * method; /** - * Requested URL (everything after "GET" only). + * Requested URL (everything after "GET" only). Allocated + * in pool. */ char * url; /** - * HTTP version string (i.e. http/1.1) + * HTTP version string (i.e. http/1.1). Allocated + * in pool. */ char * version; /** - * Buffer for reading requests. + * Buffer for reading requests. Allocated + * in pool. */ char * read_buffer; /** - * Buffer for writing response. + * Buffer for writing response (headers only). Allocated + * in pool. */ char * write_buffer; /** - * Foreign address (of length addr_len). + * Foreign address (of length addr_len). MALLOCED (not + * in pool!). */ struct sockaddr_in * addr; @@ -201,12 +231,30 @@ struct MHD_Connection { */ pthread_t pid; + /** + * Size of read_buffer (in bytes). + */ size_t read_buffer_size; + /** + * Position where we currently append data in + * read_buffer (last valid position). + */ size_t readLoc; + /** + * Size of write_buffer (in bytes). + */ size_t write_buffer_size; + /** + * Offset where we are with sending from write_buffer. + */ + size_t writePos; + + /** + * Last valid location in write_buffer. + */ size_t writeLoc; /** @@ -263,6 +311,11 @@ struct MHD_Connection { */ int headersSent; + /** + * Are we processing the POST data? + */ + int post_processed; + /** * HTTP response code. Only valid if response object * is already set. @@ -279,6 +332,9 @@ struct MHD_Daemon { struct MHD_Access_Handler default_handler; + /** + * Linked list of our current connections. + */ struct MHD_Connection * connections; MHD_AcceptPolicyCallback apc; @@ -300,12 +356,25 @@ struct MHD_Daemon { */ int shutdown; + /** + * Size of the per-connection memory pools. + */ + unsigned int pool_size; + + /** + * Limit on the number of parallel connections. + */ + unsigned int max_connections; + /** * Daemon's options. */ enum MHD_OPTION options; - unsigned short port; + /** + * Listen port. + */ + unsigned short port; }; diff --git a/src/daemon/memorypool.c b/src/daemon/memorypool.c new file mode 100644 index 00000000..5b54cd2e --- /dev/null +++ b/src/daemon/memorypool.c @@ -0,0 +1,180 @@ +/* + This file is part of libmicrohttpd + (C) 2007 Daniel Pittman + + 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 memorypool.c + * @brief memory pool + * @author Christian Grothoff + */ + +#include "memorypool.h" + +struct MemoryPool { + + /** + * Pointer to the pool's memory + */ + char * memory; + + /** + * Size of the pool. + */ + unsigned int size; + + /** + * Offset of the first unallocated byte. + */ + unsigned int pos; + + /** + * Offset of the last unallocated byte. + */ + unsigned int end; + + /** + * 0 if pool was malloc'ed, 1 if mmapped. + */ + int is_mmap; +}; + +/** + * Create a memory pool. + * + * @param max maximum size of the pool + */ +struct MemoryPool * MHD_pool_create(unsigned int max) { + struct MemoryPool * pool; + + pool = malloc(sizeof(struct MemoryPool)); + if (pool == NULL) + return NULL; + pool->memory = MMAP(NULL, max, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS, -1, 0); + if ( (pool->memory == MAP_FAILED) || + (pool->memory == NULL) ) { + pool->memory = malloc(max); + if (pool->memory == NULL) { + free(pool); + return NULL; + } + pool->is_mmap = 0; + } else { + pool->is_mmap = 1; + } + pool->pos = 0; + pool->end = max; + pool->size = max; + return pool; +} + +/** + * Destroy a memory pool. + */ +void MHD_pool_destroy(struct MemoryPool * pool) { + if (pool == NULL) + return; + if (pool->is_mmap == 0) + free(pool->memory); + else + MUNMAP(pool->memory, pool->size); + free(pool); +} + +/** + * Allocate size bytes from the pool. + * @return NULL if the pool cannot support size more + * bytes + */ +void * MHD_pool_allocate(struct MemoryPool * pool, + unsigned int size, + int from_end) { + void * ret; + + if ( (pool->pos + size > pool->end) || + (pool->pos + size < pool->pos) ) + return NULL; + if (from_end == MHD_YES) { + ret = &pool->memory[pool->end - size]; + pool->end -= size; + } else { + ret = &pool->memory[pool->pos]; + pool->pos += size; + } + return ret; +} + +/** + * Reallocate a block of memory obtained from the pool. + * This is particularly efficient when growing or + * shrinking the block that was last (re)allocated. + * If the given block is not the most recenlty + * (re)allocated block, the memory of the previous + * allocation may be leaked until the pool is + * destroyed (and copying the data maybe required). + * + * @param old the existing block + * @param old_size the size of the existing block + * @param new_size the new size of the block + * @return new address of the block, or + * NULL if the pool cannot support new_size + * bytes (old continues to be valid for old_size) + */ +void * MHD_pool_reallocate(struct MemoryPool * pool, + void * old, + unsigned int old_size, + unsigned int new_size) { + void * ret; + + if ( (pool->end < old_size) || + (pool->end < new_size) ) + return NULL; /* unsatisfiable or bogus request */ + + if ( (pool->pos >= old_size) && + (&pool->memory[pool->pos - old_size] == old) ) { + /* was the previous allocation - optimize! */ + if (pool->pos + new_size - old_size <= pool->end) { + /* fits */ + pool->pos += new_size - old_size; + if (new_size < old_size) /* shrinking - zero again! */ + memset(&pool->memory[pool->pos], + 0, + old_size - new_size); + return old; + } + /* does not fit */ + return NULL; + } + if (new_size <= old_size) + return old; /* cannot shrink, no need to move */ + if ( (pool->pos + new_size >= pool->pos) && + (pool->pos + new_size <= pool->end) ) { + /* fits */ + ret = &pool->memory[pool->pos]; + memcpy(ret, + old, + old_size); + pool->pos += new_size; + return ret; + } + /* does not fit */ + return NULL; +} + +/* end of memorypool.c */ diff --git a/src/daemon/memorypool.h b/src/daemon/memorypool.h new file mode 100644 index 00000000..c49e0d8e --- /dev/null +++ b/src/daemon/memorypool.h @@ -0,0 +1,87 @@ +/* + This file is part of libmicrohttpd + (C) 2007 Daniel Pittman + + 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 memorypool.h + * @brief memory pool; mostly used for efficient (de)allocation + * for each connection and bounding memory use for each + * request + * @author Christian Grothoff + */ + +#ifndef MEMORYPOOL_H +#define MEMORYPOOL_H + +#include "internal.h" + +/** + * Opaque handle for a memory pool. + * Pools are not reentrant and must not be used + * by multiple threads. + */ +struct MemoryPool; + +/** + * Create a memory pool. + * + * @param max maximum size of the pool + */ +struct MemoryPool * MHD_pool_create(unsigned int max); + +/** + * Destroy a memory pool. + */ +void MHD_pool_destroy(struct MemoryPool * pool); + +/** + * Allocate size bytes from the pool. + * + * @param from_end allocate from end of pool (set to MHD_YES); + * use this for small, persistent allocations that + * will never be reallocated + * @return NULL if the pool cannot support size more + * bytes + */ +void * MHD_pool_allocate(struct MemoryPool * pool, + unsigned int size, + int from_end); + +/** + * Reallocate a block of memory obtained from the pool. + * This is particularly efficient when growing or + * shrinking the block that was last (re)allocated. + * If the given block is not the most recenlty + * (re)allocated block, the memory of the previous + * allocation may be leaked until the pool is + * destroyed (and copying the data maybe required). + * + * @param old the existing block + * @param old_size the size of the existing block + * @param new_size the new size of the block + * @return new address of the block, or + * NULL if the pool cannot support new_size + * bytes (old continues to be valid for old_size) + */ +void * MHD_pool_reallocate(struct MemoryPool * pool, + void * old, + unsigned int old_size, + unsigned int new_size); + +#endif -- cgit v1.2.3