libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit c490e64780fd1c45592f5528bf755956f0f2eddb
parent e787fde83a61e3f9290c589139b657c7c414201c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 20 Dec 2007 04:23:00 +0000

new MHD with support for chunked encoding

Diffstat:
MChangeLog | 9+++++++++
MREADME | 11+++++------
Mconfigure.ac | 4++--
Msrc/daemon/Makefile.am | 13++++++++++---
Msrc/daemon/connection.c | 1845+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/daemon/connection.h | 25++++++++++++++++++-------
Msrc/daemon/daemon.c | 169+++++++++++--------------------------------------------------------------------
Msrc/daemon/daemontest_get.c | 20++++++++++++++------
Msrc/daemon/daemontest_large_put.c | 37++++++++++++++++++++++++++++++-------
Msrc/daemon/daemontest_postform.c | 9++++++++-
Msrc/daemon/daemontest_put_chunked.c | 5+++++
Msrc/daemon/fileserver_example.c | 10+++++++++-
Msrc/daemon/internal.h | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/daemon/memorypool.c | 29+++++++++++++++++++++++++++++
Msrc/daemon/memorypool.h | 12++++++++++++
Msrc/daemon/minimal_example.c | 10+++++++++-
Msrc/include/microhttpd.h | 47++++++++++++++++-------------------------------
17 files changed, 1515 insertions(+), 941 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,12 @@ +Wed Dec 19 21:12:04 MST 2007 + Implemented chunked (HTTP 1.1) downloads (including + sending of HTTP footers). Also allowed queuing of + a response early to suppress the otherwise automatic + "100 CONTINUE" response. Removed the mostly useless + "(un)register handler" methods from the API. Changed + the internal implementation to use a finite state + machine (cleaner code, slightly less memory consumption). - CG + Sun Dec 16 03:24:13 MST 2007 Implemented handling of chunked (HTTP 1.1) uploads. Note that the upload callback must be able to diff --git a/README b/README @@ -24,7 +24,7 @@ reporting (and use MHD_USE_DEBUG). Error reporting is not enabled by default to reduce the size of the library (error messages take space!). If you are concerned about space, you should set "CFLAGS" to "-Os --fomit-frame-pointer" to have gcc generate tight code. The -resulting binary should be less than 25k (on x86). +resulting binary should be about 25k (on x86). Portability @@ -50,11 +50,6 @@ indicates that a testcase should be written before implementing the feature. -For http/1.1-compliance: -======================== -connection.c: -- support sending of chunked responses (#1302, TEST, ARCH) - For POST: ========= - add support to decode multipart/form-data with @@ -71,6 +66,10 @@ Missing Testcases: - add testcases for http/1.1 pipelining (need to figure out how to ensure curl pipelines) - add testcases for resource limit enforcement +- add testcases for client queuing early response, + suppressing 100 CONTINUE +- extend testcase for chunked encoding to validate + handling of footers Documentation: ============== diff --git a/configure.ac b/configure.ac @@ -21,8 +21,8 @@ # # AC_PREREQ(2.57) -AC_INIT([libmicrohttpd], [0.1.2],[libmicrohttpd@gnunet.org]) -AM_INIT_AUTOMAKE([libmicrohttpd], [0.1.2]) +AC_INIT([libmicrohttpd], [0.2.0],[libmicrohttpd@gnunet.org]) +AM_INIT_AUTOMAKE([libmicrohttpd], [0.2.0]) AM_CONFIG_HEADER([config.h]) AH_TOP([#define _GNU_SOURCE 1]) diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am @@ -12,7 +12,7 @@ lib_LTLIBRARIES = \ libmicrohttpd.la libmicrohttpd_la_LDFLAGS = \ - -export-dynamic -version-info 2:1:1 $(retaincommand) + -export-dynamic -version-info 3:0:0 $(retaincommand) libmicrohttpd_la_SOURCES = \ connection.c connection.h \ reason_phrase.c reason_phrase.h \ @@ -48,7 +48,6 @@ check_PROGRAMS = \ daemontest_postform \ daemontest_post_loop \ daemontest_put \ - daemontest_put_chunked \ daemontest_large_put \ daemontest_get11 \ daemontest_post11 \ @@ -56,7 +55,9 @@ check_PROGRAMS = \ daemontest_post_loop11 \ daemontest_put11 \ daemontest_large_put11 \ - daemontest_long_header + daemontest_long_header \ + daemontest_get_chunked \ + daemontest_put_chunked TESTS = $(check_PROGRAMS) @@ -71,6 +72,12 @@ daemontest_get_LDADD = \ $(top_builddir)/src/daemon/libmicrohttpd.la \ @LIBCURL@ +daemontest_get_chunked_SOURCES = \ + daemontest_get_chunked.c +daemontest_get_chunked_LDADD = \ + $(top_builddir)/src/daemon/libmicrohttpd.la \ + @LIBCURL@ + daemontest_post_SOURCES = \ daemontest_post.c daemontest_post_LDADD = \ diff --git a/src/daemon/connection.c b/src/daemon/connection.c @@ -2,7 +2,6 @@ This file is part of libmicrohttpd (C) 2007 Daniel Pittman and Christian Grothoff - This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -62,6 +61,14 @@ */ #define REQUEST_LACKS_HOST "" +#define EXTRA_CHECKS MHD_YES + +#if EXTRA_CHECKS +#define EXTRA_CHECK(a) if (!(a)) abort(); +#else +#define EXTRA_CHECK(a) +#endif + /** * Add extra debug messages with reasons for closing connections * (non-error reasons). @@ -73,6 +80,10 @@ */ #define DEBUG_SEND_DATA MHD_NO +/** + * Should all state transitions be printed to stderr? + */ +#define DEBUG_STATES MHD_NO /** * Get all of the headers from the request. @@ -109,7 +120,6 @@ MHD_get_connection_values (struct MHD_Connection *connection, return ret; } - /** * Get a particular header value. If multiple * values match the kind, return any one of them. @@ -149,11 +159,11 @@ int MHD_queue_response (struct MHD_Connection *connection, unsigned int status_code, struct MHD_Response *response) { - if ((connection == NULL) || - (response == NULL) || - (connection->response != NULL) || - (connection->have_received_body == MHD_NO) - || (connection->have_received_headers == MHD_NO)) + if ( (connection == NULL) || + (response == NULL) || + (connection->response != NULL) || + ( (connection->state != MHD_CONNECTION_HEADERS_PROCESSED) && + (connection->state != MHD_CONNECTION_FOOTERS_RECEIVED) ) ) return MHD_NO; MHD_increment_response_rc (response); connection->response = response; @@ -165,6 +175,21 @@ MHD_queue_response (struct MHD_Connection *connection, have already sent the full message body */ connection->response_write_position = response->total_size; } + if ( (response->total_size == -1) && + (0 == strcasecmp(connection->version, + MHD_HTTP_VERSION_1_1)) ) + connection->have_chunked_response = MHD_YES; + else + connection->have_chunked_response = MHD_NO; + if (connection->state == MHD_CONNECTION_HEADERS_PROCESSED) + { + /* response was queued "early", + refuse to read body / footers or further + requests! */ + SHUTDOWN (connection->socket_fd, SHUT_RD); + connection->read_closed = MHD_YES; + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + } return MHD_YES; } @@ -173,14 +198,14 @@ MHD_queue_response (struct MHD_Connection *connection, * message for this connection? */ static int -MHD_need_100_continue (struct MHD_Connection *connection) +need_100_continue (struct MHD_Connection *connection) { const char *expect; - return ((connection->version != NULL) && + return ((connection->response == NULL) && + (connection->version != NULL) && (0 == strcasecmp (connection->version, MHD_HTTP_VERSION_1_1)) && - (connection->have_received_headers == MHD_YES) && (NULL != (expect = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_EXPECT))) @@ -199,6 +224,7 @@ connection_close_error (struct MHD_Connection *connection) SHUTDOWN (connection->socket_fd, SHUT_RDWR); CLOSE (connection->socket_fd); connection->socket_fd = -1; + connection->state = MHD_CONNECTION_CLOSED; if (connection->daemon->notify_completed != NULL) connection->daemon->notify_completed (connection->daemon-> notify_completed_cls, connection, @@ -216,12 +242,14 @@ connection_close_error (struct MHD_Connection *connection) * @return MHD_NO if readying the response failed */ static int -ready_response (struct MHD_Connection *connection) +try_ready_normal_body (struct MHD_Connection *connection) { int ret; struct MHD_Response *response; response = connection->response; + if (response->crc == NULL) + return MHD_YES; ret = response->crc (response->crc_cls, connection->response_write_position, response->data, @@ -230,7 +258,8 @@ ready_response (struct MHD_Connection *connection) connection->response_write_position)); if (ret == -1) { - /* end of message, signal other side by closing! */ + /* either error or http 1.0 transfer, close + socket! */ #if DEBUG_CLOSE #if HAVE_MESSAGES MHD_DLOG (connection->daemon, "Closing connection (end of response)\n"); @@ -239,94 +268,281 @@ ready_response (struct MHD_Connection *connection) response->total_size = connection->response_write_position; connection_close_error (connection); return MHD_NO; - } + } response->data_start = connection->response_write_position; response->data_size = ret; if (ret == 0) + return MHD_NO; + return MHD_YES; +} + +/** + * Prepare the response buffer of this connection for + * sending. Assumes that the response mutex is + * already held. If the transmission is complete, + * this function may close the socket (and return + * MHD_NO). + * + * @return MHD_NO if readying the response failed + */ +static int +try_ready_chunked_body (struct MHD_Connection *connection) +{ + int ret; + char * buf; + struct MHD_Response *response; + unsigned int size; + char cbuf[9]; + + response = connection->response; + if (connection->write_buffer_size == 0) { - /* avoid busy-waiting when using external select - (we assume that the main application will - wake up the external select once more data - is ready). With internal selects, we - have no choice; if the app uses a thread - per connection, ret==0 is likely a bug -- - the application should block until data - is ready! */ - if ((0 == - (connection->daemon-> - options & (MHD_USE_SELECT_INTERNALLY | - MHD_USE_THREAD_PER_CONNECTION)))) - connection->response_unready = MHD_YES; + size = connection->daemon->pool_size; + do + { + size /= 2; + if (size < 128) + { + /* not enough memory */ +#if DEBUG_CLOSE +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, "Closing connection (out of memory)\n"); +#endif +#endif + connection_close_error (connection); + return MHD_NO; + } + buf = MHD_pool_allocate (connection->pool, + size, + MHD_NO); + } + while (buf == NULL); + connection->write_buffer_size = size; + connection->write_buffer = buf; + } + + ret = response->crc (response->crc_cls, + connection->response_write_position, + &connection->write_buffer[8], + connection->write_buffer_size - 8 - 2); + if (ret == -1) + { + /* end of message, signal other side! */ + strcpy(connection->write_buffer, + "0\r\n"); + connection->write_buffer_append_offset = 3; + connection->write_buffer_send_offset = 0; + response->total_size = connection->response_write_position; + return MHD_YES; + } + if (ret == 0) + { + connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY; return MHD_NO; } - connection->response_unready = MHD_NO; + if (ret > 0xFFFFFF) + ret = 0xFFFFFF; + snprintf(cbuf, + 8, + "%X\r\n", + ret); + memcpy(&connection->write_buffer[8 - strlen(cbuf)], + cbuf, + strlen(cbuf)); + memcpy(&connection->write_buffer[8 + ret], + "\r\n", + 2); + connection->response_write_position += ret; + connection->write_buffer_send_offset = 8 - strlen(cbuf); + connection->write_buffer_append_offset = 8 + ret + 2; return MHD_YES; } /** - * Obtain the select sets for this connection - * - * @return MHD_YES on success + * Check if we need to set some additional headers + * for http-compiliance. */ -int -MHD_connection_get_fdset (struct MHD_Connection *connection, - fd_set * read_fd_set, - fd_set * write_fd_set, - fd_set * except_fd_set, int *max_fd) +static void +add_extra_headers (struct MHD_Connection *connection) { - int fd; - void *buf; + const char *have; + char buf[128]; - fd = connection->socket_fd; - if (fd == -1) - return MHD_YES; - if ((connection->read_close == MHD_NO) && - ((connection->have_received_headers == MHD_NO) || - (connection->read_buffer_offset < connection->read_buffer_size))) + connection->have_chunked_upload = MHD_NO; + if (connection->response->total_size == -1) + { + have = MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_CONNECTION); + if ( (have == NULL) || + (0 != strcasecmp(have, "close")) ) + { + if ( (connection->version != NULL) && + (0 == strcasecmp(connection->version, + MHD_HTTP_VERSION_1_1)) ) + { + connection->have_chunked_upload = MHD_YES; + have = MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_TRANSFER_ENCODING); + if (have == NULL) + MHD_add_response_header (connection->response, + MHD_HTTP_HEADER_TRANSFER_ENCODING, + "chunked"); + } + else + { + MHD_add_response_header (connection->response, + MHD_HTTP_HEADER_CONNECTION, "close"); + } + } + } + else if (NULL == MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_CONTENT_LENGTH)) { - FD_SET (fd, read_fd_set); - if (fd > *max_fd) - *max_fd = fd; + _REAL_SNPRINTF (buf, + 128, + "%llu", + (unsigned long long) connection->response->total_size); + MHD_add_response_header (connection->response, + MHD_HTTP_HEADER_CONTENT_LENGTH, buf); + } +} + +/** + * Produce HTTP "Date:" header. + * @param date where to write the header + * @param max maximum number of characters to write + */ +static void +get_date_string (char *date, unsigned int max) +{ + static const char *days[] = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + static const char *mons[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec" + }; + struct tm now; + time_t t; + + time (&t); + gmtime_r (&t, &now); + snprintf (date, + max - 1, + "Date: %3s, %02u %3s %04u %02u:%02u:%02u GMT\r\n", + days[now.tm_wday % 7], + now.tm_mday, + mons[now.tm_mon % 12], + now.tm_year, now.tm_hour, now.tm_min, now.tm_sec); +} + +/** + * try growing the read buffer + * @return MHD_YES on success, MHD_NO on failure + */ +static int +try_grow_read_buffer(struct MHD_Connection * connection) +{ + void * buf; + + buf = MHD_pool_reallocate (connection->pool, + connection->read_buffer, + connection->read_buffer_size, + connection->read_buffer_size * 2 + + MHD_BUF_INC_SIZE + 1); + if (buf == NULL) + return MHD_NO; + /* 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; + return MHD_YES; +} + +/** + * Allocate the connection's write buffer and + * fill it with all of the headers (or footers, + * if we have already sent the body) from the + * HTTPd's response. + */ +static int +build_header_response (struct MHD_Connection *connection) +{ + size_t size; + size_t off; + struct MHD_HTTP_Header *pos; + char code[128]; + char date[128]; + char *data; + enum MHD_ValueKind kind; + const char *reason_phrase; + + if (connection->state == MHD_CONNECTION_FOOTERS_RECEIVED) + { + add_extra_headers (connection); + reason_phrase = + MHD_get_reason_phrase_for (connection->responseCode); + _REAL_SNPRINTF (code, 128, "%s %u %s\r\n", MHD_HTTP_VERSION_1_1, + connection->responseCode, reason_phrase); + off = strlen (code); + /* estimate size */ + size = off + 2; /* extra \r\n at the end */ + kind = MHD_HEADER_KIND; + if (NULL == MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_DATE)) + get_date_string (date, sizeof (date)); + else + date[0] = '\0'; + size += strlen (date); } else { - if ((connection->read_close == MHD_NO) && - ((connection->have_received_headers == MHD_YES) && - (connection->read_buffer_offset == 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; - } - } + size = 2; + kind = MHD_FOOTER_KIND; + off = 0; + } + pos = connection->response->first_header; + while (pos != NULL) + { + if (pos->kind == kind) + size += strlen (pos->header) + strlen (pos->value) + 4; /* colon, space, linefeeds */ + pos = pos->next; + } + /* produce data */ + data = MHD_pool_allocate (connection->pool, size + 1, MHD_YES); + if (data == NULL) + { +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, "Not enough memory for write!\n"); +#endif + return MHD_NO; + } + if (connection->state == MHD_CONNECTION_FOOTERS_RECEIVED) + { + memcpy (data, code, off); } - if ((connection->response != NULL) && - (connection->response_unready == MHD_YES)) + pos = connection->response->first_header; + while (pos != NULL) { - pthread_mutex_lock (&connection->response->mutex); - ready_response (connection); - pthread_mutex_unlock (&connection->response->mutex); + if (pos->kind == kind) + { + SPRINTF (&data[off], "%s: %s\r\n", pos->header, pos->value); + off += strlen (pos->header) + strlen (pos->value) + 4; + } + pos = pos->next; } - if (((connection->response != NULL) && - (connection->response_unready == MHD_NO)) || - MHD_need_100_continue (connection)) + if (connection->state == MHD_CONNECTION_FOOTERS_RECEIVED) { - FD_SET (fd, write_fd_set); - if (fd > *max_fd) - *max_fd = fd; + strcpy (&data[off], date); + off += strlen (date); } + sprintf (&data[off], "\r\n"); + off += 2; + if (off != size) + abort (); + connection->write_buffer = data; + connection->write_buffer_append_offset = size; + connection->write_buffer_send_offset = 0; + connection->write_buffer_size = size + 1; return MHD_YES; } @@ -338,15 +554,14 @@ MHD_connection_get_fdset (struct MHD_Connection *connection, * @param status_code the response code to send (413 or 414) */ static void -MHD_excessive_data_handler (struct MHD_Connection *connection, - unsigned int status_code) +excessive_data_handler (struct MHD_Connection *connection, + unsigned int status_code) { struct MHD_Response *response; /* die, header far too long to be reasonable */ - connection->read_close = MHD_YES; - connection->have_received_headers = MHD_YES; - connection->have_received_body = MHD_YES; + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + connection->read_closed = MHD_YES; #if HAVE_MESSAGES MHD_DLOG (connection->daemon, "Received excessively long header, closing connection.\n"); @@ -354,7 +569,166 @@ MHD_excessive_data_handler (struct MHD_Connection *connection, response = MHD_create_response_from_data (strlen (REQUEST_TOO_BIG), REQUEST_TOO_BIG, MHD_NO, MHD_NO); MHD_queue_response (connection, status_code, response); + EXTRA_CHECK(connection->response != NULL); MHD_destroy_response (response); + if (MHD_NO == build_header_response (connection)) + { + /* oops - close! */ +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Closing connection (failed to create response header)\n"); +#endif + connection->state = MHD_CONNECTION_CLOSED; + } + else + { + connection->state = MHD_CONNECTION_HEADERS_SENDING; + } +} + +/** + * Add "fd" to the "fd_set". If "fd" is + * greater than "*max", set "*max" to fd. + */ +static void +do_fd_set(int fd, + fd_set * set, + int * max_fd) +{ + FD_SET (fd, set); + if (fd > *max_fd) + *max_fd = fd; +} + +/** + * Obtain the select sets for this connection + * + * @return MHD_YES on success + */ +int +MHD_connection_get_fdset (struct MHD_Connection *connection, + fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set, int *max_fd) +{ + int fd; + + if (connection->pool == NULL) + connection->pool = MHD_pool_create (connection->daemon->pool_size); + if (connection->pool == NULL) + { +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, "Failed to create memory pool!\n"); +#endif + connection_close_error (connection); + return MHD_NO; + } + fd = connection->socket_fd; + if (fd == -1) + return MHD_YES; + while (1) { +#if DEBUG_STATES + fprintf(stderr, + "`%s' in state %u\n", + __FUNCTION__, + connection->state); +#endif + switch (connection->state) + { + case MHD_CONNECTION_INIT: + case MHD_CONNECTION_URL_RECEIVED: + case MHD_CONNECTION_HEADER_PART_RECEIVED: + /* while reading headers, we always grow the + read buffer if needed, no size-check required */ + if ( (connection->read_closed) && + (connection->read_buffer_offset == 0) ) + { + connection->state = MHD_CONNECTION_CLOSED; + continue; + } + if ( (connection->read_buffer_offset == connection->read_buffer_size) && + (MHD_NO == try_grow_read_buffer(connection)) ) + { + excessive_data_handler (connection, + (connection->url != NULL) + ? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE + : MHD_HTTP_REQUEST_URI_TOO_LONG); + continue; + } + if (MHD_NO == connection->read_closed) + do_fd_set (fd, read_fd_set, max_fd); + break; + case MHD_CONNECTION_HEADERS_RECEIVED: + /* we should never get here */ + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_HEADERS_PROCESSED: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_CONTINUE_SENDING: + do_fd_set (fd, write_fd_set, max_fd); + break; + case MHD_CONNECTION_CONTINUE_SENT: + if (connection->read_buffer_offset == connection->read_buffer_size) + try_grow_read_buffer(connection); + if (connection->read_buffer_offset < connection->read_buffer_size) + do_fd_set (fd, read_fd_set, max_fd); + break; + case MHD_CONNECTION_BODY_RECEIVED: + case MHD_CONNECTION_FOOTER_PART_RECEIVED: + /* while reading footers, we always grow the + read buffer if needed, no size-check required */ + if (MHD_YES == connection->read_closed) + { + connection->state = MHD_CONNECTION_CLOSED; + continue; + } + do_fd_set (fd, read_fd_set, max_fd); + /* transition to FOOTERS_RECEIVED + happens in read handler */ + break; + case MHD_CONNECTION_FOOTERS_RECEIVED: + /* no socket action, wait for client + to provide response */ + break; + case MHD_CONNECTION_HEADERS_SENDING: + /* headers in buffer, keep writing */ + do_fd_set(fd, write_fd_set, max_fd); + break; + case MHD_CONNECTION_HEADERS_SENT: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_NORMAL_BODY_READY: + do_fd_set (fd, write_fd_set, max_fd); + break; + case MHD_CONNECTION_NORMAL_BODY_UNREADY: + /* not ready, no socket action */ + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + do_fd_set (fd, write_fd_set, max_fd); + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + /* not ready, no socket action */ + break; + case MHD_CONNECTION_BODY_SENT: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_FOOTERS_SENDING: + do_fd_set (fd, write_fd_set, max_fd); + break; + case MHD_CONNECTION_FOOTERS_SENT: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_CLOSED: + if (connection->socket_fd != -1) + connection_close_error(connection); + return MHD_YES; /* do nothing, not even reading */ + default: + EXTRA_CHECK(0); + } + break; + } + return MHD_YES; } /** @@ -366,7 +740,7 @@ MHD_excessive_data_handler (struct MHD_Connection *connection, * return NULL. Otherwise return a pointer to the line. */ static char * -MHD_get_next_header_line (struct MHD_Connection *connection) +get_next_header_line (struct MHD_Connection *connection) { char *rbuf; size_t pos; @@ -390,10 +764,10 @@ MHD_get_next_header_line (struct MHD_Connection *connection) MHD_BUF_INC_SIZE); if (rbuf == NULL) { - MHD_excessive_data_handler (connection, - (connection->url != NULL) - ? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE - : MHD_HTTP_REQUEST_URI_TOO_LONG); + excessive_data_handler (connection, + (connection->url != NULL) + ? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE + : MHD_HTTP_REQUEST_URI_TOO_LONG); } else { @@ -418,8 +792,8 @@ MHD_get_next_header_line (struct MHD_Connection *connection) * @return MHD_NO on failure (out of memory), MHD_YES for success */ static int -MHD_connection_add_header (struct MHD_Connection *connection, - char *key, char *value, enum MHD_ValueKind kind) +connection_add_header (struct MHD_Connection *connection, + char *key, char *value, enum MHD_ValueKind kind) { struct MHD_HTTP_Header *hdr; @@ -431,8 +805,8 @@ MHD_connection_add_header (struct MHD_Connection *connection, MHD_DLOG (connection->daemon, "Not enough memory to allocate header record!\n"); #endif - MHD_excessive_data_handler (connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); + excessive_data_handler (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); return MHD_NO; } hdr->next = connection->headers_received; @@ -468,8 +842,8 @@ parse_arguments (enum MHD_ValueKind kind, } MHD_http_unescape (args); MHD_http_unescape (equals); - if (MHD_NO == MHD_connection_add_header (connection, - args, equals, kind)) + if (MHD_NO == connection_add_header (connection, + args, equals, kind)) return MHD_NO; args = amper; } @@ -482,7 +856,7 @@ parse_arguments (enum MHD_ValueKind kind, * @return MHD_YES for success, MHD_NO for failure (malformed, out of memory) */ static int -MHD_parse_cookie_header (struct MHD_Connection *connection) +parse_cookie_header (struct MHD_Connection *connection) { const char *hdr; char *cpy; @@ -500,8 +874,8 @@ MHD_parse_cookie_header (struct MHD_Connection *connection) #if HAVE_MESSAGES MHD_DLOG (connection->daemon, "Not enough memory to parse cookies!\n"); #endif - MHD_excessive_data_handler (connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); + excessive_data_handler (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); return MHD_NO; } memcpy (cpy, hdr, strlen (hdr) + 1); @@ -536,8 +910,8 @@ MHD_parse_cookie_header (struct MHD_Connection *connection) equals[strlen (equals) - 1] = '\0'; equals++; } - if (MHD_NO == MHD_connection_add_header (connection, - pos, equals, MHD_COOKIE_KIND)) + if (MHD_NO == connection_add_header (connection, + pos, equals, MHD_COOKIE_KIND)) return MHD_NO; pos = semicolon; } @@ -587,235 +961,29 @@ parse_initial_message_line (struct MHD_Connection *connection, char *line) return MHD_YES; } - -/** - * This function is designed to parse the input buffer of a given - * connection for HTTP headers -- and in the case of chunked encoding, - * also for HTTP "footers". - * - * Once the header is complete, it should have set the - * headers_received, url and method values and set - * have_received_headers to MHD_YES. If no body is expected, it - * should also set "have_received_body" to MHD_YES. Otherwise, it - * should set "remaining_upload_size" to the expected size of the - * body. If the size of the body is unknown, it should be set to -1. - */ -static void -MHD_parse_connection_headers (struct MHD_Connection *connection) -{ - char *last; - char *line; - char *colon; - char *tmp; - const char *clen; - unsigned long long cval; - struct MHD_Response *response; - - if (((connection->have_received_body == MHD_YES) && - (connection->have_chunked_upload == MHD_NO)) || - (connection->have_received_headers == MHD_YES)) - abort (); - colon = NULL; /* make gcc happy */ - last = NULL; - while (NULL != (line = MHD_get_next_header_line (connection))) - { - if (last != NULL) - { - if ((line[0] == ' ') || (line[0] == '\t')) - { - /* value was continued on the next line, see - http://www.jmarshall.com/easy/http/ */ - last = MHD_pool_reallocate (connection->pool, - last, - strlen (last) + 1, - strlen (line) + strlen (last) + 1); - if (last == NULL) - { - MHD_excessive_data_handler (connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); - break; - } - tmp = line; - while ((tmp[0] == ' ') || (tmp[0] == '\t')) - tmp++; /* skip whitespace at start of 2nd line */ - strcat (last, tmp); - continue; /* possibly more than 2 lines... */ - } - else - { - 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)) - goto DIE; - continue; - } - /* check if this is the end of the header */ - if (strlen (line) == 0) - { - /* end of header */ - connection->have_received_headers = MHD_YES; - clen = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_CONTENT_LENGTH); - if (clen != NULL) - { - if (1 != sscanf (clen, "%llu", &cval)) - { -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Failed to parse `%s' header `%s', closing connection.\n", - MHD_HTTP_HEADER_CONTENT_LENGTH, clen); -#endif - goto DIE; - } - connection->remaining_upload_size = cval; - connection->have_received_body = cval == 0 ? MHD_YES : MHD_NO; - } - else - { - if (NULL == MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_TRANSFER_ENCODING)) - { - /* this request does not have a body */ - connection->remaining_upload_size = 0; - connection->have_received_body = MHD_YES; - } - else - { - if (connection->have_chunked_upload == MHD_NO) - { - connection->remaining_upload_size = -1; /* unknown size */ - if (0 == - strcasecmp (MHD_lookup_connection_value - (connection, MHD_HEADER_KIND, - MHD_HTTP_HEADER_TRANSFER_ENCODING), - "chunked")) - connection->have_chunked_upload = MHD_YES; - } - else - { - /* we were actually processing the footers at the - END of a chunked encoding; give connection - handler an extra chance... */ - connection->have_chunked_upload = MHD_NO; /* no more! */ - MHD_call_connection_handler (connection); - } - } - } - if ((0 != (MHD_USE_PEDANTIC_CHECKS & connection->daemon->options)) - && (NULL != connection->version) - && (0 == strcasecmp (MHD_HTTP_VERSION_1_1, connection->version)) - && (NULL == - MHD_lookup_connection_value (connection, MHD_HEADER_KIND, - MHD_HTTP_HEADER_HOST))) - { - /* die, http 1.1 request without host and we are pedantic */ - connection->have_received_body = MHD_YES; - connection->read_close = MHD_YES; -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Received `%s' request without `%s' header.\n", - MHD_HTTP_VERSION_1_1, MHD_HTTP_HEADER_HOST); -#endif - response = - MHD_create_response_from_data (strlen (REQUEST_LACKS_HOST), - REQUEST_LACKS_HOST, MHD_NO, - MHD_NO); - MHD_queue_response (connection, MHD_HTTP_BAD_REQUEST, response); - MHD_destroy_response (response); - } - break; - } - /* line should be normal header line, find colon */ - colon = strstr (line, ":"); - if (colon == NULL) - { - /* error in header line, die hard */ -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Received malformed line (no colon), closing connection.\n"); -#endif - goto DIE; - } - /* zero-terminate header */ - colon[0] = '\0'; - colon++; /* advance to value */ - while ((colon[0] != '\0') && ((colon[0] == ' ') || (colon[0] == '\t'))) - colon++; - /* we do the actual adding of the connection - header at the beginning of the while - loop since we need to be able to inspect - the *next* header line (in case it starts - with a space...) */ - last = line; - } - if ((last != NULL) && - (MHD_NO == MHD_connection_add_header (connection, - last, colon, MHD_HEADER_KIND))) - return; /* error */ - MHD_parse_cookie_header (connection); - return; -DIE: -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Closing connection (problem parsing headers)\n"); -#endif - connection_close_error (connection); -} - - -/** - * Find the handler responsible for this request. - */ -static struct MHD_Access_Handler * -MHD_find_access_handler (struct MHD_Connection *connection) -{ - struct MHD_Access_Handler *pos; - - pos = connection->daemon->handlers; - while (pos != NULL) - { - if (0 == strcmp (connection->url, pos->uri_prefix)) - return pos; - pos = pos->next; - } - return &connection->daemon->default_handler; -} - - /** * Call the handler of the application for this - * connection. + * connection. Handles chunking of the upload + * as well as normal uploads. */ -void -MHD_call_connection_handler (struct MHD_Connection *connection) +static void +call_connection_handler (struct MHD_Connection *connection) { - struct MHD_Access_Handler *ah; unsigned int processed; unsigned int available; unsigned int used; int instant_retry; unsigned int i; + int malformed; if (connection->response != NULL) - return; /* already queued a response */ - if (connection->have_received_headers == MHD_NO) - abort (); /* bad timing... */ + return; /* already queued a response */ do { instant_retry = MHD_NO; available = connection->read_buffer_offset; - if (connection->have_chunked_upload == MHD_YES) + if ( (connection->have_chunked_upload == MHD_YES) && + (connection->remaining_upload_size == -1) ) { if ((connection->current_chunk_offset == connection->current_chunk_size) @@ -876,18 +1044,17 @@ MHD_call_connection_handler (struct MHD_Connection *connection) } if (i >= available) return; /* need more data... */ - /* The following if-statement is a bit crazy -- we - use the second clause only for the side-effect, - 0-terminating the buffer for the following sscanf - attempts; yes, there should be only a single - "="-sign (assignment!) in the read_buffer[i]-line. */ - if ((i >= 6) || - ((connection->read_buffer[i] = '\0')) || - ((1 != sscanf (connection->read_buffer, - "%X", - &connection->current_chunk_size)) && - (1 != sscanf (connection->read_buffer, - "%x", &connection->current_chunk_size)))) + malformed = (i >= 6); + if (! malformed) + { + connection->read_buffer[i] = '\0'; + malformed = (1 != sscanf (connection->read_buffer, + "%X", + &connection->current_chunk_size)) && + (1 != sscanf (connection->read_buffer, + "%x", &connection->current_chunk_size)); + } + if (malformed) { /* malformed encoding */ #if HAVE_MESSAGES @@ -908,11 +1075,7 @@ MHD_call_connection_handler (struct MHD_Connection *connection) instant_retry = MHD_YES; if (connection->current_chunk_size == 0) { - /* we're back to reading HEADERS (footers!) */ - connection->have_received_body = MHD_YES; connection->remaining_upload_size = 0; - connection->have_received_headers = MHD_NO; - MHD_parse_connection_headers (connection); return; } continue; @@ -925,8 +1088,7 @@ MHD_call_connection_handler (struct MHD_Connection *connection) available = 0; } used = processed; - ah = MHD_find_access_handler (connection); - if (MHD_NO == ah->dh (ah->dh_cls, + if (MHD_NO == connection->daemon->default_handler (connection->daemon->default_handler_cls, connection, connection->url, connection->method, @@ -942,6 +1104,8 @@ MHD_call_connection_handler (struct MHD_Connection *connection) connection_close_error (connection); return; } + if (processed > used) + abort(); /* fatal client API violation! */ if (processed != 0) instant_retry = MHD_NO; /* client did not process everything */ used -= processed; @@ -953,79 +1117,26 @@ MHD_call_connection_handler (struct MHD_Connection *connection) &connection->read_buffer[used], processed + available); if (connection->remaining_upload_size != -1) connection->remaining_upload_size -= used; - connection->read_buffer_offset = processed + available; - if ((connection->remaining_upload_size == 0) || - ((connection->read_buffer_offset == 0) && - (connection->remaining_upload_size == -1) - && (connection->socket_fd == -1))) - { - connection->have_received_body = MHD_YES; - if (connection->read_buffer != NULL) - MHD_pool_reallocate (connection->pool, - connection->read_buffer, - (connection->read_buffer == - NULL) ? 0 : connection->read_buffer_size + - 1, 0); - connection->read_buffer_offset = 0; - connection->read_buffer_size = 0; - connection->read_buffer = NULL; - } + connection->read_buffer_offset = processed + available; } while (instant_retry == MHD_YES); } /** - * This function handles a particular connection when it has been - * determined that there is data to be read off a socket. All implementations - * (multithreaded, external select, internal select) call this function - * to handle reads. + * Try reading data from the socket into the + * read buffer of the connection. + * + * @return MHD_YES if something changed, + * MHD_NO if we were interrupted or if + * no space was available */ -int -MHD_connection_handle_read (struct MHD_Connection *connection) +static int +do_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) - { -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, "Failed to create memory pool!\n"); -#endif - connection_close_error (connection); - return MHD_NO; - } - if ((connection->read_buffer_offset >= connection->read_buffer_size) && - (connection->have_received_headers == MHD_NO)) - { - /* need to grow read buffer */ - tmp = MHD_pool_reallocate (connection->pool, - connection->read_buffer, - connection->read_buffer_size, - connection->read_buffer_size * 2 + - MHD_BUF_INC_SIZE + 1); - if (tmp == NULL) - { -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Not enough memory for reading headers!\n"); -#endif - MHD_excessive_data_handler (connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); - return MHD_NO; - } - connection->read_buffer = tmp; - connection->read_buffer_size = - connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE; - } - if (connection->read_buffer_offset >= connection->read_buffer_size) - { -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, "Unexpected call to %s.\n", __FUNCTION__); -#endif - return MHD_NO; - } + + if (connection->read_buffer_size == connection->read_buffer_offset) + return MHD_NO; bytes_read = RECV (connection->socket_fd, &connection->read_buffer[connection->read_buffer_offset], connection->read_buffer_size - @@ -1044,150 +1155,297 @@ MHD_connection_handle_read (struct MHD_Connection *connection) if (bytes_read == 0) { /* other side closed connection */ - connection->read_close = MHD_YES; - if ((connection->have_received_headers == MHD_YES) - && (connection->read_buffer_offset > 0)) - MHD_call_connection_handler (connection); -#if DEBUG_CLOSE -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Shutting down connection for reading (other side closed connection)\n"); -#endif -#endif - shutdown (connection->socket_fd, SHUT_RD); - if ((connection->have_received_headers == MHD_NO) || - (connection->have_received_body == MHD_NO)) - { - /* no request => no response! */ - CLOSE (connection->socket_fd); - connection->socket_fd = -1; - } - return MHD_YES; + connection->read_closed = MHD_YES; + return MHD_NO; } connection->read_buffer_offset += bytes_read; - if (connection->have_received_headers == MHD_NO) - MHD_parse_connection_headers (connection); - if ((connection->have_received_headers == MHD_YES) - && (connection->method != NULL)) - MHD_call_connection_handler (connection); return MHD_YES; } /** - * Check if we need to set some additional headers - * for http-compiliance. + * We have received (possibly the beginning of) a line in the + * header (or footer). Validate (check for ":") and prepare + * to process. */ static void -MHD_add_extra_headers (struct MHD_Connection *connection) +process_header_line(struct MHD_Connection * connection, + char * line) { - const char *have; - char buf[128]; + char *colon; - if (connection->response->total_size == -1) + /* line should be normal header line, find colon */ + colon = strstr (line, ":"); + if (colon == NULL) { - have = MHD_get_response_header (connection->response, - MHD_HTTP_HEADER_CONNECTION); - if (have == NULL) - MHD_add_response_header (connection->response, - MHD_HTTP_HEADER_CONNECTION, "close"); - } - 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); - MHD_add_response_header (connection->response, - MHD_HTTP_HEADER_CONTENT_LENGTH, buf); + /* error in header line, die hard */ +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Received malformed line (no colon), closing connection.\n"); +#endif + connection->state = MHD_CONNECTION_CLOSED; + return; } + /* zero-terminate header */ + colon[0] = '\0'; + colon++; /* advance to value */ + while ((colon[0] != '\0') && ((colon[0] == ' ') || (colon[0] == '\t'))) + colon++; + /* we do the actual adding of the connection + header at the beginning of the while + loop since we need to be able to inspect + the *next* header line (in case it starts + with a space...) */ + connection->last = line; + connection->colon = colon; } +/** + * Process a header value that spans multiple lines. + * The previous line(s) are in connection->last. + * + * @param line the current input line + * @param kind if the line is complete, add a header + * of the given kind + */ static void -get_date_string (char *date, unsigned int max) +process_broken_line(struct MHD_Connection * connection, + char * line, + enum MHD_ValueKind kind) { - static const char *days[] = - { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - static const char *mons[] = - { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", - "Nov", "Dec" - }; - struct tm now; - time_t t; + char * last; + char * tmp; - time (&t); - gmtime_r (&t, &now); - snprintf (date, - max - 1, - "Date: %3s, %02u %3s %04u %02u:%02u:%02u GMT\r\n", - days[now.tm_wday % 7], - now.tm_mday, - mons[now.tm_mon % 12], - now.tm_year, now.tm_hour, now.tm_min, now.tm_sec); + last = connection->last; + if ((line[0] == ' ') || (line[0] == '\t')) + { + /* value was continued on the next line, see + http://www.jmarshall.com/easy/http/ */ + last = MHD_pool_reallocate (connection->pool, + last, + strlen (last) + 1, + strlen (line) + strlen (last) + 1); + if (last == NULL) + { + excessive_data_handler (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); + return; + } + tmp = line; + while ((tmp[0] == ' ') || (tmp[0] == '\t')) + tmp++; /* skip whitespace at start of 2nd line */ + strcat (last, tmp); + connection->last = last; + return; /* possibly more than 2 lines... */ + } + if (MHD_NO == connection_add_header (connection, + last, + connection->colon, + kind)) + { + excessive_data_handler (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE); + return; + } + /* we still have the current line to deal with... */ + if (strlen(line) != 0) + process_header_line(connection, + line); } /** - * Allocate the connection's write buffer and - * fill it with all of the headers from the - * HTTPd's response. + * Parse the various headers; figure out the size + * of the upload and make sure the headers follow + * the protocol. Advance to the appropriate state. */ -static int -MHD_build_header_response (struct MHD_Connection *connection) +static void +parse_connection_headers(struct MHD_Connection * connection) { - size_t size; - size_t off; - struct MHD_HTTP_Header *pos; - char code[128]; - char date[128]; - char *data; + const char *clen; + unsigned long long cval; + struct MHD_Response *response; - MHD_add_extra_headers (connection); - const char *reason_phrase = - MHD_get_reason_phrase_for (connection->responseCode); - _REAL_SNPRINTF (code, 128, "%s %u %s\r\n", MHD_HTTP_VERSION_1_1, - connection->responseCode, reason_phrase); - off = strlen (code); - /* estimate size */ - size = off + 2; /* extra \r\n at the end */ - pos = connection->response->first_header; - while (pos != NULL) + parse_cookie_header (connection); + if ((0 != (MHD_USE_PEDANTIC_CHECKS & connection->daemon->options)) + && (NULL != connection->version) + && (0 == strcasecmp (MHD_HTTP_VERSION_1_1, connection->version)) + && (NULL == + MHD_lookup_connection_value (connection, MHD_HEADER_KIND, + MHD_HTTP_HEADER_HOST))) { - size += strlen (pos->header) + strlen (pos->value) + 4; /* colon, space, linefeeds */ - pos = pos->next; + /* die, http 1.1 request without host and we are pedantic */ + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + connection->read_closed = MHD_YES; +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Received `%s' request without `%s' header.\n", + MHD_HTTP_VERSION_1_1, MHD_HTTP_HEADER_HOST); +#endif + response = + MHD_create_response_from_data (strlen (REQUEST_LACKS_HOST), + REQUEST_LACKS_HOST, MHD_NO, + MHD_NO); + MHD_queue_response (connection, MHD_HTTP_BAD_REQUEST, response); + MHD_destroy_response (response); + return; } - if (NULL == MHD_get_response_header (connection->response, - MHD_HTTP_HEADER_DATE)) - get_date_string (date, sizeof (date)); - else - date[0] = '\0'; - size += strlen (date); - /* produce data */ - data = MHD_pool_allocate (connection->pool, size + 1, MHD_YES); - if (data == NULL) + + clen = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_LENGTH); + if (clen != NULL) { + if (1 != sscanf (clen, "%llu", &cval)) + { #if HAVE_MESSAGES - MHD_DLOG (connection->daemon, "Not enough memory for write!\n"); + MHD_DLOG (connection->daemon, + "Failed to parse `%s' header `%s', closing connection.\n", + MHD_HTTP_HEADER_CONTENT_LENGTH, clen); #endif - return MHD_NO; + connection->state = MHD_CONNECTION_CLOSED; + return; + } + connection->remaining_upload_size = cval; } - memcpy (data, code, off); - pos = connection->response->first_header; - while (pos != NULL) + else { - SPRINTF (&data[off], "%s: %s\r\n", pos->header, pos->value); - off += strlen (pos->header) + strlen (pos->value) + 4; - pos = pos->next; + if (NULL == MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_TRANSFER_ENCODING)) + { + /* this request does not have a body */ + connection->remaining_upload_size = 0; + } + else + { + connection->remaining_upload_size = -1; /* unknown size */ + if (0 == + strcasecmp (MHD_lookup_connection_value + (connection, MHD_HEADER_KIND, + MHD_HTTP_HEADER_TRANSFER_ENCODING), + "chunked")) + connection->have_chunked_upload = MHD_YES; + } + } +} + +/** + * This function handles a particular connection when it has been + * determined that there is data to be read off a socket. All + * implementations (multithreaded, external select, internal select) + * call this function to handle reads. + * + * @return MHD_YES if we should continue to process the + * connection (not dead yet), MHD_NO if it died + */ +int +MHD_connection_handle_read (struct MHD_Connection *connection) +{ + connection->last_activity = time(NULL); + if (connection->state == MHD_CONNECTION_CLOSED) + return MHD_NO; + if (MHD_NO == do_read(connection)) + return MHD_YES; + while (1) { +#if DEBUG_STATES + fprintf(stderr, + "`%s' in state %u\n", + __FUNCTION__, + connection->state); +#endif + switch (connection->state) + { + case MHD_CONNECTION_INIT: + case MHD_CONNECTION_URL_RECEIVED: + case MHD_CONNECTION_HEADER_PART_RECEIVED: + case MHD_CONNECTION_HEADERS_RECEIVED: + case MHD_CONNECTION_HEADERS_PROCESSED: + case MHD_CONNECTION_CONTINUE_SENDING: + case MHD_CONNECTION_CONTINUE_SENT: + case MHD_CONNECTION_BODY_RECEIVED: + case MHD_CONNECTION_FOOTER_PART_RECEIVED: + /* nothing to do but default action */ + if (MHD_YES == connection->read_closed) + { + connection->state = MHD_CONNECTION_CLOSED; + continue; + } + break; + case MHD_CONNECTION_CLOSED: + if (connection->socket_fd != -1) + connection_close_error (connection); + return MHD_NO; + default: + /* shrink read buffer to how much is actually used */ + MHD_pool_reallocate (connection->pool, + connection->read_buffer, + connection->read_buffer_size + 1, + connection->read_buffer_offset); + break; + } + break; + } + return MHD_YES; +} + +/** + * Try writing data to the socket from the + * write buffer of the connection. + * + * @return MHD_YES if something changed, + * MHD_NO if we were interrupted + */ +static int +do_write(struct MHD_Connection * connection) +{ + int ret; + + ret = SEND (connection->socket_fd, + &connection->write_buffer[connection-> + write_buffer_send_offset], + connection->write_buffer_append_offset - + connection->write_buffer_send_offset, MSG_NOSIGNAL); + if (ret < 0) + { + if (errno == EINTR) + return MHD_NO; +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Failed to send data: %s\n", STRERROR (errno)); +#endif + connection_close_error (connection); + return MHD_YES; } - strcpy (&data[off], date); - off += strlen (date); - sprintf (&data[off], "\r\n"); - off += 2; - if (off != size) - abort (); - connection->write_buffer = data; - connection->write_buffer_append_offset = size; +#if DEBUG_SEND_DATA + fprintf (stderr, + "Sent HEADER response: `%.*s'\n", + ret, + &connection->write_buffer[connection-> + write_buffer_send_offset]); +#endif + connection->write_buffer_send_offset += ret; + return MHD_YES; +} + +/** + * Check if we are done sending the write-buffer. + * If so, transition into "next_state". + * @return MHY_NO if we are not done, MHD_YES if we are + */ +static int +check_write_done(struct MHD_Connection * connection, + enum MHD_CONNECTION_STATE next_state) +{ + if (connection->write_buffer_append_offset != + connection->write_buffer_send_offset) + return MHD_NO; + connection->write_buffer_append_offset = 0; connection->write_buffer_send_offset = 0; - connection->write_buffer_size = size + 1; + connection->state = next_state; + MHD_pool_reallocate (connection->pool, + connection->write_buffer, + connection->write_buffer_size, 0); + connection->write_buffer = NULL; + connection->write_buffer_size = 0; return MHD_YES; } @@ -1196,207 +1454,414 @@ MHD_build_header_response (struct MHD_Connection *connection) * been determined that the socket can be written to. All * implementations (multithreaded, external select, internal select) * call this function + * + * @return MHD_YES if we should continue to process the + * connection (not dead yet), MHD_NO if it died */ int MHD_connection_handle_write (struct MHD_Connection *connection) { struct MHD_Response *response; int ret; - const char *end; - if (MHD_need_100_continue (connection)) - { - ret = SEND (connection->socket_fd, - &HTTP_100_CONTINUE[connection-> - continue_message_write_offset], - strlen (HTTP_100_CONTINUE) - - connection->continue_message_write_offset, MSG_NOSIGNAL); - if (ret < 0) - { - if (errno == EINTR) - return MHD_YES; + connection->last_activity = time(NULL); + while (1) { +#if DEBUG_STATES + fprintf(stderr, + "`%s' in state %u\n", + __FUNCTION__, + connection->state); +#endif + switch (connection->state) + { + case MHD_CONNECTION_INIT: + case MHD_CONNECTION_URL_RECEIVED: + case MHD_CONNECTION_HEADER_PART_RECEIVED: + case MHD_CONNECTION_HEADERS_RECEIVED: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_HEADERS_PROCESSED: + break; + case MHD_CONNECTION_CONTINUE_SENDING: + ret = SEND (connection->socket_fd, + &HTTP_100_CONTINUE[connection-> + continue_message_write_offset], + strlen (HTTP_100_CONTINUE) - + connection->continue_message_write_offset, MSG_NOSIGNAL); + if (ret < 0) + { + if (errno == EINTR) + break; #if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Failed to send data: %s\n", STRERROR (errno)); + MHD_DLOG (connection->daemon, + "Failed to send data: %s\n", STRERROR (errno)); #endif - connection_close_error (connection); - return MHD_YES; - } + connection_close_error (connection); + return MHD_NO; + } #if DEBUG_SEND_DATA - fprintf (stderr, - "Sent 100 continue response: `%.*s'\n", - ret, - &HTTP_100_CONTINUE[connection->continue_message_write_offset]); + fprintf (stderr, + "Sent 100 continue response: `%.*s'\n", + ret, + &HTTP_100_CONTINUE[connection->continue_message_write_offset]); #endif - connection->continue_message_write_offset += ret; - return MHD_YES; - } - response = connection->response; - if (response == NULL) - { -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, "Unexpected call to %s.\n", __FUNCTION__); -#endif - return MHD_NO; - } - if (MHD_NO == connection->have_sent_headers) - { - if ((connection->write_buffer == NULL) && - (MHD_NO == MHD_build_header_response (connection))) - { - /* oops - close! */ + connection->continue_message_write_offset += ret; + break; + case MHD_CONNECTION_CONTINUE_SENT: + case MHD_CONNECTION_BODY_RECEIVED: + case MHD_CONNECTION_FOOTER_PART_RECEIVED: + case MHD_CONNECTION_FOOTERS_RECEIVED: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_HEADERS_SENDING: + do_write(connection); + check_write_done(connection, + MHD_CONNECTION_HEADERS_SENT); + break; + case MHD_CONNECTION_HEADERS_SENT: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_NORMAL_BODY_READY: + response = connection->response; + if (response->crc != NULL) + pthread_mutex_lock (&response->mutex); + if (MHD_YES != try_ready_normal_body(connection)) + { + if (response->crc != NULL) + pthread_mutex_unlock (&response->mutex); + connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY; + break; + } + ret = SEND (connection->socket_fd, + &response->data[connection->response_write_position - + response->data_start], + response->data_size - (connection->response_write_position - + response->data_start), MSG_NOSIGNAL); +#if DEBUG_SEND_DATA + if (ret > 0) + fprintf (stderr, + "Sent DATA response: `%.*s'\n", + ret, + &response->data[connection->response_write_position - + response->data_start]); +#endif + if (response->crc != NULL) + pthread_mutex_unlock (&response->mutex); + if (ret < 0) + { + if (errno == EINTR) + return MHD_YES; #if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Closing connection (failed to create response header)\n"); + MHD_DLOG (connection->daemon, + "Failed to send data: %s\n", STRERROR (errno)); #endif - connection_close_error (connection); - return MHD_NO; - } - ret = SEND (connection->socket_fd, - &connection->write_buffer[connection-> - write_buffer_send_offset], - connection->write_buffer_append_offset - - connection->write_buffer_send_offset, MSG_NOSIGNAL); - if (ret < 0) - { - if (errno == EINTR) - return MHD_YES; + connection_close_error (connection); + return MHD_NO; + } + connection->response_write_position += ret; + if (connection->response_write_position == connection->response->total_size) + connection->state = MHD_CONNECTION_FOOTERS_SENT; /* have no footers... */ + break; + case MHD_CONNECTION_NORMAL_BODY_UNREADY: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + do_write(connection); + check_write_done(connection, + (connection->response->total_size == connection->response_write_position) + ? MHD_CONNECTION_BODY_SENT + : MHD_CONNECTION_CHUNKED_BODY_UNREADY); + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + case MHD_CONNECTION_BODY_SENT: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_FOOTERS_SENDING: + do_write(connection); + check_write_done(connection, + MHD_CONNECTION_FOOTERS_SENT); + break; + case MHD_CONNECTION_FOOTERS_SENT: + EXTRA_CHECK(0); + break; + case MHD_CONNECTION_CLOSED: + if (connection->socket_fd != -1) + connection_close_error(connection); + return MHD_NO; + } + break; + } + return MHD_YES; +} + +/** + * This function was created to handle per-connection processing that + * has to happen even if the socket cannot be read or written to. All + * implementations (multithreaded, external select, internal select) + * call this function. + * + * @return MHD_YES if we should continue to process the + * connection (not dead yet), MHD_NO if it died + */ +int +MHD_connection_handle_idle (struct MHD_Connection *connection) +{ + unsigned int timeout; + const char * end; + char * line; + + while (1) { +#if DEBUG_STATES + fprintf(stderr, + "`%s' in state %u\n", + __FUNCTION__, + connection->state); +#endif + switch (connection->state) + { + case MHD_CONNECTION_INIT: + line = get_next_header_line(connection); + if (line == NULL) + break; + if (MHD_NO == parse_initial_message_line (connection, line)) + connection->state = MHD_CONNECTION_CLOSED; + else + connection->state = MHD_CONNECTION_URL_RECEIVED; + continue; + case MHD_CONNECTION_URL_RECEIVED: + line = get_next_header_line(connection); + if (line == NULL) + break; + if (strlen(line) == 0) + { + connection->state = MHD_CONNECTION_HEADERS_RECEIVED; + continue; + } + process_header_line(connection, line); + connection->state = MHD_CONNECTION_HEADER_PART_RECEIVED; + continue; + case MHD_CONNECTION_HEADER_PART_RECEIVED: + line = get_next_header_line(connection); + if (line == NULL) + break; + process_broken_line(connection, + line, + MHD_HEADER_KIND); + if (strlen(line) == 0) + { + connection->state = MHD_CONNECTION_HEADERS_RECEIVED; + continue; + } + continue; + case MHD_CONNECTION_HEADERS_RECEIVED: + parse_connection_headers (connection); + if (connection->state == MHD_CONNECTION_CLOSED) + continue; + connection->state = MHD_CONNECTION_HEADERS_PROCESSED; + continue; + case MHD_CONNECTION_HEADERS_PROCESSED: + call_connection_handler (connection); /* first call */ + if (need_100_continue(connection)) + { + connection->state = MHD_CONNECTION_CONTINUE_SENDING; + break; + } + connection->state = (connection->remaining_upload_size == 0) + ? MHD_CONNECTION_FOOTERS_RECEIVED + : MHD_CONNECTION_CONTINUE_SENT; + continue; + case MHD_CONNECTION_CONTINUE_SENDING: + if (connection->continue_message_write_offset == + strlen (HTTP_100_CONTINUE)) + { + connection->state = MHD_CONNECTION_CONTINUE_SENT; + continue; + } + break; + case MHD_CONNECTION_CONTINUE_SENT: + if (connection->read_buffer_offset != 0) { + call_connection_handler(connection); /* loop call */ + if (connection->state == MHD_CONNECTION_CLOSED) + continue; + } + if ( (connection->remaining_upload_size == 0) || + ( (connection->remaining_upload_size == -1) && + (connection->read_buffer_offset == 0) && + (MHD_YES == connection->read_closed) ) ) + { + if ( (MHD_YES == connection->have_chunked_upload) && + (MHD_NO == connection->read_closed) ) + connection->state = MHD_CONNECTION_BODY_RECEIVED; + else + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + continue; + } + break; + case MHD_CONNECTION_BODY_RECEIVED: + line = get_next_header_line(connection); + if (line == NULL) + break; + if (strlen(line) == 0) + { + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + continue; + } + process_header_line(connection, line); + connection->state = MHD_CONNECTION_FOOTER_PART_RECEIVED; + continue; + case MHD_CONNECTION_FOOTER_PART_RECEIVED: + line = get_next_header_line(connection); + if (line == NULL) + break; + process_broken_line(connection, + line, + MHD_FOOTER_KIND); + if (strlen(line) == 0) + { + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + continue; + } + continue; + case MHD_CONNECTION_FOOTERS_RECEIVED: + call_connection_handler (connection); /* "final" call */ + if (connection->state == MHD_CONNECTION_CLOSED) + continue; + if (connection->response == NULL) + break; /* try again next time */ + if (MHD_NO == build_header_response (connection)) + { + /* oops - close! */ #if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Failed to send data: %s\n", STRERROR (errno)); + MHD_DLOG (connection->daemon, + "Closing connection (failed to create response header)\n"); #endif - connection_close_error (connection); - return MHD_YES; - } -#if DEBUG_SEND_DATA - fprintf (stderr, - "Sent HEADER response: `%.*s'\n", - ret, - &connection->write_buffer[connection-> - write_buffer_send_offset]); -#endif - connection->write_buffer_send_offset += ret; - if (connection->write_buffer_append_offset == - connection->write_buffer_send_offset) - { - connection->write_buffer_append_offset = 0; - connection->write_buffer_send_offset = 0; - connection->have_sent_headers = MHD_YES; - MHD_pool_reallocate (connection->pool, - connection->write_buffer, - connection->write_buffer_size, 0); - connection->write_buffer = NULL; - connection->write_buffer_size = 0; - } - return MHD_YES; - } - if (response->total_size < connection->response_write_position) - abort (); /* internal error */ - if (response->crc != NULL) - pthread_mutex_lock (&response->mutex); - - /* prepare send buffer */ - if ((response->crc != NULL) && - ((response->data_start > connection->response_write_position) || - (response->data_start + response->data_size <= - connection->response_write_position)) - && (MHD_YES != ready_response (connection))) - { - pthread_mutex_unlock (&response->mutex); - return MHD_YES; - } - /* transmit */ - ret = SEND (connection->socket_fd, - &response->data[connection->response_write_position - - response->data_start], - response->data_size - (connection->response_write_position - - response->data_start), MSG_NOSIGNAL); - if (response->crc != NULL) - pthread_mutex_unlock (&response->mutex); - if (ret < 0) + connection->state = MHD_CONNECTION_CLOSED; + continue; + } + connection->state = MHD_CONNECTION_HEADERS_SENDING; + break; + case MHD_CONNECTION_HEADERS_SENDING: + /* no default action */ + break; + case MHD_CONNECTION_HEADERS_SENT: + if (connection->have_chunked_upload) + connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY; + else + connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY; + continue; + case MHD_CONNECTION_NORMAL_BODY_READY: + /* nothing to do here */ + break; + case MHD_CONNECTION_NORMAL_BODY_UNREADY: + if (connection->response->crc != NULL) + pthread_mutex_lock (&connection->response->mutex); + if (MHD_YES == try_ready_normal_body(connection)) + { + if (connection->response->crc != NULL) + pthread_mutex_unlock (&connection->response->mutex); + connection->state = MHD_CONNECTION_NORMAL_BODY_READY; + break; + } + if (connection->response->crc != NULL) + pthread_mutex_unlock (&connection->response->mutex); + /* not ready, no socket action */ + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + /* nothing to do here */ + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + if (connection->response->crc != NULL) + pthread_mutex_lock (&connection->response->mutex); + if (MHD_YES == try_ready_chunked_body(connection)) + { + if (connection->response->crc != NULL) + pthread_mutex_unlock (&connection->response->mutex); + connection->state = MHD_CONNECTION_CHUNKED_BODY_READY; + continue; + } + if (connection->response->crc != NULL) + pthread_mutex_unlock (&connection->response->mutex); + break; + case MHD_CONNECTION_BODY_SENT: + build_header_response(connection); + if (connection->write_buffer_send_offset == connection->write_buffer_append_offset) + connection->state = MHD_CONNECTION_FOOTERS_SENT; + else + connection->state = MHD_CONNECTION_FOOTERS_SENDING; + continue; + case MHD_CONNECTION_FOOTERS_SENDING: + /* no default action */ + break; + case MHD_CONNECTION_FOOTERS_SENT: + MHD_destroy_response (connection->response); + if (connection->daemon->notify_completed != NULL) + connection->daemon->notify_completed (connection->daemon-> + notify_completed_cls, + connection, + &connection->client_context, + MHD_REQUEST_TERMINATED_COMPLETED_OK); + end = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONNECTION); + connection->client_context = NULL; + connection->continue_message_write_offset = 0; + connection->responseCode = 0; + connection->response = NULL; + connection->headers_received = NULL; + connection->response_write_position = 0; + connection->have_chunked_upload = MHD_NO; + connection->method = NULL; + connection->url = NULL; + connection->write_buffer = NULL; + connection->write_buffer_size = 0; + connection->write_buffer_send_offset = 0; + connection->write_buffer_append_offset = 0; + if ((end != NULL) && (0 == strcasecmp (end, "close"))) + { + connection->read_closed = MHD_YES; + connection->read_buffer_offset = 0; + } + if ( ( (MHD_YES == connection->read_closed) && + (0 == connection->read_buffer_offset) ) || + (connection->version == NULL) || + (0 != strcasecmp (MHD_HTTP_VERSION_1_1, connection->version)) ) + { + connection->state = MHD_CONNECTION_CLOSED; + MHD_pool_destroy (connection->pool); + connection->pool = NULL; + connection->read_buffer = NULL; + connection->read_buffer_size = 0; + connection->read_buffer_offset = 0; + } + else + { + connection->version = NULL; + connection->state = MHD_CONNECTION_INIT; + connection->read_buffer + = MHD_pool_reset(connection->pool, + connection->read_buffer, + connection->read_buffer_size); + } + continue; + case MHD_CONNECTION_CLOSED: + if (connection->socket_fd != -1) + connection_close_error (connection); + break; + default: + EXTRA_CHECK(0); + break; + } + break; + } + timeout = connection->daemon->connection_timeout; + if ( (timeout != 0) && + (time(NULL) - timeout > connection->last_activity) ) { - if (errno == EINTR) - return MHD_YES; -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Failed to send data: %s\n", STRERROR (errno)); -#endif connection_close_error (connection); - return MHD_YES; - } -#if DEBUG_SEND_DATA - fprintf (stderr, - "Sent DATA response: `%.*s'\n", - ret, - &response->data[connection->response_write_position - - response->data_start]); -#endif - connection->response_write_position += ret; - if (connection->response_write_position > response->total_size) - abort (); /* internal error */ - if (connection->response_write_position == response->total_size) - { - if ((connection->have_received_body == MHD_NO) || - (connection->have_received_headers == MHD_NO)) - abort (); /* internal error */ - MHD_destroy_response (response); - if (connection->daemon->notify_completed != NULL) - connection->daemon->notify_completed (connection->daemon-> - notify_completed_cls, - connection, - &connection->client_context, - MHD_REQUEST_TERMINATED_COMPLETED_OK); - end = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_CONNECTION); - connection->client_context = NULL; - connection->continue_message_write_offset = 0; - connection->responseCode = 0; - connection->response = NULL; - connection->headers_received = NULL; - connection->have_received_headers = MHD_NO; - connection->have_sent_headers = MHD_NO; - connection->have_received_body = MHD_NO; - connection->response_write_position = 0; - connection->have_chunked_upload = MHD_NO; - connection->method = NULL; - connection->url = NULL; - if ((end != NULL) && (0 == strcasecmp (end, "close"))) - { - /* other side explicitly requested - that we close the connection after - this request */ - connection->read_close = MHD_YES; - } - if ((connection->read_close == MHD_YES) || - (0 != strcasecmp (MHD_HTTP_VERSION_1_1, connection->version))) - { - /* closed for reading => close for good! */ - if (connection->socket_fd != -1) - { -#if DEBUG_CLOSE -#if HAVE_MESSAGES - MHD_DLOG (connection->daemon, - "Closing connection (http 1.0 or end-of-stream for unknown content length)\n"); -#endif -#endif - SHUTDOWN (connection->socket_fd, SHUT_RDWR); - CLOSE (connection->socket_fd); - } - connection->socket_fd = -1; - } - connection->version = NULL; - connection->read_buffer = NULL; - connection->write_buffer = NULL; - connection->read_buffer_size = 0; - connection->read_buffer_offset = 0; - connection->write_buffer_size = 0; - connection->write_buffer_send_offset = 0; - connection->write_buffer_append_offset = 0; - MHD_pool_destroy (connection->pool); - connection->pool = NULL; + return MHD_NO; } return MHD_YES; + } /* end of connection.c */ diff --git a/src/daemon/connection.h b/src/daemon/connection.h @@ -39,18 +39,14 @@ MHD_connection_get_fdset (struct MHD_Connection *connection, fd_set * write_fd_set, fd_set * except_fd_set, int *max_fd); - -/** - * Call the handler of the application for this - * connection. - */ -void MHD_call_connection_handler (struct MHD_Connection *connection); - /** * This function handles a particular connection when it has been * determined that there is data to be read off a socket. All implementations * (multithreaded, external select, internal select) call this function * to handle reads. + * + * @return MHD_YES if we should continue to process the + * connection (not dead yet), MHD_NO if it died */ int MHD_connection_handle_read (struct MHD_Connection *connection); @@ -60,8 +56,23 @@ int MHD_connection_handle_read (struct MHD_Connection *connection); * determined that the socket can be written to. If there is no data * to be written, however, the function call does nothing. All implementations * (multithreaded, external select, internal select) call this function + * + * @return MHD_YES if we should continue to process the + * connection (not dead yet), MHD_NO if it died */ int MHD_connection_handle_write (struct MHD_Connection *connection); +/** + * This function was created to handle per-connection processing that + * has to happen even if the socket cannot be read or written to. All + * implementations (multithreaded, external select, internal select) + * call this function. + * + * @return MHD_YES if we should continue to process the + * connection (not dead yet), MHD_NO if it died + */ +int +MHD_connection_handle_idle (struct MHD_Connection *connection); + #endif diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c @@ -52,85 +52,6 @@ */ #define DEBUG_CONNECT MHD_NO -/** - * Register an access handler for all URIs beginning with uri_prefix. - * - * @param uri_prefix - * @return MRI_NO if a handler for this exact prefix - * already exists - */ -int -MHD_register_handler (struct MHD_Daemon *daemon, - const char *uri_prefix, - MHD_AccessHandlerCallback dh, void *dh_cls) -{ - struct MHD_Access_Handler *ah; - - if ((daemon == NULL) || (uri_prefix == NULL) || (dh == NULL)) - return MHD_NO; - ah = daemon->handlers; - while (ah != NULL) - { - if (0 == strcmp (uri_prefix, ah->uri_prefix)) - return MHD_NO; - ah = ah->next; - } - ah = malloc (sizeof (struct MHD_Access_Handler)); - if (ah == NULL) - { -#if HAVE_MESSAGES - MHD_DLOG (daemon, "Error allocating memory: %s\n", STRERROR (errno)); -#endif - return MHD_NO; - } - - ah->next = daemon->handlers; - ah->uri_prefix = strdup (uri_prefix); - ah->dh = dh; - ah->dh_cls = dh_cls; - daemon->handlers = ah; - return MHD_YES; -} - - -/** - * Unregister an access handler for the URIs beginning with - * uri_prefix. - * - * @param uri_prefix - * @return MHD_NO if a handler for this exact prefix - * is not known for this daemon - */ -int -MHD_unregister_handler (struct MHD_Daemon *daemon, - const char *uri_prefix, - MHD_AccessHandlerCallback dh, void *dh_cls) -{ - struct MHD_Access_Handler *prev; - struct MHD_Access_Handler *pos; - - if ((daemon == NULL) || (uri_prefix == NULL) || (dh == NULL)) - return MHD_NO; - pos = daemon->handlers; - prev = NULL; - while (pos != NULL) - { - if ((dh == pos->dh) && - (dh_cls == pos->dh_cls) && - (0 == strcmp (uri_prefix, pos->uri_prefix))) - { - if (prev == NULL) - daemon->handlers = pos->next; - else - prev->next = pos->next; - free (pos); - return MHD_YES; - } - prev = pos; - pos = pos->next; - } - return MHD_NO; -} /** * Obtain the select sets for this daemon. @@ -175,7 +96,6 @@ MHD_get_fdset (struct MHD_Daemon *daemon, return MHD_YES; } - /** * Main function of the thread that handles an individual * connection. @@ -191,26 +111,27 @@ MHD_handle_connection (void *data) int max; struct timeval tv; unsigned int timeout; - time_t now; + unsigned int now; if (con == NULL) abort (); timeout = con->daemon->connection_timeout; - now = time (NULL); - while ((!con->daemon->shutdown) && - (con->socket_fd != -1) && - ((timeout == 0) || (now - timeout > con->last_activity))) + while ( (!con->daemon->shutdown) && + (con->socket_fd != -1) ) { FD_ZERO (&rs); FD_ZERO (&ws); FD_ZERO (&es); max = 0; MHD_connection_get_fdset (con, &rs, &ws, &es, &max); + now = time(NULL); tv.tv_usec = 0; - tv.tv_sec = timeout - (now - con->last_activity); + if ( timeout > (now - con->last_activity) ) + tv.tv_sec = timeout - (now - con->last_activity); + else + tv.tv_sec = 0; num_ready = SELECT (max + 1, &rs, &ws, &es, (timeout != 0) ? &tv : NULL); - now = time (NULL); if (num_ready < 0) { if (errno == EINTR) @@ -221,18 +142,13 @@ MHD_handle_connection (void *data) #endif break; } - if (((FD_ISSET (con->socket_fd, &rs)) && - (MHD_YES != MHD_connection_handle_read (con))) || - ((con->socket_fd != -1) && - (FD_ISSET (con->socket_fd, &ws)) && - (MHD_YES != MHD_connection_handle_write (con)))) - break; - if ((con->have_received_headers == MHD_YES) && (con->response == NULL)) - MHD_call_connection_handler (con); + if (FD_ISSET (con->socket_fd, &rs)) + MHD_connection_handle_read (con); if ((con->socket_fd != -1) && - ((FD_ISSET (con->socket_fd, &rs)) || - (FD_ISSET (con->socket_fd, &ws)))) - con->last_activity = now; + (FD_ISSET (con->socket_fd, &ws)) ) + MHD_connection_handle_write (con); + if (con->socket_fd != -1) + MHD_connection_handle_idle (con); } if (con->socket_fd != -1) { @@ -370,11 +286,6 @@ MHD_accept_connection (struct MHD_Daemon *daemon) * Free resources associated with all closed connections. * (destroy responses, free buffers, etc.). A connection * is known to be closed if the socket_fd is -1. - * - * Also performs connection actions that need to be run - * even if the connection is not selectable (such as - * calling the application again with upload data when - * the upload data buffer is full). */ static void MHD_cleanup_connections (struct MHD_Daemon *daemon) @@ -382,33 +293,11 @@ MHD_cleanup_connections (struct MHD_Daemon *daemon) struct MHD_Connection *pos; struct MHD_Connection *prev; void *unused; - time_t timeout; - timeout = time (NULL); - if (daemon->connection_timeout != 0) - timeout -= daemon->connection_timeout; - else - timeout = 0; pos = daemon->connections; prev = NULL; while (pos != NULL) { - if ((pos->last_activity < timeout) && (pos->socket_fd != -1)) - { -#if DEBUG_CLOSE -#if HAVE_MESSAGES - MHD_DLOG (daemon, "Connection timed out, closing connection\n"); -#endif -#endif - SHUTDOWN (pos->socket_fd, SHUT_RDWR); - CLOSE (pos->socket_fd); - pos->socket_fd = -1; - if (pos->daemon->notify_completed != NULL) - pos->daemon->notify_completed (pos->daemon->notify_completed_cls, - pos, - &pos->client_context, - MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); - } if (pos->socket_fd == -1) { if (prev == NULL) @@ -420,8 +309,7 @@ MHD_cleanup_connections (struct MHD_Daemon *daemon) pthread_kill (pos->pid, SIGALRM); pthread_join (pos->pid, &unused); } - if (pos->response != NULL) - MHD_destroy_response (pos->response); + MHD_destroy_response (pos->response); MHD_pool_destroy (pos->pool); free (pos->addr); free (pos); @@ -432,12 +320,6 @@ MHD_cleanup_connections (struct MHD_Daemon *daemon) pos = prev->next; continue; } - - if ((0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && - ((pos->have_received_headers == MHD_YES) - && (pos->response == NULL))) - MHD_call_connection_handler (pos); - prev = pos; pos = pos->next; } @@ -573,15 +455,12 @@ MHD_select (struct MHD_Daemon *daemon, int may_block) if (ds != -1) { if (FD_ISSET (ds, &rs)) - { - pos->last_activity = now; - MHD_connection_handle_read (pos); - } - if (FD_ISSET (ds, &ws)) - { - pos->last_activity = now; - MHD_connection_handle_write (pos); - } + MHD_connection_handle_read (pos); + if ( (pos->socket_fd != -1) && + (FD_ISSET (ds, &ws)) ) + MHD_connection_handle_write (pos); + if (pos->socket_fd != -1) + MHD_connection_handle_idle(pos); } pos = pos->next; } @@ -726,10 +605,8 @@ MHD_start_daemon (unsigned int options, retVal->apc = apc; retVal->apc_cls = apc_cls; retVal->socket_fd = socket_fd; - retVal->default_handler.dh = dh; - retVal->default_handler.dh_cls = dh_cls; - retVal->default_handler.uri_prefix = ""; - retVal->default_handler.next = NULL; + retVal->default_handler = dh; + retVal->default_handler_cls = dh_cls; retVal->max_connections = MHD_MAX_CONNECTIONS_DEFAULT; retVal->pool_size = MHD_POOL_SIZE_DEFAULT; retVal->connection_timeout = 0; /* no timeout */ diff --git a/src/daemon/daemontest_get.c b/src/daemon/daemontest_get.c @@ -66,16 +66,24 @@ ahc_echo (void *cls, const char *upload_data, unsigned int *upload_data_size, void **unused) { + static int ptr; const char *me = cls; struct MHD_Response *response; int ret; if (0 != strcmp (me, method)) return MHD_NO; /* unexpected method */ + if (&ptr != *unused) { + *unused = &ptr; + return MHD_YES; + } + *unused = NULL; 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); + if (ret == MHD_NO) + abort(); return ret; } @@ -93,11 +101,11 @@ testInternalGet () cbc.size = 2048; cbc.pos = 0; d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, - 1080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); + 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); if (d == NULL) return 1; c = curl_easy_init (); - curl_easy_setopt (c, CURLOPT_URL, "http://localhost:1080/hello_world"); + curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11080/hello_world"); curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); @@ -150,12 +158,12 @@ testMultithreadedGet () curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); 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_TIMEOUT, 150L); 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); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L); // NOTE: use of CONNECTTIMEOUT without also // setting NOSIGNAL results in really weird // crashes on my system! @@ -214,8 +222,8 @@ testExternalGet () 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_TIMEOUT, 5L); - curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L); // NOTE: use of CONNECTTIMEOUT without also // setting NOSIGNAL results in really weird // crashes on my system! diff --git a/src/daemon/daemontest_large_put.c b/src/daemon/daemontest_large_put.c @@ -96,8 +96,16 @@ ahc_echo (void *cls, return MHD_NO; /* unexpected method */ if ((*done) == 0) { - if (*upload_data_size != PUT_SIZE) - return MHD_YES; /* not yet ready */ + if (*upload_data_size != PUT_SIZE) + { +#if 0 + fprintf(stderr, + "Waiting for more data (%u/%u)...\n", + *upload_data_size, + PUT_SIZE); +#endif + return MHD_YES; /* not yet ready */ + } if (0 == memcmp (upload_data, put_buffer, PUT_SIZE)) { *upload_data_size = 0; @@ -227,7 +235,13 @@ testMultithreadedPut () curl_easy_cleanup (c); MHD_stop_daemon (d); if (cbc.pos != strlen ("/hello_world")) - return 64; + { + fprintf(stderr, + "Got invalid response `%.*s'\n", + cbc.pos, + cbc.buf); + return 64; + } if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) return 128; return 0; @@ -260,7 +274,10 @@ testExternalPut () multi = NULL; d = MHD_start_daemon (MHD_USE_DEBUG, 1082, - NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END); + NULL, NULL, &ahc_echo, &done_flag, + MHD_OPTION_CONNECTION_MEMORY_LIMIT, + PUT_SIZE * 4, + MHD_OPTION_END); if (d == NULL) return 256; c = curl_easy_init (); @@ -357,9 +374,15 @@ testExternalPut () } MHD_stop_daemon (d); if (cbc.pos != strlen ("/hello_world")) - return 64; + { + fprintf(stderr, + "Got invalid response `%.*s'\n", + cbc.pos, + cbc.buf); + return 8192; + } if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) - return 128; + return 16384; return 0; } @@ -380,7 +403,7 @@ main (int argc, char *const *argv) errorCount += testInternalPut (); errorCount += testMultithreadedPut (); } - errorCount += testExternalPut (); + errorCount += testExternalPut (); free (put_buffer); if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); diff --git a/src/daemon/daemontest_postform.c b/src/daemon/daemontest_postform.c @@ -72,6 +72,13 @@ post_iterator (void *cls, { int *eok = cls; +#if 0 + fprintf(stderr, + "PI sees %s-%.*s\n", + key, + size, + value); +#endif if ((0 == strcmp (key, "name")) && (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size))) (*eok) |= 1; @@ -95,7 +102,7 @@ ahc_echo (void *cls, struct MHD_PostProcessor *pp; int ret; - if (0 != strcmp ("POST", method)) + if (0 != strcmp ("POST", method)) { printf ("METHOD: %s\n", method); return MHD_NO; /* unexpected method */ diff --git a/src/daemon/daemontest_put_chunked.c b/src/daemon/daemontest_put_chunked.c @@ -105,6 +105,11 @@ ahc_echo (void *cls, printf ("Invalid upload data `%8s'!\n", upload_data); return MHD_NO; } +#if 0 + fprintf(stderr, + "Not ready for response: %u/%u\n", + *done, 8); +#endif return MHD_YES; } response = MHD_create_response_from_data (strlen (url), diff --git a/src/daemon/fileserver_example.c b/src/daemon/fileserver_example.c @@ -52,8 +52,9 @@ ahc_echo (void *cls, const char *url, const char *method, const char *upload_data, - const char *version, unsigned int *upload_data_size, void **unused) + const char *version, unsigned int *upload_data_size, void **ptr) { + static int aptr; struct MHD_Response *response; int ret; FILE *file; @@ -61,6 +62,13 @@ ahc_echo (void *cls, if (0 != strcmp (method, "GET")) return MHD_NO; /* unexpected method */ + if (&aptr != *ptr) + { + /* do never respond on first call */ + *ptr = &aptr; + return MHD_YES; + } + *ptr = NULL; /* reset when done */ file = fopen (&url[1], "r"); if (file == NULL) { diff --git a/src/daemon/internal.h b/src/daemon/internal.h @@ -85,21 +85,9 @@ struct MHD_HTTP_Header char *value; enum MHD_ValueKind kind; -}; - - -struct MHD_Access_Handler -{ - struct MHD_Access_Handler *next; - - char *uri_prefix; - - MHD_AccessHandlerCallback dh; - void *dh_cls; }; - /** * Representation of a response. */ @@ -172,7 +160,129 @@ struct MHD_Response }; +/** + * States in a state machine for a connection. + * + * Transitions are any-state to CLOSED, any state to state+1, + * FOOTERS_SENT to INIT. CLOSED is the terminal state and + * INIT the initial state. + * + * Note that transitions for *reading* happen only after + * the input has been processed; transitions for + * *writing* happen after the respective data has been + * put into the write buffer (the write does not have + * to be completed yet). A transition to CLOSED or INIT + * requires the write to be complete. + */ +enum MHD_CONNECTION_STATE +{ + /** + * Connection just started (no headers received). + * Waiting for the line with the request type, URL and version. + */ + MHD_CONNECTION_INIT = 0, + + /** + * 1: We got the URL (and request type and version). Wait for a header line. + */ + MHD_CONNECTION_URL_RECEIVED = MHD_CONNECTION_INIT + 1, + + /** + * 2: We got part of a multi-line request header. Wait for the rest. + */ + MHD_CONNECTION_HEADER_PART_RECEIVED = MHD_CONNECTION_URL_RECEIVED + 1, + + /** + * 3: We got the request headers. Process them. + */ + MHD_CONNECTION_HEADERS_RECEIVED = MHD_CONNECTION_HEADER_PART_RECEIVED + 1, + + /** + * 4: We have processed the request headers. Send 100 continue. + */ + MHD_CONNECTION_HEADERS_PROCESSED = MHD_CONNECTION_HEADERS_RECEIVED + 1, + + /** + * 5: We have processed the headers and need to send 100 CONTINUE. + */ + MHD_CONNECTION_CONTINUE_SENDING = MHD_CONNECTION_HEADERS_PROCESSED + 1, + + /** + * 6: We have sent 100 CONTINUE (or do not need to). Read the message body. + */ + MHD_CONNECTION_CONTINUE_SENT = MHD_CONNECTION_CONTINUE_SENDING + 1, + + /** + * 7: We got the request body. Wait for a line of the footer. + */ + MHD_CONNECTION_BODY_RECEIVED = MHD_CONNECTION_CONTINUE_SENT + 1, + /** + * 8: We got part of a line of the footer. Wait for the + * rest. + */ + MHD_CONNECTION_FOOTER_PART_RECEIVED = MHD_CONNECTION_BODY_RECEIVED + 1, + + /** + * 9: We received the entire footer. Wait for a response to be queued + * and prepare the response headers. + */ + MHD_CONNECTION_FOOTERS_RECEIVED = MHD_CONNECTION_FOOTER_PART_RECEIVED + 1, + + /** + * 10: We have prepared the response headers in the writ buffer. + * Send the response headers. + */ + MHD_CONNECTION_HEADERS_SENDING = MHD_CONNECTION_FOOTERS_RECEIVED + 1, + + /** + * 11: We have sent the response headers. Get ready to send the body. + */ + MHD_CONNECTION_HEADERS_SENT = MHD_CONNECTION_HEADERS_SENDING + 1, + + /** + * 12: We are ready to send a part of a non-chunked body. Send it. + */ + MHD_CONNECTION_NORMAL_BODY_READY = MHD_CONNECTION_HEADERS_SENT + 1, + + /** + * 13: We are waiting for the client to provide more + * data of a non-chunked body. + */ + MHD_CONNECTION_NORMAL_BODY_UNREADY = MHD_CONNECTION_NORMAL_BODY_READY + 1, + + /** + * 14: We are ready to send a chunk. + */ + MHD_CONNECTION_CHUNKED_BODY_READY = MHD_CONNECTION_NORMAL_BODY_UNREADY + 1, + + /** + * 15: We are waiting for the client to provide a chunk of the body. + */ + MHD_CONNECTION_CHUNKED_BODY_UNREADY = MHD_CONNECTION_CHUNKED_BODY_READY + 1, + + /** + * 16: We have sent the response body. Prepare the footers. + */ + MHD_CONNECTION_BODY_SENT = MHD_CONNECTION_CHUNKED_BODY_UNREADY + 1, + + /** + * 17: We have prepared the response footer. Send it. + */ + MHD_CONNECTION_FOOTERS_SENDING = MHD_CONNECTION_BODY_SENT + 1, + + /** + * 18: We have sent the response footer. Shutdown or restart. + */ + MHD_CONNECTION_FOOTERS_SENT = MHD_CONNECTION_FOOTERS_SENDING + 1, + + /** + * 19: This connection is closed (no more activity + * allowed). + */ + MHD_CONNECTION_CLOSED = MHD_CONNECTION_FOOTERS_SENT + 1, + +}; struct MHD_Connection { @@ -250,6 +360,21 @@ struct MHD_Connection char *write_buffer; /** + * Last incomplete header line during parsing of headers. + * Allocated in pool. Only valid if state is + * either HEADER_PART_RECEIVED or FOOTER_PART_RECEIVED. + */ + char *last; + + /** + * Position after the colon on the last incomplete header + * line during parsing of headers. + * Allocated in pool. Only valid if state is + * either HEADER_PART_RECEIVED or FOOTER_PART_RECEIVED. + */ + char *colon; + + /** * Foreign address (of length addr_len). MALLOCED (not * in pool!). */ @@ -292,6 +417,12 @@ struct MHD_Connection size_t write_buffer_append_offset; /** + * How many more bytes of the body do we expect + * to read? "-1" for unknown. + */ + size_t remaining_upload_size; + + /** * Current write position in the actual response * (excluding headers, content only; should be 0 * while sending headers). @@ -299,13 +430,6 @@ struct MHD_Connection size_t response_write_position; /** - * Remaining (!) number of bytes in the upload. - * Set to -1 for unknown (connection will close - * to indicate end of upload). - */ - size_t remaining_upload_size; - - /** * Position in the 100 CONTINUE message that * we need to send when receiving http 1.1 requests. */ @@ -333,29 +457,15 @@ struct MHD_Connection * Has this socket been closed for reading (i.e. * other side closed the connection)? If so, * we must completely close the connection once - * we are done sending our response. - */ - int read_close; - - /** - * Have we finished receiving all of the headers yet? - * Set to 1 once we are done processing all of the - * headers. Note that due to pipelining, it is - * possible that the NEXT request is already - * (partially) waiting in the read buffer. - */ - int have_received_headers; - - /** - * Have we finished receiving the data from a - * potential file-upload? + * we are done sending our response (and stop + * trying to read from this socket). */ - int have_received_body; + int read_closed; /** - * Have we finished sending all of the headers yet? + * State in the FSM for this connection. */ - int have_sent_headers; + enum MHD_CONNECTION_STATE state; /** * HTTP response code. Only valid if response object @@ -373,6 +483,11 @@ struct MHD_Connection int response_unready; /** + * Are we sending with chunked encoding? + */ + int have_chunked_response; + + /** * Are we receiving with chunked encoding? This will be set to * MHD_YES after we parse the headers and are processing the body * with chunks. After we are done with the body and we are @@ -402,9 +517,15 @@ struct MHD_Connection struct MHD_Daemon { - struct MHD_Access_Handler *handlers; + /** + * Callback function for all requests. + */ + MHD_AccessHandlerCallback default_handler; - struct MHD_Access_Handler default_handler; + /** + * Closure argument to default_handler. + */ + void * default_handler_cls; /** * Linked list of our current connections. diff --git a/src/daemon/memorypool.c b/src/daemon/memorypool.c @@ -188,4 +188,33 @@ MHD_pool_reallocate (struct MemoryPool *pool, return NULL; } +/** + * Clear all entries from the memory pool except + * for "keep" of the given "size". + * + * @param keep pointer to the entry to keep (maybe NULL) + * @param size how many bytes need to be kept at this address + * @return addr new address of "keep" (if it had to change) + */ +void *MHD_pool_reset(struct MemoryPool * pool, + void * keep, + unsigned int size) +{ + if (keep != NULL) + { + if (keep != pool->memory) + { + memmove(pool->memory, + keep, + size); + keep = pool->memory; + } + pool->pos = size; + } + pool->end = pool->size; + return keep; +} + + + /* end of memorypool.c */ diff --git a/src/daemon/memorypool.h b/src/daemon/memorypool.h @@ -81,4 +81,16 @@ void *MHD_pool_reallocate (struct MemoryPool *pool, void *old, unsigned int old_size, unsigned int new_size); +/** + * Clear all entries from the memory pool except + * for "keep" of the given "size". + * + * @param keep pointer to the entry to keep (maybe NULL) + * @param size how many bytes need to be kept at this address + * @return addr new address of "keep" (if it had to change) + */ +void *MHD_pool_reset(struct MemoryPool * pool, + void * keep, + unsigned int size); + #endif diff --git a/src/daemon/minimal_example.c b/src/daemon/minimal_example.c @@ -41,14 +41,22 @@ ahc_echo (void *cls, const char *url, const char *method, const char *upload_data, - const char *version, unsigned int *upload_data_size, void **unused) + const char *version, unsigned int *upload_data_size, void **ptr) { + static int aptr; const char *me = cls; struct MHD_Response *response; int ret; if (0 != strcmp (method, "GET")) return MHD_NO; /* unexpected method */ + if (&aptr != *ptr) + { + /* do never respond on first call */ + *ptr = &aptr; + return MHD_YES; + } + *ptr = NULL; /* reset when done */ response = MHD_create_response_from_data (strlen (me), (void *) me, MHD_NO, MHD_NO); ret = MHD_queue_response (connection, MHD_HTTP_OK, response); diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -84,7 +84,7 @@ extern "C" /** * Current version of the library. */ -#define MHD_VERSION 0x00000100 +#define MHD_VERSION 0x00000200 /** * MHD-internal return codes. @@ -373,6 +373,11 @@ enum MHD_ValueKind */ MHD_GET_ARGUMENT_KIND = 8, + /** + * HTTP footer (only for http 1.1 chunked encodings). + */ + MHD_FOOTER_KIND = 16, + }; /** @@ -544,9 +549,15 @@ typedef int * libmicrohttpd guarantees that "pos" will be * the sum of all non-negative return values * obtained from the content reader so far. - * @return -1 on error (libmicrohttpd will no longer - * try to read content and instead close the connection - * with the client). + * @return -1 for the end of transmission (or on error); + * if a content transfer size was pre-set and the callback + * has provided fewer than that amount of data, + * MHD will close the connection with the client; + * if no content size was specified and this is an + * http 1.1 connection using chunked encoding, MHD will + * interpret "-1" as the normal end of the transfer + * (possibly allowing the client to perform additional + * requests using the same TCP connection). */ typedef int (*MHD_ContentReaderCallback) (void *cls, size_t pos, char *buf, int max); @@ -596,7 +607,7 @@ typedef int * in which case connections from any IP will be * accepted * @param apc_cls extra argument to apc - * @param dh default handler for all URIs + * @param dh handler called for all requests (repeatedly) * @param dh_cls extra argument to dh * @param ... list of options (type-value pairs, * terminated with MHD_OPTION_END). @@ -655,32 +666,6 @@ int MHD_get_timeout (struct MHD_Daemon *daemon, unsigned long long *timeout); */ int MHD_run (struct MHD_Daemon *daemon); - -/** - * Register an access handler for all URIs beginning with uri_prefix. - * - * @param uri_prefix - * @return MRI_NO if a handler for this exact prefix - * already exists - */ -int -MHD_register_handler (struct MHD_Daemon *daemon, - const char *uri_prefix, - MHD_AccessHandlerCallback dh, void *dh_cls); - -/** - * Unregister an access handler for the URIs beginning with - * uri_prefix. - * - * @param uri_prefix - * @return MHD_NO if a handler for this exact prefix - * is not known for this daemon - */ -int -MHD_unregister_handler (struct MHD_Daemon *daemon, - const char *uri_prefix, - MHD_AccessHandlerCallback dh, void *dh_cls); - /** * Get all of the headers from the request. *