diff options
Diffstat (limited to 'src/microhttpd/connection.c')
-rw-r--r-- | src/microhttpd/connection.c | 2919 |
1 files changed, 2919 insertions, 0 deletions
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c new file mode 100644 index 00000000..785fafdc --- /dev/null +++ b/src/microhttpd/connection.c @@ -0,0 +1,2919 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2007-2015 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 + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file connection.c + * @brief Methods for managing connections + * @author Daniel Pittman + * @author Christian Grothoff + */ + +#include "internal.h" +#include <limits.h> +#include "connection.h" +#include "memorypool.h" +#include "response.h" +#include "reason_phrase.h" + +#if HAVE_NETINET_TCP_H +/* for TCP_CORK */ +#include <netinet/tcp.h> +#endif + +#if defined(_WIN32) && defined(MHD_W32_MUTEX_) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif /* !WIN32_LEAN_AND_MEAN */ +#include <windows.h> +#endif /* _WIN32 && MHD_W32_MUTEX_ */ + + +/** + * Message to transmit when http 1.1 request is received + */ +#define HTTP_100_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n" + +/** + * Response text used when the request (http header) is too big to + * be processed. + * + * Intentionally empty here to keep our memory footprint + * minimal. + */ +#if HAVE_MESSAGES +#define REQUEST_TOO_BIG "<html><head><title>Request too big</title></head><body>Your HTTP header was too big for the memory constraints of this webserver.</body></html>" +#else +#define REQUEST_TOO_BIG "" +#endif + +/** + * Response text used when the request (http header) does not + * contain a "Host:" header and still claims to be HTTP 1.1. + * + * Intentionally empty here to keep our memory footprint + * minimal. + */ +#if HAVE_MESSAGES +#define REQUEST_LACKS_HOST "<html><head><title>"Host:" header required</title></head><body>In HTTP 1.1, requests must include a "Host:" header, and your HTTP 1.1 request lacked such a header.</body></html>" +#else +#define REQUEST_LACKS_HOST "" +#endif + +/** + * Response text used when the request (http header) is + * malformed. + * + * Intentionally empty here to keep our memory footprint + * minimal. + */ +#if HAVE_MESSAGES +#define REQUEST_MALFORMED "<html><head><title>Request malformed</title></head><body>Your HTTP request was syntactically incorrect.</body></html>" +#else +#define REQUEST_MALFORMED "" +#endif + +/** + * Response text used when there is an internal server error. + * + * Intentionally empty here to keep our memory footprint + * minimal. + */ +#if HAVE_MESSAGES +#define INTERNAL_ERROR "<html><head><title>Internal server error</title></head><body>Some programmer needs to study the manual more carefully.</body></html>" +#else +#define INTERNAL_ERROR "" +#endif + +/** + * Add extra debug messages with reasons for closing connections + * (non-error reasons). + */ +#define DEBUG_CLOSE MHD_NO + +/** + * Should all data send be printed to stderr? + */ +#define DEBUG_SEND_DATA MHD_NO + + +/** + * Get all of the headers from the request. + * + * @param connection connection to get values from + * @param kind types of values to iterate over + * @param iterator callback to call on each header; + * maybe NULL (then just count headers) + * @param iterator_cls extra argument to @a iterator + * @return number of entries iterated over + * @ingroup request + */ +int +MHD_get_connection_values (struct MHD_Connection *connection, + enum MHD_ValueKind kind, + MHD_KeyValueIterator iterator, void *iterator_cls) +{ + int ret; + struct MHD_HTTP_Header *pos; + + if (NULL == connection) + return -1; + ret = 0; + for (pos = connection->headers_received; NULL != pos; pos = pos->next) + if (0 != (pos->kind & kind)) + { + ret++; + if ((NULL != iterator) && + (MHD_YES != iterator (iterator_cls, + kind, pos->header, pos->value))) + return ret; + } + return ret; +} + + +/** + * This function can be used to add an entry to the HTTP headers of a + * connection (so that the #MHD_get_connection_values function will + * return them -- and the `struct MHD_PostProcessor` will also see + * them). This maybe required in certain situations (see Mantis + * #1399) where (broken) HTTP implementations fail to supply values + * needed by the post processor (or other parts of the application). + * + * This function MUST only be called from within the + * #MHD_AccessHandlerCallback (otherwise, access maybe improperly + * synchronized). Furthermore, the client must guarantee that the key + * and value arguments are 0-terminated strings that are NOT freed + * until the connection is closed. (The easiest way to do this is by + * passing only arguments to permanently allocated strings.). + * + * @param connection the connection for which a + * value should be set + * @param kind kind of the value + * @param key key for the value + * @param value the value itself + * @return #MHD_NO if the operation could not be + * performed due to insufficient memory; + * #MHD_YES on success + * @ingroup request + */ +int +MHD_set_connection_value (struct MHD_Connection *connection, + enum MHD_ValueKind kind, + const char *key, const char *value) +{ + struct MHD_HTTP_Header *pos; + + pos = MHD_pool_allocate (connection->pool, + sizeof (struct MHD_HTTP_Header), MHD_YES); + if (NULL == pos) + return MHD_NO; + pos->header = (char *) key; + pos->value = (char *) value; + pos->kind = kind; + pos->next = NULL; + /* append 'pos' to the linked list of headers */ + if (NULL == connection->headers_received_tail) + { + connection->headers_received = pos; + connection->headers_received_tail = pos; + } + else + { + connection->headers_received_tail->next = pos; + connection->headers_received_tail = pos; + } + return MHD_YES; +} + + +/** + * Get a particular header value. If multiple + * values match the kind, return any one of them. + * + * @param connection connection to get values from + * @param kind what kind of value are we looking for + * @param key the header to look for, NULL to lookup 'trailing' value without a key + * @return NULL if no such item was found + * @ingroup request + */ +const char * +MHD_lookup_connection_value (struct MHD_Connection *connection, + enum MHD_ValueKind kind, const char *key) +{ + struct MHD_HTTP_Header *pos; + + if (NULL == connection) + return NULL; + for (pos = connection->headers_received; NULL != pos; pos = pos->next) + if ((0 != (pos->kind & kind)) && + ( (key == pos->header) || + ( (NULL != pos->header) && + (NULL != key) && + (MHD_str_equal_caseless_(key, pos->header))))) + return pos->value; + return NULL; +} + + +/** + * Do we (still) need to send a 100 continue + * message for this connection? + * + * @param connection connection to test + * @return 0 if we don't need 100 CONTINUE, 1 if we do + */ +static int +need_100_continue (struct MHD_Connection *connection) +{ + const char *expect; + + return ( (NULL == connection->response) && + (NULL != connection->version) && + (MHD_str_equal_caseless_(connection->version, + MHD_HTTP_VERSION_1_1)) && + (NULL != (expect = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_EXPECT))) && + (MHD_str_equal_caseless_(expect, "100-continue")) && + (connection->continue_message_write_offset < + strlen (HTTP_100_CONTINUE)) ); +} + + +/** + * Close the given connection and give the + * specified termination code to the user. + * + * @param connection connection to close + * @param termination_code termination reason to give + */ +void +MHD_connection_close (struct MHD_Connection *connection, + enum MHD_RequestTerminationCode termination_code) +{ + struct MHD_Daemon *daemon; + + daemon = connection->daemon; + if (0 == (connection->daemon->options & MHD_USE_EPOLL_TURBO)) + shutdown (connection->socket_fd, + (MHD_YES == connection->read_closed) ? SHUT_WR : SHUT_RDWR); + connection->state = MHD_CONNECTION_CLOSED; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + if ( (NULL != daemon->notify_completed) && + (MHD_YES == connection->client_aware) ) + daemon->notify_completed (daemon->notify_completed_cls, + connection, + &connection->client_context, + termination_code); + connection->client_aware = MHD_NO; +} + + +/** + * A serious error occured, close the + * connection (and notify the application). + * + * @param connection connection to close with error + * @param emsg error message (can be NULL) + */ +static void +connection_close_error (struct MHD_Connection *connection, + const char *emsg) +{ +#if HAVE_MESSAGES + if (NULL != emsg) + MHD_DLOG (connection->daemon, emsg); +#endif + MHD_connection_close (connection, MHD_REQUEST_TERMINATED_WITH_ERROR); +} + + +/** + * Macro to only include error message in call to + * "connection_close_error" if we have HAVE_MESSAGES. + */ +#if HAVE_MESSAGES +#define CONNECTION_CLOSE_ERROR(c, emsg) connection_close_error (c, emsg) +#else +#define CONNECTION_CLOSE_ERROR(c, emsg) connection_close_error (c, NULL) +#endif + + +/** + * 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). + * + * @param connection the connection + * @return #MHD_NO if readying the response failed (the + * lock on the response will have been released already + * in this case). + */ +static int +try_ready_normal_body (struct MHD_Connection *connection) +{ + ssize_t ret; + struct MHD_Response *response; + + response = connection->response; + if (NULL == response->crc) + return MHD_YES; + if (0 == response->total_size) + return MHD_YES; /* 0-byte response is always ready */ + if ( (response->data_start <= + connection->response_write_position) && + (response->data_size + response->data_start > + connection->response_write_position) ) + return MHD_YES; /* response already ready */ +#if LINUX + if ( (MHD_INVALID_SOCKET != response->fd) && + (0 == (connection->daemon->options & MHD_USE_SSL)) ) + { + /* will use sendfile, no need to bother response crc */ + return MHD_YES; + } +#endif + + ret = response->crc (response->crc_cls, + connection->response_write_position, + response->data, + MHD_MIN (response->data_buffer_size, + response->total_size - + connection->response_write_position)); + if ( (((ssize_t) MHD_CONTENT_READER_END_OF_STREAM) == ret) || + (((ssize_t) MHD_CONTENT_READER_END_WITH_ERROR) == ret) ) + { + /* either error or http 1.0 transfer, close socket! */ + response->total_size = connection->response_write_position; + if (NULL != response->crc) + (void) MHD_mutex_unlock_ (&response->mutex); + if ( ((ssize_t)MHD_CONTENT_READER_END_OF_STREAM) == ret) + MHD_connection_close (connection, MHD_REQUEST_TERMINATED_COMPLETED_OK); + else + CONNECTION_CLOSE_ERROR (connection, + "Closing connection (stream error)\n"); + return MHD_NO; + } + response->data_start = connection->response_write_position; + response->data_size = ret; + if (0 == ret) + { + connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY; + if (NULL != response->crc) + (void) MHD_mutex_unlock_ (&response->mutex); + 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). + * + * @param connection the connection + * @return #MHD_NO if readying the response failed + */ +static int +try_ready_chunked_body (struct MHD_Connection *connection) +{ + ssize_t ret; + char *buf; + struct MHD_Response *response; + size_t size; + char cbuf[10]; /* 10: max strlen of "%x\r\n" */ + size_t cblen; + + response = connection->response; + if (0 == connection->write_buffer_size) + { + size = connection->daemon->pool_size; + do + { + size /= 2; + if (size < 128) + { + /* not enough memory */ + CONNECTION_CLOSE_ERROR (connection, + "Closing connection (out of memory)\n"); + return MHD_NO; + } + buf = MHD_pool_allocate (connection->pool, size, MHD_NO); + } + while (NULL == buf); + connection->write_buffer_size = size; + connection->write_buffer = buf; + } + + if ( (response->data_start <= + connection->response_write_position) && + (response->data_size + response->data_start > + connection->response_write_position) ) + { + /* buffer already ready, use what is there for the chunk */ + ret = response->data_size + response->data_start - connection->response_write_position; + if ( (ret > 0) && + (((size_t) ret) > connection->write_buffer_size - sizeof (cbuf) - 2) ) + ret = connection->write_buffer_size - sizeof (cbuf) - 2; + memcpy (&connection->write_buffer[sizeof (cbuf)], + &response->data[connection->response_write_position - response->data_start], + ret); + } + else + { + /* buffer not in range, try to fill it */ + if (0 == response->total_size) + ret = 0; /* response must be empty, don't bother calling crc */ + else + ret = response->crc (response->crc_cls, + connection->response_write_position, + &connection->write_buffer[sizeof (cbuf)], + connection->write_buffer_size - sizeof (cbuf) - 2); + } + if ( ((ssize_t) MHD_CONTENT_READER_END_WITH_ERROR) == ret) + { + /* error, close socket! */ + response->total_size = connection->response_write_position; + CONNECTION_CLOSE_ERROR (connection, + "Closing connection (error generating response)\n"); + return MHD_NO; + } + if ( (((ssize_t) MHD_CONTENT_READER_END_OF_STREAM) == ret) || + (0 == response->total_size) ) + { + /* 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 (0 == ret) + { + connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY; + return MHD_NO; + } + if (ret > 0xFFFFFF) + ret = 0xFFFFFF; + MHD_snprintf_ (cbuf, + sizeof (cbuf), + "%X\r\n", (unsigned int) ret); + cblen = strlen (cbuf); + EXTRA_CHECK (cblen <= sizeof (cbuf)); + memcpy (&connection->write_buffer[sizeof (cbuf) - cblen], cbuf, cblen); + memcpy (&connection->write_buffer[sizeof (cbuf) + ret], "\r\n", 2); + connection->response_write_position += ret; + connection->write_buffer_send_offset = sizeof (cbuf) - cblen; + connection->write_buffer_append_offset = sizeof (cbuf) + ret + 2; + return MHD_YES; +} + + +/** + * Are we allowed to keep the given connection alive? We can use the + * TCP stream for a second request if the connection is HTTP 1.1 and + * the "Connection" header either does not exist or is not set to + * "close", or if the connection is HTTP 1.0 and the "Connection" + * header is explicitly set to "keep-alive". If no HTTP version is + * specified (or if it is not 1.0 or 1.1), we definitively close the + * connection. If the "Connection" header is not exactly "close" or + * "keep-alive", we proceed to use the default for the respective HTTP + * version (which is conservative for HTTP 1.0, but might be a bit + * optimistic for HTTP 1.1). + * + * @param connection the connection to check for keepalive + * @return #MHD_YES if (based on the request), a keepalive is + * legal + */ +static int +keepalive_possible (struct MHD_Connection *connection) +{ + const char *end; + + if (NULL == connection->version) + return MHD_NO; + if ( (NULL != connection->response) && + (0 != (connection->response->flags & MHD_RF_HTTP_VERSION_1_0_ONLY) ) ) + return MHD_NO; + end = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONNECTION); + if (MHD_str_equal_caseless_(connection->version, + MHD_HTTP_VERSION_1_1)) + { + if (NULL == end) + return MHD_YES; + if ( (MHD_str_equal_caseless_ (end, "close")) || + (MHD_str_equal_caseless_ (end, "upgrade")) ) + return MHD_NO; + return MHD_YES; + } + if (MHD_str_equal_caseless_(connection->version, + MHD_HTTP_VERSION_1_0)) + { + if (NULL == end) + return MHD_NO; + if (MHD_str_equal_caseless_(end, "Keep-Alive")) + return MHD_YES; + return MHD_NO; + } + return MHD_NO; +} + + +/** + * Produce HTTP "Date:" header. + * + * @param date where to write the header, with + * at least 128 bytes available space. + */ +static void +get_date_string (char *date) +{ + static const char *const days[] = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + static const char *const mons[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec" + }; + struct tm now; + time_t t; +#if defined(_WIN32) && !defined(HAVE_GMTIME_S) && !defined(__CYGWIN__) + struct tm* pNow; +#endif + + date[0] = 0; + time (&t); +#if !defined(_WIN32) + if (NULL != gmtime_r (&t, &now)) + { +#elif defined(HAVE_GMTIME_S) + if (0 == gmtime_s (&now, &t)) + { +#elif defined(__CYGWIN__) + if (NULL != gmtime_r (&t, &now)) + { +#else + pNow = gmtime(&t); + if (NULL != pNow) + { + now = *pNow; +#endif + sprintf (date, + "Date: %3s, %02u %3s %04u %02u:%02u:%02u GMT\r\n", + days[now.tm_wday % 7], + (unsigned int) now.tm_mday, + mons[now.tm_mon % 12], + (unsigned int) (1900 + now.tm_year), + (unsigned int) now.tm_hour, + (unsigned int) now.tm_min, + (unsigned int) now.tm_sec); + } +} + + +/** + * Try growing the read buffer. We initially claim half the + * available buffer space for the read buffer (the other half + * being left for management data structures; the write + * buffer can in the end take virtually everything as the + * read buffer can be reduced to the minimum necessary at that + * point. + * + * @param connection the connection + * @return #MHD_YES on success, #MHD_NO on failure + */ +static int +try_grow_read_buffer (struct MHD_Connection *connection) +{ + void *buf; + size_t new_size; + + if (0 == connection->read_buffer_size) + new_size = connection->daemon->pool_size / 2; + else + new_size = connection->read_buffer_size + MHD_BUF_INC_SIZE; + buf = MHD_pool_reallocate (connection->pool, + connection->read_buffer, + connection->read_buffer_size, + new_size); + if (NULL == buf) + return MHD_NO; + /* we can actually grow the buffer, do it! */ + connection->read_buffer = buf; + connection->read_buffer_size = new_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. If headers are missing in the response supplied + * by the application, additional headers may be added here. + * + * @param connection the connection + * @return #MHD_YES on success, #MHD_NO on failure (out of memory) + */ +static int +build_header_response (struct MHD_Connection *connection) +{ + size_t size; + size_t off; + struct MHD_HTTP_Header *pos; + char code[256]; + char date[128]; + char content_length_buf[128]; + size_t content_length_len; + char *data; + enum MHD_ValueKind kind; + const char *reason_phrase; + uint32_t rc; + const char *client_requested_close; + const char *response_has_close; + const char *response_has_keepalive; + const char *have_encoding; + const char *have_content_length; + int must_add_close; + int must_add_chunked_encoding; + int must_add_keep_alive; + int must_add_content_length; + + EXTRA_CHECK (NULL != connection->version); + if (0 == strlen (connection->version)) + { + data = MHD_pool_allocate (connection->pool, 0, MHD_YES); + connection->write_buffer = data; + connection->write_buffer_append_offset = 0; + connection->write_buffer_send_offset = 0; + connection->write_buffer_size = 0; + return MHD_YES; + } + if (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state) + { + rc = connection->responseCode & (~MHD_ICY_FLAG); + reason_phrase = MHD_get_reason_phrase_for (rc); + sprintf (code, + "%s %u %s\r\n", + (0 != (connection->responseCode & MHD_ICY_FLAG)) + ? "ICY" + : ( (MHD_str_equal_caseless_ (MHD_HTTP_VERSION_1_0, + connection->version)) + ? MHD_HTTP_VERSION_1_0 + : MHD_HTTP_VERSION_1_1), + rc, + reason_phrase); + off = strlen (code); + /* estimate size */ + size = off + 2; /* +2 for extra "\r\n" at the end */ + kind = MHD_HEADER_KIND; + if ( (0 == (connection->daemon->options & MHD_SUPPRESS_DATE_NO_CLOCK)) && + (NULL == MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_DATE)) ) + get_date_string (date); + else + date[0] = '\0'; + size += strlen (date); + } + else + { + /* 2 bytes for final CRLF of a Chunked-Body */ + size = 2; + kind = MHD_FOOTER_KIND; + off = 0; + } + + /* calculate extra headers we need to add, such as 'Connection: close', + first see what was explicitly requested by the application */ + must_add_close = MHD_NO; + must_add_chunked_encoding = MHD_NO; + must_add_keep_alive = MHD_NO; + must_add_content_length = MHD_NO; + switch (connection->state) + { + case MHD_CONNECTION_FOOTERS_RECEIVED: + response_has_close = MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_CONNECTION); + response_has_keepalive = response_has_close; + if ( (NULL != response_has_close) && + (!MHD_str_equal_caseless_ (response_has_close, "close")) ) + response_has_close = NULL; + if ( (NULL != response_has_keepalive) && + (!MHD_str_equal_caseless_ (response_has_keepalive, "Keep-Alive")) ) + response_has_keepalive = NULL; + client_requested_close = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONNECTION); + if ( (NULL != client_requested_close) && + (!MHD_str_equal_caseless_ (client_requested_close, "close")) ) + client_requested_close = NULL; + + /* now analyze chunked encoding situation */ + connection->have_chunked_upload = MHD_NO; + + if ( (MHD_SIZE_UNKNOWN == connection->response->total_size) && + (NULL == response_has_close) && + (NULL == client_requested_close) ) + { + /* size is unknown, and close was not explicitly requested; + need to either to HTTP 1.1 chunked encoding or + close the connection */ + /* 'close' header doesn't exist yet, see if we need to add one; + if the client asked for a close, no need to start chunk'ing */ + if ( (MHD_YES == keepalive_possible (connection)) && + (MHD_str_equal_caseless_ (MHD_HTTP_VERSION_1_1, + connection->version) ) ) + { + have_encoding = MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_TRANSFER_ENCODING); + if (NULL == have_encoding) + { + must_add_chunked_encoding = MHD_YES; + connection->have_chunked_upload = MHD_YES; + } + else if (MHD_str_equal_caseless_(have_encoding, "identity")) + { + /* application forced identity encoding, can't do 'chunked' */ + must_add_close = MHD_YES; + } + else + { + connection->have_chunked_upload = MHD_YES; + } + } + else + { + /* Keep alive or chunking not possible + => set close header if not present */ + if (NULL == response_has_close) + must_add_close = MHD_YES; + } + } + + /* check for other reasons to add 'close' header */ + if ( ( (NULL != client_requested_close) || + (MHD_YES == connection->read_closed) ) && + (NULL == response_has_close) && + (0 == (connection->response->flags & MHD_RF_HTTP_VERSION_1_0_ONLY) ) ) + must_add_close = MHD_YES; + + /* check if we should add a 'content length' header */ + have_content_length = MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_CONTENT_LENGTH); + + if ( (MHD_SIZE_UNKNOWN != connection->response->total_size) && + (NULL == have_content_length) && + ( (NULL == connection->method) || + (! MHD_str_equal_caseless_ (connection->method, + MHD_HTTP_METHOD_CONNECT)) ) ) + { + /* + Here we add a content-length if one is missing; however, + for 'connect' methods, the responses MUST NOT include a + content-length header *if* the response code is 2xx (in + which case we expect there to be no body). Still, + as we don't know the response code here in some cases, we + simply only force adding a content-length header if this + is not a 'connect' or if the response is not empty + (which is kind of more sane, because if some crazy + application did return content with a 2xx status code, + then having a content-length might again be a good idea). + + Note that the change from 'SHOULD NOT' to 'MUST NOT' is + a recent development of the HTTP 1.1 specification. + */ + content_length_len + = sprintf (content_length_buf, + MHD_HTTP_HEADER_CONTENT_LENGTH ": " MHD_UNSIGNED_LONG_LONG_PRINTF "\r\n", + (MHD_UNSIGNED_LONG_LONG) connection->response->total_size); + must_add_content_length = MHD_YES; + } + + /* check for adding keep alive */ + if ( (NULL == response_has_keepalive) && + (NULL == response_has_close) && + (MHD_NO == must_add_close) && + (0 == (connection->response->flags & MHD_RF_HTTP_VERSION_1_0_ONLY) ) && + (MHD_YES == keepalive_possible (connection)) ) + must_add_keep_alive = MHD_YES; + break; + case MHD_CONNECTION_BODY_SENT: + break; + default: + EXTRA_CHECK (0); + } + + if (must_add_close) + size += strlen ("Connection: close\r\n"); + if (must_add_keep_alive) + size += strlen ("Connection: Keep-Alive\r\n"); + if (must_add_chunked_encoding) + size += strlen ("Transfer-Encoding: chunked\r\n"); + if (must_add_content_length) + size += content_length_len; + EXTRA_CHECK (! (must_add_close && must_add_keep_alive) ); + EXTRA_CHECK (! (must_add_chunked_encoding && must_add_content_length) ); + + for (pos = connection->response->first_header; NULL != pos; pos = pos->next) + if ( (pos->kind == kind) && + (! ( (MHD_YES == must_add_close) && + (pos->value == response_has_keepalive) && + (MHD_str_equal_caseless_(pos->header, + MHD_HTTP_HEADER_CONNECTION) ) ) ) ) + size += strlen (pos->header) + strlen (pos->value) + 4; /* colon, space, linefeeds */ + /* produce data */ + data = MHD_pool_allocate (connection->pool, size + 1, MHD_NO); + if (NULL == data) + { +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Not enough memory for write!\n"); +#endif + return MHD_NO; + } + if (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state) + { + memcpy (data, code, off); + } + if (must_add_close) + { + /* we must add the 'Connection: close' header */ + memcpy (&data[off], + "Connection: close\r\n", + strlen ("Connection: close\r\n")); + off += strlen ("Connection: close\r\n"); + } + if (must_add_keep_alive) + { + /* we must add the 'Connection: Keep-Alive' header */ + memcpy (&data[off], + "Connection: Keep-Alive\r\n", + strlen ("Connection: Keep-Alive\r\n")); + off += strlen ("Connection: Keep-Alive\r\n"); + } + if (must_add_chunked_encoding) + { + /* we must add the 'Transfer-Encoding: chunked' header */ + memcpy (&data[off], + "Transfer-Encoding: chunked\r\n", + strlen ("Transfer-Encoding: chunked\r\n")); + off += strlen ("Transfer-Encoding: chunked\r\n"); + } + if (must_add_content_length) + { + /* we must add the 'Content-Length' header */ + memcpy (&data[off], + content_length_buf, + content_length_len); + off += content_length_len; + } + for (pos = connection->response->first_header; NULL != pos; pos = pos->next) + if ( (pos->kind == kind) && + (! ( (pos->value == response_has_keepalive) && + (MHD_YES == must_add_close) && + (MHD_str_equal_caseless_(pos->header, + MHD_HTTP_HEADER_CONNECTION) ) ) ) ) + off += sprintf (&data[off], + "%s: %s\r\n", + pos->header, + pos->value); + if (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state) + { + strcpy (&data[off], date); + off += strlen (date); + } + memcpy (&data[off], "\r\n", 2); + off += 2; + + if (off != size) + mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); + 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; +} + + +/** + * We encountered an error processing the request. + * Handle it properly by stopping to read data + * and sending the indicated response code and message. + * + * @param connection the connection + * @param status_code the response code to send (400, 413 or 414) + * @param message the error message to send + */ +static void +transmit_error_response (struct MHD_Connection *connection, + unsigned int status_code, + const char *message) +{ + struct MHD_Response *response; + + if (NULL == connection->version) + { + /* we were unable to process the full header line, so we don't + really know what version the client speaks; assume 1.0 */ + connection->version = MHD_HTTP_VERSION_1_0; + } + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + connection->read_closed = MHD_YES; +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Error %u (`%s') processing request, closing connection.\n", + status_code, message); +#endif + EXTRA_CHECK (NULL == connection->response); + response = MHD_create_response_from_buffer (strlen (message), + (void *) message, + MHD_RESPMEM_PERSISTENT); + MHD_queue_response (connection, status_code, response); + EXTRA_CHECK (NULL != connection->response); + MHD_destroy_response (response); + if (MHD_NO == build_header_response (connection)) + { + /* oops - close! */ + CONNECTION_CLOSE_ERROR (connection, + "Closing connection (failed to create response header)\n"); + } + else + { + connection->state = MHD_CONNECTION_HEADERS_SENDING; + } +} + + +/** + * Update the 'event_loop_info' field of this connection based on the state + * that the connection is now in. May also close the connection or + * perform other updates to the connection if needed to prepare for + * the next round of the event loop. + * + * @param connection connetion to get poll set for + */ +static void +MHD_connection_update_event_loop_info (struct MHD_Connection *connection) +{ + while (1) + { +#if DEBUG_STATES + MHD_DLOG (connection->daemon, + "%s: state: %s\n", + __FUNCTION__, + MHD_state_to_string (connection->state)); +#endif + switch (connection->state) + { +#if HTTPS_SUPPORT + case MHD_TLS_CONNECTION_INIT: + if (0 == gnutls_record_get_direction (connection->tls_session)) + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + else + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; +#endif + 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_buffer_offset == connection->read_buffer_size) && + (MHD_NO == try_grow_read_buffer (connection)) ) + { + transmit_error_response (connection, + (connection->url != NULL) + ? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE + : MHD_HTTP_REQUEST_URI_TOO_LONG, + REQUEST_TOO_BIG); + continue; + } + if (MHD_NO == connection->read_closed) + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + else + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; + break; + case MHD_CONNECTION_HEADERS_RECEIVED: + EXTRA_CHECK (0); + break; + case MHD_CONNECTION_HEADERS_PROCESSED: + EXTRA_CHECK (0); + break; + case MHD_CONNECTION_CONTINUE_SENDING: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_CONTINUE_SENT: + if (connection->read_buffer_offset == connection->read_buffer_size) + { + if ((MHD_YES != try_grow_read_buffer (connection)) && + (0 != (connection->daemon->options & + (MHD_USE_SELECT_INTERNALLY | + MHD_USE_THREAD_PER_CONNECTION)))) + { + /* failed to grow the read buffer, and the + client which is supposed to handle the + received data in a *blocking* fashion + (in this mode) did not handle the data as + it was supposed to! + => we would either have to do busy-waiting + (on the client, which would likely fail), + or if we do nothing, we would just timeout + on the connection (if a timeout is even + set!). + Solution: we kill the connection with an error */ + transmit_error_response (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + INTERNAL_ERROR); + continue; + } + } + if ( (connection->read_buffer_offset < connection->read_buffer_size) && + (MHD_NO == connection->read_closed) ) + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + else + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; + 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_CLOSE_ERROR (connection, + NULL); + continue; + } + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + /* transition to FOOTERS_RECEIVED + happens in read handler */ + break; + case MHD_CONNECTION_FOOTERS_RECEIVED: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; + break; + case MHD_CONNECTION_HEADERS_SENDING: + /* headers in buffer, keep writing */ + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_HEADERS_SENT: + EXTRA_CHECK (0); + break; + case MHD_CONNECTION_NORMAL_BODY_READY: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_NORMAL_BODY_UNREADY: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_BLOCK; + break; + case MHD_CONNECTION_BODY_SENT: + EXTRA_CHECK (0); + break; + case MHD_CONNECTION_FOOTERS_SENDING: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_FOOTERS_SENT: + EXTRA_CHECK (0); + break; + case MHD_CONNECTION_CLOSED: + connection->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + return; /* do nothing, not even reading */ + default: + EXTRA_CHECK (0); + } + break; + } +} + + +/** + * 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 pointer to the line. + * + * @param connection connection we're processing + * @return NULL if no full line is available + */ +static char * +get_next_header_line (struct MHD_Connection *connection) +{ + char *rbuf; + size_t pos; + + if (0 == connection->read_buffer_offset) + return NULL; + pos = 0; + rbuf = connection->read_buffer; + while ((pos < connection->read_buffer_offset - 1) && + ('\r' != rbuf[pos]) && ('\n' != rbuf[pos])) + pos++; + if ( (pos == connection->read_buffer_offset - 1) && + ('\n' != rbuf[pos]) ) + { + /* not found, consider growing... */ + if ( (connection->read_buffer_offset == connection->read_buffer_size) && + (MHD_NO == + try_grow_read_buffer (connection)) ) + { + transmit_error_response (connection, + (NULL != connection->url) + ? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE + : MHD_HTTP_REQUEST_URI_TOO_LONG, + REQUEST_TOO_BIG); + } + return NULL; + } + /* found, check if we have proper LFCR */ + if (('\r' == rbuf[pos]) && ('\n' == rbuf[pos + 1])) + rbuf[pos++] = '\0'; /* skip both r and n */ + rbuf[pos++] = '\0'; + connection->read_buffer += pos; + connection->read_buffer_size -= pos; + connection->read_buffer_offset -= pos; + return rbuf; +} + + +/** + * Add an entry to the HTTP headers of a connection. If this fails, + * transmit an error response (request too big). + * + * @param connection the connection for which a + * value should be set + * @param kind kind of the value + * @param key key for the value + * @param value the value itself + * @return #MHD_NO on failure (out of memory), #MHD_YES for success + */ +static int +connection_add_header (struct MHD_Connection *connection, + char *key, char *value, enum MHD_ValueKind kind) +{ + if (MHD_NO == MHD_set_connection_value (connection, + kind, + key, value)) + { +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Not enough memory to allocate header record!\n"); +#endif + transmit_error_response (connection, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + REQUEST_TOO_BIG); + return MHD_NO; + } + return MHD_YES; +} + + +/** + * Parse and unescape the arguments given by the client as part + * of the HTTP request URI. + * + * @param kind header kind to use for adding to the connection + * @param connection connection to add headers to + * @param args argument URI string (after "?" in URI) + * @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) +{ + char *equals; + char *amper; + + while (NULL != args) + { + equals = strchr (args, '='); + amper = strchr (args, '&'); + if (NULL == amper) + { + /* last argument */ + if (NULL == equals) + { + /* got 'foo', add key 'foo' with NULL for value */ + MHD_unescape_plus (args); + connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, + connection, + args); + return connection_add_header (connection, + args, + NULL, + kind); + } + /* got 'foo=bar' */ + equals[0] = '\0'; + equals++; + MHD_unescape_plus (args); + connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, + connection, + args); + MHD_unescape_plus (equals); + connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, + connection, + equals); + return connection_add_header (connection, args, equals, kind); + } + /* amper is non-NULL here */ + amper[0] = '\0'; + amper++; + if ( (NULL == equals) || + (equals >= amper) ) + { + /* got 'foo&bar' or 'foo&bar=val', add key 'foo' with NULL for value */ + MHD_unescape_plus (args); + connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, + connection, + args); + if (MHD_NO == + connection_add_header (connection, + args, + NULL, + kind)) + return MHD_NO; + /* continue with 'bar' */ + args = amper; + continue; + + } + /* equals and amper are non-NULL here, and equals < amper, + so we got regular 'foo=value&bar...'-kind of argument */ + equals[0] = '\0'; + equals++; + MHD_unescape_plus (args); + connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, + connection, + args); + MHD_unescape_plus (equals); + connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, + connection, + equals); + if (MHD_NO == 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 int +parse_cookie_header (struct MHD_Connection *connection) +{ + const char *hdr; + char *cpy; + char *pos; + char *sce; + char *semicolon; + char *equals; + char *ekill; + char old; + int quotes; + + hdr = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_COOKIE); + if (NULL == hdr) + return MHD_YES; + cpy = MHD_pool_allocate (connection->pool, strlen (hdr) + 1, MHD_YES); + if (NULL == cpy) + { +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Not enough memory to parse cookies!\n"); +#endif + transmit_error_response (connection, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + REQUEST_TOO_BIG); + return MHD_NO; + } + memcpy (cpy, hdr, strlen (hdr) + 1); + pos = cpy; + while (NULL != pos) + { + while (' ' == *pos) + pos++; /* skip spaces */ + + sce = pos; + while (((*sce) != '\0') && + ((*sce) != ',') && ((*sce) != ';') && ((*sce) != '=')) + sce++; + /* remove tailing whitespace (if any) from key */ + ekill = sce - 1; + while ((*ekill == ' ') && (ekill >= pos)) + *(ekill--) = '\0'; + old = *sce; + *sce = '\0'; + if (old != '=') + { + /* value part omitted, use empty string... */ + if (MHD_NO == + connection_add_header (connection, pos, "", MHD_COOKIE_KIND)) + return MHD_NO; + if (old == '\0') + break; + pos = sce + 1; + continue; + } + equals = sce + 1; + quotes = 0; + semicolon = equals; + while ( ('\0' != semicolon[0]) && + ( (0 != quotes) || + ( (';' != semicolon[0]) && + (',' != semicolon[0]) ) ) ) + { + if ('"' == semicolon[0]) + quotes = (quotes + 1) & 1; + semicolon++; + } + if ('\0' == semicolon[0]) + semicolon = NULL; + if (NULL != semicolon) + { + semicolon[0] = '\0'; + semicolon++; + } + /* remove quotes */ + if ( ('"' == equals[0]) && + ('"' == equals[strlen (equals) - 1]) ) + { + equals[strlen (equals) - 1] = '\0'; + equals++; + } + if (MHD_NO == connection_add_header (connection, + pos, equals, MHD_COOKIE_KIND)) + return MHD_NO; + pos = semicolon; + } + return MHD_YES; +} + + +/** + * Parse the first line of the HTTP HEADER. + * + * @param connection the connection (updated) + * @param line the first line + * @return #MHD_YES if the line is ok, #MHD_NO if it is malformed + */ +static int +parse_initial_message_line (struct MHD_Connection *connection, + char *line) +{ + char *uri; + char *http_version; + char *args; + + if (NULL == (uri = strchr (line, ' '))) + return MHD_NO; /* serious error */ + uri[0] = '\0'; + connection->method = line; + uri++; + while (' ' == uri[0]) + uri++; + http_version = strchr (uri, ' '); + if (NULL != http_version) + { + http_version[0] = '\0'; + http_version++; + } + if (NULL != connection->daemon->uri_log_callback) + connection->client_context + = connection->daemon->uri_log_callback (connection->daemon->uri_log_callback_cls, + uri, + connection); + args = strchr (uri, '?'); + if (NULL != args) + { + args[0] = '\0'; + args++; + parse_arguments (MHD_GET_ARGUMENT_KIND, connection, args); + } + connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, + connection, + uri); + connection->url = uri; + if (NULL == http_version) + connection->version = ""; + else + connection->version = http_version; + return MHD_YES; +} + + +/** + * Call the handler of the application for this + * connection. Handles chunking of the upload + * as well as normal uploads. + * + * @param connection connection we're processing + */ +static void +call_connection_handler (struct MHD_Connection *connection) +{ + size_t processed; + + if (NULL != connection->response) + return; /* already queued a response */ + processed = 0; + connection->client_aware = MHD_YES; + if (MHD_NO == + connection->daemon->default_handler (connection->daemon-> default_handler_cls, + connection, + connection->url, + connection->method, + connection->version, + NULL, &processed, + &connection->client_context)) + { + /* serious internal error, close connection */ + CONNECTION_CLOSE_ERROR (connection, + "Internal application error, closing connection.\n"); + return; + } +} + + + +/** + * Call the handler of the application for this + * connection. Handles chunking of the upload + * as well as normal uploads. + * + * @param connection connection we're processing + */ +static void +process_request_body (struct MHD_Connection *connection) +{ + size_t processed; + size_t available; + size_t used; + size_t i; + int instant_retry; + int malformed; + char *buffer_head; + char *end; + + if (NULL != connection->response) + return; /* already queued a response */ + + buffer_head = connection->read_buffer; + available = connection->read_buffer_offset; + do + { + instant_retry = MHD_NO; + if ( (MHD_YES == connection->have_chunked_upload) && + (MHD_SIZE_UNKNOWN == connection->remaining_upload_size) ) + { + if ( (connection->current_chunk_offset == connection->current_chunk_size) && + (0 != connection->current_chunk_offset) && + (available >= 2) ) + { + /* skip new line at the *end* of a chunk */ + i = 0; + if ((buffer_head[i] == '\r') || (buffer_head[i] == '\n')) + i++; /* skip 1st part of line feed */ + if ((buffer_head[i] == '\r') || (buffer_head[i] == '\n')) + i++; /* skip 2nd part of line feed */ + if (i == 0) + { + /* malformed encoding */ + CONNECTION_CLOSE_ERROR (connection, + "Received malformed HTTP request (bad chunked encoding), closing connection.\n"); + return; + } + available -= i; + buffer_head += i; + connection->current_chunk_offset = 0; + connection->current_chunk_size = 0; + } + if (connection->current_chunk_offset < + connection->current_chunk_size) + { + /* we are in the middle of a chunk, give + as much as possible to the client (without + crossing chunk boundaries) */ + processed = + connection->current_chunk_size - + connection->current_chunk_offset; + if (processed > available) + processed = available; + if (available > processed) + instant_retry = MHD_YES; + } + else + { + /* we need to read chunk boundaries */ + i = 0; + while (i < available) + { + if ((buffer_head[i] == '\r') || (buffer_head[i] == '\n')) + break; + i++; + if (i >= 6) + break; + } + /* take '\n' into account; if '\n' + is the unavailable character, we + will need to wait until we have it + before going further */ + if ((i + 1 >= available) && + !((i == 1) && (available == 2) && (buffer_head[0] == '0'))) + break; /* need more data... */ + malformed = (i >= 6); + if (!malformed) + { + buffer_head[i] = '\0'; + connection->current_chunk_size = strtoul (buffer_head, &end, 16); + malformed = ('\0' != *end); + } + if (malformed) + { + /* malformed encoding */ + CONNECTION_CLOSE_ERROR (connection, + "Received malformed HTTP request (bad chunked encoding), closing connection.\n"); + return; + } + i++; + if ((i < available) && + ((buffer_head[i] == '\r') || (buffer_head[i] == '\n'))) + i++; /* skip 2nd part of line feed */ + + buffer_head += i; + available -= i; + connection->current_chunk_offset = 0; + + if (available > 0) + instant_retry = MHD_YES; + if (0 == connection->current_chunk_size) + { + connection->remaining_upload_size = 0; + break; + } + continue; + } + } + else + { + /* no chunked encoding, give all to the client */ + if ( (0 != connection->remaining_upload_size) && + (MHD_SIZE_UNKNOWN != connection->remaining_upload_size) && + (connection->remaining_upload_size < available) ) + { + processed = connection->remaining_upload_size; + } + else + { + /** + * 1. no chunked encoding, give all to the client + * 2. client may send large chunked data, but only a smaller part is available at one time. + */ + processed = available; + } + } + used = processed; + connection->client_aware = MHD_YES; + if (MHD_NO == + connection->daemon->default_handler (connection->daemon->default_handler_cls, + connection, + connection->url, + connection->method, + connection->version, + buffer_head, + &processed, + &connection->client_context)) + { + /* serious internal error, close connection */ + CONNECTION_CLOSE_ERROR (connection, + "Internal application error, closing connection.\n"); + return; + } + if (processed > used) + mhd_panic (mhd_panic_cls, __FILE__, __LINE__ +#if HAVE_MESSAGES + , "API violation" +#else + , NULL +#endif + ); + if (0 != processed) + instant_retry = MHD_NO; /* client did not process everything */ + used -= processed; + if (connection->have_chunked_upload == MHD_YES) + connection->current_chunk_offset += used; + /* dh left "processed" bytes in buffer for next time... */ + buffer_head += used; + available -= used; + if (connection->remaining_upload_size != MHD_SIZE_UNKNOWN) + connection->remaining_upload_size -= used; + } + while (MHD_YES == instant_retry); + if (available > 0) + memmove (connection->read_buffer, buffer_head, available); + connection->read_buffer_offset = available; +} + + +/** + * Try reading data from the socket into the + * read buffer of the connection. + * + * @param connection connection we're processing + * @return #MHD_YES if something changed, + * #MHD_NO if we were interrupted or if + * no space was available + */ +static int +do_read (struct MHD_Connection *connection) +{ + int bytes_read; + + if (connection->read_buffer_size == connection->read_buffer_offset) + return MHD_NO; + bytes_read = connection->recv_cls (connection, + &connection->read_buffer + [connection->read_buffer_offset], + connection->read_buffer_size - + connection->read_buffer_offset); + if (bytes_read < 0) + { + const int err = MHD_socket_errno_; + if ((EINTR == err) || (EAGAIN == err) || (EWOULDBLOCK == err)) + return MHD_NO; + if (ECONNRESET == err) + { + CONNECTION_CLOSE_ERROR (connection, NULL); + return MHD_NO; + } + CONNECTION_CLOSE_ERROR (connection, NULL); + return MHD_YES; + } + if (0 == bytes_read) + { + /* other side closed connection; RFC 2616, section 8.1.4 suggests + we should then shutdown ourselves as well. */ + connection->read_closed = MHD_YES; + MHD_connection_close (connection, + MHD_REQUEST_TERMINATED_CLIENT_ABORT); + return MHD_YES; + } + connection->read_buffer_offset += bytes_read; + return MHD_YES; +} + + +/** + * Try writing data to the socket from the + * write buffer of the connection. + * + * @param connection connection we're processing + * @return #MHD_YES if something changed, + * #MHD_NO if we were interrupted + */ +static int +do_write (struct MHD_Connection *connection) +{ + ssize_t ret; + size_t max; + + max = connection->write_buffer_append_offset - connection->write_buffer_send_offset; + ret = connection->send_cls (connection, + &connection->write_buffer + [connection->write_buffer_send_offset], + max); + + if (ret < 0) + { + const int err = MHD_socket_errno_; + if ((EINTR == err) || (EAGAIN == err) || (EWOULDBLOCK == err)) + return MHD_NO; + CONNECTION_CLOSE_ERROR (connection, NULL); + return MHD_YES; + } +#if DEBUG_SEND_DATA + fprintf (stderr, + "Sent response: `%.*s'\n", + ret, + &connection->write_buffer[connection->write_buffer_send_offset]); +#endif + /* only increment if this wasn't a "sendfile" transmission without + buffer involvement! */ + if (0 != max) + connection->write_buffer_send_offset += ret; + return MHD_YES; +} + + +/** + * Check if we are done sending the write-buffer. + * If so, transition into "next_state". + * + * @param connection connection to check write status for + * @param next_state the next state to transition to + * @return #MHD_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->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; +} + + +/** + * We have received (possibly the beginning of) a line in the + * header (or footer). Validate (check for ":") and prepare + * to process. + * + * @param connection connection we're processing + * @param line line from the header to process + * @return #MHD_YES on success, #MHD_NO on error (malformed @a line) + */ +static int +process_header_line (struct MHD_Connection *connection, char *line) +{ + char *colon; + + /* line should be normal header line, find colon */ + colon = strchr (line, ':'); + if (NULL == colon) + { + /* error in header line, die hard */ + CONNECTION_CLOSE_ERROR (connection, + "Received malformed line (no colon), closing connection.\n"); + return MHD_NO; + } + /* 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; + return MHD_YES; +} + + +/** + * Process a header value that spans multiple lines. + * The previous line(s) are in connection->last. + * + * @param connection connection we're processing + * @param line the current input line + * @param kind if the line is complete, add a header + * of the given kind + * @return #MHD_YES if the line was processed successfully + */ +static int +process_broken_line (struct MHD_Connection *connection, + char *line, enum MHD_ValueKind kind) +{ + char *last; + char *tmp; + size_t last_len; + size_t tmp_len; + + last = connection->last; + if ((line[0] == ' ') || (line[0] == '\t')) + { + /* value was continued on the next line, see + http://www.jmarshall.com/easy/http/ */ + last_len = strlen (last); + /* skip whitespace at start of 2nd line */ + tmp = line; + while ((tmp[0] == ' ') || (tmp[0] == '\t')) + tmp++; + tmp_len = strlen (tmp); + /* FIXME: we might be able to do this better (faster!), as most + likely 'last' and 'line' should already be adjacent in + memory; however, doing this right gets tricky if we have a + value continued over multiple lines (in which case we need to + record how often we have done this so we can check for + adjaency); also, in the case where these are not adjacent + (not sure how it can happen!), we would want to allocate from + the end of the pool, so as to not destroy the read-buffer's + ability to grow nicely. */ + last = MHD_pool_reallocate (connection->pool, + last, + last_len + 1, + last_len + tmp_len + 1); + if (NULL == last) + { + transmit_error_response (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + REQUEST_TOO_BIG); + return MHD_NO; + } + memcpy (&last[last_len], tmp, tmp_len + 1); + connection->last = last; + return MHD_YES; /* possibly more than 2 lines... */ + } + EXTRA_CHECK ((NULL != last) && (NULL != connection->colon)); + if ((MHD_NO == connection_add_header (connection, + last, connection->colon, kind))) + { + transmit_error_response (connection, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + REQUEST_TOO_BIG); + return MHD_NO; + } + /* we still have the current line to deal with... */ + if (0 != strlen (line)) + { + if (MHD_NO == process_header_line (connection, line)) + { + transmit_error_response (connection, + MHD_HTTP_BAD_REQUEST, REQUEST_MALFORMED); + return MHD_NO; + } + } + return MHD_YES; +} + + +/** + * Parse the various headers; figure out the size + * of the upload and make sure the headers follow + * the protocol. Advance to the appropriate state. + * + * @param connection connection we're processing + */ +static void +parse_connection_headers (struct MHD_Connection *connection) +{ + const char *clen; + MHD_UNSIGNED_LONG_LONG cval; + struct MHD_Response *response; + const char *enc; + char *end; + + parse_cookie_header (connection); + if ( (0 != (MHD_USE_PEDANTIC_CHECKS & connection->daemon->options)) && + (NULL != connection->version) && + (MHD_str_equal_caseless_(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->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 + EXTRA_CHECK (NULL == connection->response); + response = + MHD_create_response_from_buffer (strlen (REQUEST_LACKS_HOST), + REQUEST_LACKS_HOST, + MHD_RESPMEM_PERSISTENT); + MHD_queue_response (connection, MHD_HTTP_BAD_REQUEST, response); + MHD_destroy_response (response); + return; + } + + connection->remaining_upload_size = 0; + enc = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_TRANSFER_ENCODING); + if (NULL != enc) + { + connection->remaining_upload_size = MHD_SIZE_UNKNOWN; + if (MHD_str_equal_caseless_(enc, "chunked")) + connection->have_chunked_upload = MHD_YES; + } + else + { + clen = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_LENGTH); + if (NULL != clen) + { + cval = strtoul (clen, &end, 10); + if ( ('\0' != *end) || + ( (LONG_MAX == cval) && (errno == ERANGE) ) ) + { +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Failed to parse `%s' header `%s', closing connection.\n", + MHD_HTTP_HEADER_CONTENT_LENGTH, + clen); +#endif + CONNECTION_CLOSE_ERROR (connection, NULL); + return; + } + connection->remaining_upload_size = cval; + } + } +} + + +/** + * Update the 'last_activity' field of the connection to the current time + * and move the connection to the head of the 'normal_timeout' list if + * the timeout for the connection uses the default value. + * + * @param connection the connection that saw some activity + */ +static void +update_last_activity (struct MHD_Connection *connection) +{ + struct MHD_Daemon *daemon = connection->daemon; + + connection->last_activity = MHD_monotonic_time(); + if (connection->connection_timeout != daemon->connection_timeout) + return; /* custom timeout, no need to move it in "normal" DLL */ + + /* move connection to head of timeout list (by remove + add operation) */ + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + XDLL_insert (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); +} + + +/** + * This function handles a particular connection when it has been + * determined that there is data to be read off a socket. + * + * @param connection connection to handle + * @return always #MHD_YES (we should continue to process the + * connection) + */ +int +MHD_connection_handle_read (struct MHD_Connection *connection) +{ + update_last_activity (connection); + if (MHD_CONNECTION_CLOSED == connection->state) + return MHD_YES; + /* make sure "read" has a reasonable number of bytes + in buffer to use per system call (if possible) */ + if (connection->read_buffer_offset + connection->daemon->pool_increment > + connection->read_buffer_size) + try_grow_read_buffer (connection); + if (MHD_NO == do_read (connection)) + return MHD_YES; + while (1) + { +#if DEBUG_STATES + MHD_DLOG (connection->daemon, "%s: state: %s\n", + __FUNCTION__, + MHD_state_to_string (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) + { + MHD_connection_close (connection, + MHD_REQUEST_TERMINATED_READ_ERROR); + continue; + } + break; + case MHD_CONNECTION_CLOSED: + return MHD_YES; + 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; +} + + +/** + * This function was created to handle writes to sockets when it has + * been determined that the socket can be written to. + * + * @param connection connection to handle + * @return always #MHD_YES (we should continue to process the + * connection) + */ +int +MHD_connection_handle_write (struct MHD_Connection *connection) +{ + struct MHD_Response *response; + ssize_t ret; + + update_last_activity (connection); + while (1) + { +#if DEBUG_STATES + MHD_DLOG (connection->daemon, "%s: state: %s\n", + __FUNCTION__, + MHD_state_to_string (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 = connection->send_cls (connection, + &HTTP_100_CONTINUE + [connection->continue_message_write_offset], + strlen (HTTP_100_CONTINUE) - + connection->continue_message_write_offset); + if (ret < 0) + { + const int err = MHD_socket_errno_; + if ((err == EINTR) || (err == EAGAIN) || (EWOULDBLOCK == err)) + break; +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Failed to send data: %s\n", + MHD_socket_last_strerr_ ()); +#endif + CONNECTION_CLOSE_ERROR (connection, NULL); + return MHD_YES; + } +#if DEBUG_SEND_DATA + fprintf (stderr, + "Sent 100 continue response: `%.*s'\n", + (int) ret, + &HTTP_100_CONTINUE[connection->continue_message_write_offset]); +#endif + 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); + if (connection->state != MHD_CONNECTION_HEADERS_SENDING) + break; + 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 (NULL != response->crc) + (void) MHD_mutex_lock_ (&response->mutex); + if (MHD_YES != try_ready_normal_body (connection)) + break; + ret = connection->send_cls (connection, + &response->data + [connection->response_write_position + - response->data_start], + response->data_size - + (connection->response_write_position + - response->data_start)); + const int err = MHD_socket_errno_; +#if DEBUG_SEND_DATA + if (ret > 0) + fprintf (stderr, + "Sent DATA response: `%.*s'\n", + (int) ret, + &response->data[connection->response_write_position - + response->data_start]); +#endif + if (NULL != response->crc) + (void) MHD_mutex_unlock_ (&response->mutex); + if (ret < 0) + { + if ((err == EINTR) || (err == EAGAIN) || (EWOULDBLOCK == err)) + return MHD_YES; +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Failed to send data: %s\n", + MHD_socket_last_strerr_ ()); +#endif + CONNECTION_CLOSE_ERROR (connection, NULL); + return MHD_YES; + } + 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); + if (MHD_CONNECTION_CHUNKED_BODY_READY != connection->state) + break; + 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); + if (connection->state != MHD_CONNECTION_FOOTERS_SENDING) + break; + check_write_done (connection, MHD_CONNECTION_FOOTERS_SENT); + break; + case MHD_CONNECTION_FOOTERS_SENT: + EXTRA_CHECK (0); + break; + case MHD_CONNECTION_CLOSED: + return MHD_YES; + case MHD_TLS_CONNECTION_INIT: + EXTRA_CHECK (0); + break; + default: + EXTRA_CHECK (0); + CONNECTION_CLOSE_ERROR (connection, + "Internal error\n"); + return MHD_YES; + } + break; + } + return MHD_YES; +} + + +/** + * Clean up the state of the given connection and move it into the + * clean up queue for final disposal. + * + * @param connection handle for the connection to clean up + */ +static void +cleanup_connection (struct MHD_Connection *connection) +{ + struct MHD_Daemon *daemon = connection->daemon; + + if (NULL != connection->response) + { + MHD_destroy_response (connection->response); + connection->response = NULL; + } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + if (connection->connection_timeout == daemon->connection_timeout) + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + else + XDLL_remove (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + connection); + if (MHD_YES == connection->suspended) + DLL_remove (daemon->suspended_connections_head, + daemon->suspended_connections_tail, + connection); + else + DLL_remove (daemon->connections_head, + daemon->connections_tail, + connection); + DLL_insert (daemon->cleanup_head, + daemon->cleanup_tail, + connection); + connection->suspended = MHD_NO; + connection->resuming = MHD_NO; + connection->in_idle = MHD_NO; + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_(&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); +} + + +/** + * This function was created to handle per-connection processing that + * has to happen even if the socket cannot be read or written to. + * + * @param connection connection to handle + * @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) +{ + struct MHD_Daemon *daemon = connection->daemon; + unsigned int timeout; + const char *end; + char *line; + int client_close; + + connection->in_idle = MHD_YES; + while (1) + { +#if DEBUG_STATES + MHD_DLOG (daemon, + "%s: state: %s\n", + __FUNCTION__, + MHD_state_to_string (connection->state)); +#endif + switch (connection->state) + { + case MHD_CONNECTION_INIT: + line = get_next_header_line (connection); + if (NULL == line) + { + if (MHD_CONNECTION_INIT != connection->state) + continue; + if (MHD_YES == connection->read_closed) + { + CONNECTION_CLOSE_ERROR (connection, + NULL); + continue; + } + break; + } + if (MHD_NO == parse_initial_message_line (connection, line)) + CONNECTION_CLOSE_ERROR (connection, NULL); + else + connection->state = MHD_CONNECTION_URL_RECEIVED; + continue; + case MHD_CONNECTION_URL_RECEIVED: + line = get_next_header_line (connection); + if (NULL == line) + { + if (MHD_CONNECTION_URL_RECEIVED != connection->state) + continue; + if (MHD_YES == connection->read_closed) + { + CONNECTION_CLOSE_ERROR (connection, + NULL); + continue; + } + break; + } + if (strlen (line) == 0) + { + connection->state = MHD_CONNECTION_HEADERS_RECEIVED; + continue; + } + if (MHD_NO == process_header_line (connection, line)) + { + transmit_error_response (connection, + MHD_HTTP_BAD_REQUEST, + REQUEST_MALFORMED); + break; + } + connection->state = MHD_CONNECTION_HEADER_PART_RECEIVED; + continue; + case MHD_CONNECTION_HEADER_PART_RECEIVED: + line = get_next_header_line (connection); + if (NULL == line) + { + if (connection->state != MHD_CONNECTION_HEADER_PART_RECEIVED) + continue; + if (MHD_YES == connection->read_closed) + { + CONNECTION_CLOSE_ERROR (connection, + NULL); + continue; + } + break; + } + if (MHD_NO == + process_broken_line (connection, line, MHD_HEADER_KIND)) + continue; + if (0 == strlen (line)) + { + connection->state = MHD_CONNECTION_HEADERS_RECEIVED; + continue; + } + continue; + case MHD_CONNECTION_HEADERS_RECEIVED: + parse_connection_headers (connection); + if (MHD_CONNECTION_CLOSED == connection->state) + continue; + connection->state = MHD_CONNECTION_HEADERS_PROCESSED; + continue; + case MHD_CONNECTION_HEADERS_PROCESSED: + call_connection_handler (connection); /* first call */ + if (MHD_CONNECTION_CLOSED == connection->state) + continue; + if (need_100_continue (connection)) + { + connection->state = MHD_CONNECTION_CONTINUE_SENDING; + break; + } + if ( (NULL != connection->response) && + ( (MHD_str_equal_caseless_ (connection->method, + MHD_HTTP_METHOD_POST)) || + (MHD_str_equal_caseless_ (connection->method, + MHD_HTTP_METHOD_PUT))) ) + { + /* we refused (no upload allowed!) */ + connection->remaining_upload_size = 0; + /* force close, in case client still tries to upload... */ + connection->read_closed = MHD_YES; + } + connection->state = (0 == connection->remaining_upload_size) + ? 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 (0 != connection->read_buffer_offset) + { + process_request_body (connection); /* loop call */ + if (MHD_CONNECTION_CLOSED == connection->state) + continue; + } + if ((0 == connection->remaining_upload_size) || + ((connection->remaining_upload_size == MHD_SIZE_UNKNOWN) && + (0 == connection->read_buffer_offset) && + (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 (NULL == line) + { + if (connection->state != MHD_CONNECTION_BODY_RECEIVED) + continue; + if (MHD_YES == connection->read_closed) + { + CONNECTION_CLOSE_ERROR (connection, + NULL); + continue; + } + break; + } + if (0 == strlen (line)) + { + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + continue; + } + if (MHD_NO == process_header_line (connection, line)) + { + transmit_error_response (connection, + MHD_HTTP_BAD_REQUEST, + REQUEST_MALFORMED); + break; + } + connection->state = MHD_CONNECTION_FOOTER_PART_RECEIVED; + continue; + case MHD_CONNECTION_FOOTER_PART_RECEIVED: + line = get_next_header_line (connection); + if (NULL == line) + { + if (connection->state != MHD_CONNECTION_FOOTER_PART_RECEIVED) + continue; + if (MHD_YES == connection->read_closed) + { + CONNECTION_CLOSE_ERROR (connection, + NULL); + continue; + } + break; + } + if (MHD_NO == + process_broken_line (connection, line, MHD_FOOTER_KIND)) + continue; + if (0 == strlen (line)) + { + 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 (NULL == connection->response) + break; /* try again next time */ + if (MHD_NO == build_header_response (connection)) + { + /* oops - close! */ + CONNECTION_CLOSE_ERROR (connection, + "Closing connection (failed to create response header)\n"); + continue; + } + connection->state = MHD_CONNECTION_HEADERS_SENDING; + +#if HAVE_DECL_TCP_CORK + /* starting header send, set TCP cork */ + { + const int val = 1; + setsockopt (connection->socket_fd, IPPROTO_TCP, TCP_CORK, &val, + sizeof (val)); + } +#endif + 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 (NULL != connection->response->crc) + (void) MHD_mutex_lock_ (&connection->response->mutex); + if (0 == connection->response->total_size) + { + if (NULL != connection->response->crc) + (void) MHD_mutex_unlock_ (&connection->response->mutex); + connection->state = MHD_CONNECTION_BODY_SENT; + continue; + } + if (MHD_YES == try_ready_normal_body (connection)) + { + if (NULL != connection->response->crc) + (void) MHD_mutex_unlock_ (&connection->response->mutex); + connection->state = MHD_CONNECTION_NORMAL_BODY_READY; + break; + } + /* not ready, no socket action */ + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + /* nothing to do here */ + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + if (NULL != connection->response->crc) + (void) MHD_mutex_lock_ (&connection->response->mutex); + if (0 == connection->response->total_size) + { + if (NULL != connection->response->crc) + (void) MHD_mutex_unlock_ (&connection->response->mutex); + connection->state = MHD_CONNECTION_BODY_SENT; + continue; + } + if (MHD_YES == try_ready_chunked_body (connection)) + { + if (NULL != connection->response->crc) + (void) MHD_mutex_unlock_ (&connection->response->mutex); + connection->state = MHD_CONNECTION_CHUNKED_BODY_READY; + continue; + } + if (NULL != connection->response->crc) + (void) MHD_mutex_unlock_ (&connection->response->mutex); + break; + case MHD_CONNECTION_BODY_SENT: + if (MHD_NO == build_header_response (connection)) + { + /* oops - close! */ + CONNECTION_CLOSE_ERROR (connection, + "Closing connection (failed to create response header)\n"); + continue; + } + if ( (MHD_NO == connection->have_chunked_upload) || + (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: +#if HAVE_DECL_TCP_CORK + /* done sending, uncork */ + { + const int val = 0; + setsockopt (connection->socket_fd, IPPROTO_TCP, TCP_CORK, &val, + sizeof (val)); + } +#endif + end = + MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_CONNECTION); + client_close = ((NULL != end) && (MHD_str_equal_caseless_(end, "close"))); + MHD_destroy_response (connection->response); + connection->response = NULL; + if ( (NULL != daemon->notify_completed) && + (MHD_YES == connection->client_aware) ) + { + daemon->notify_completed (daemon->notify_completed_cls, + connection, + &connection->client_context, + MHD_REQUEST_TERMINATED_COMPLETED_OK); + connection->client_aware = MHD_NO; + } + end = + MHD_lookup_connection_value (connection, MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONNECTION); + if ( (MHD_YES == connection->read_closed) || + (client_close) || + ((NULL != end) && (MHD_str_equal_caseless_ (end, "close"))) ) + { + connection->read_closed = MHD_YES; + connection->read_buffer_offset = 0; + } + if (((MHD_YES == connection->read_closed) && + (0 == connection->read_buffer_offset)) || + (MHD_NO == keepalive_possible (connection))) + { + /* have to close for some reason */ + MHD_connection_close (connection, + MHD_REQUEST_TERMINATED_COMPLETED_OK); + MHD_pool_destroy (connection->pool); + connection->pool = NULL; + connection->read_buffer = NULL; + connection->read_buffer_size = 0; + connection->read_buffer_offset = 0; + } + else + { + /* can try to keep-alive */ + connection->version = NULL; + connection->state = MHD_CONNECTION_INIT; + connection->read_buffer + = MHD_pool_reset (connection->pool, + connection->read_buffer, + connection->read_buffer_size); + } + connection->client_aware = MHD_NO; + connection->client_context = NULL; + connection->continue_message_write_offset = 0; + connection->responseCode = 0; + connection->headers_received = NULL; + connection->headers_received_tail = 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; + continue; + case MHD_CONNECTION_CLOSED: + cleanup_connection (connection); + return MHD_NO; + default: + EXTRA_CHECK (0); + break; + } + break; + } + timeout = connection->connection_timeout; + if ( (0 != timeout) && + (timeout <= (MHD_monotonic_time() - connection->last_activity)) ) + { + MHD_connection_close (connection, MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); + connection->in_idle = MHD_NO; + return MHD_YES; + } + MHD_connection_update_event_loop_info (connection); +#if EPOLL_SUPPORT + switch (connection->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + if ( (0 != (connection->epoll_state & MHD_EPOLL_STATE_READ_READY)) && + (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) && + (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL)) ) + { + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + connection); + connection->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL; + } + break; + case MHD_EVENT_LOOP_INFO_WRITE: + if ( (0 != (connection->epoll_state & MHD_EPOLL_STATE_WRITE_READY)) && + (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) && + (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL)) ) + { + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + connection); + connection->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL; + } + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + /* we should look at this connection again in the next iteration + of the event loop, as we're waiting on the application */ + if ( (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) && + (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED))) ) + { + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + connection); + connection->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL; + } + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* This connection is finished, nothing left to do */ + break; + } + return MHD_connection_epoll_update_ (connection); +#else + return MHD_YES; +#endif +} + + +#if EPOLL_SUPPORT +/** + * Perform epoll() processing, possibly moving the connection back into + * the epoll() set if needed. + * + * @param connection connection to process + * @return #MHD_YES if we should continue to process the + * connection (not dead yet), #MHD_NO if it died + */ +int +MHD_connection_epoll_update_ (struct MHD_Connection *connection) +{ + struct MHD_Daemon *daemon = connection->daemon; + + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (0 == (connection->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET)) && + (0 == (connection->epoll_state & MHD_EPOLL_STATE_SUSPENDED)) && + ( (0 == (connection->epoll_state & MHD_EPOLL_STATE_WRITE_READY)) || + ( (0 == (connection->epoll_state & MHD_EPOLL_STATE_READ_READY)) && + ( (MHD_EVENT_LOOP_INFO_READ == connection->event_loop_info) || + (connection->read_buffer_size > connection->read_buffer_offset) ) && + (MHD_NO == connection->read_closed) ) ) ) + { + /* add to epoll set */ + struct epoll_event event; + + event.events = EPOLLIN | EPOLLOUT | EPOLLET; + event.data.ptr = connection; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + connection->socket_fd, + &event)) + { +#if HAVE_MESSAGES + if (0 != (daemon->options & MHD_USE_DEBUG)) + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + connection->state = MHD_CONNECTION_CLOSED; + cleanup_connection (connection); + return MHD_NO; + } + connection->epoll_state |= MHD_EPOLL_STATE_IN_EPOLL_SET; + } + connection->in_idle = MHD_NO; + return MHD_YES; +} +#endif + + +/** + * Set callbacks for this connection to those for HTTP. + * + * @param connection connection to initialize + */ +void +MHD_set_http_callbacks_ (struct MHD_Connection *connection) +{ + connection->read_handler = &MHD_connection_handle_read; + connection->write_handler = &MHD_connection_handle_write; + connection->idle_handler = &MHD_connection_handle_idle; +} + + +/** + * Obtain information about the given connection. + * + * @param connection what connection to get information about + * @param info_type what information is desired? + * @param ... depends on @a info_type + * @return NULL if this information is not available + * (or if the @a info_type is unknown) + * @ingroup specialized + */ +const union MHD_ConnectionInfo * +MHD_get_connection_info (struct MHD_Connection *connection, + enum MHD_ConnectionInfoType info_type, ...) +{ + switch (info_type) + { +#if HTTPS_SUPPORT + case MHD_CONNECTION_INFO_CIPHER_ALGO: + if (connection->tls_session == NULL) + return NULL; + connection->cipher = gnutls_cipher_get (connection->tls_session); + return (const union MHD_ConnectionInfo *) &connection->cipher; + case MHD_CONNECTION_INFO_PROTOCOL: + if (connection->tls_session == NULL) + return NULL; + connection->protocol = gnutls_protocol_get_version (connection->tls_session); + return (const union MHD_ConnectionInfo *) &connection->protocol; + case MHD_CONNECTION_INFO_GNUTLS_SESSION: + if (connection->tls_session == NULL) + return NULL; + return (const union MHD_ConnectionInfo *) &connection->tls_session; +#endif + case MHD_CONNECTION_INFO_CLIENT_ADDRESS: + return (const union MHD_ConnectionInfo *) &connection->addr; + case MHD_CONNECTION_INFO_DAEMON: + return (const union MHD_ConnectionInfo *) &connection->daemon; + case MHD_CONNECTION_INFO_CONNECTION_FD: + return (const union MHD_ConnectionInfo *) &connection->socket_fd; + case MHD_CONNECTION_INFO_SOCKET_CONTEXT: + return (const union MHD_ConnectionInfo *) &connection->socket_context; + default: + return NULL; + }; +} + + +/** + * Set a custom option for the given connection, overriding defaults. + * + * @param connection connection to modify + * @param option option to set + * @param ... arguments to the option, depending on the option type + * @return #MHD_YES on success, #MHD_NO if setting the option failed + * @ingroup specialized + */ +int +MHD_set_connection_option (struct MHD_Connection *connection, + enum MHD_CONNECTION_OPTION option, + ...) +{ + va_list ap; + struct MHD_Daemon *daemon; + + daemon = connection->daemon; + switch (option) + { + case MHD_CONNECTION_OPTION_TIMEOUT: + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + if (MHD_YES != connection->suspended) + { + if (connection->connection_timeout == daemon->connection_timeout) + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + else + XDLL_remove (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + connection); + } + va_start (ap, option); + connection->connection_timeout = va_arg (ap, unsigned int); + va_end (ap); + if (MHD_YES != connection->suspended) + { + if (connection->connection_timeout == daemon->connection_timeout) + XDLL_insert (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + else + XDLL_insert (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + connection); + } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); + return MHD_YES; + default: + return MHD_NO; + } +} + + +/** + * Queue a response to be transmitted to the client (as soon as + * possible but after #MHD_AccessHandlerCallback returns). + * + * @param connection the connection identifying the client + * @param status_code HTTP status code (i.e. #MHD_HTTP_OK) + * @param response response to transmit + * @return #MHD_NO on error (i.e. reply already sent), + * #MHD_YES on success or if message has been queued + * @ingroup response + */ +int +MHD_queue_response (struct MHD_Connection *connection, + unsigned int status_code, + struct MHD_Response *response) +{ + if ( (NULL == connection) || + (NULL == response) || + (NULL != connection->response) || + ( (MHD_CONNECTION_HEADERS_PROCESSED != connection->state) && + (MHD_CONNECTION_FOOTERS_RECEIVED != connection->state) ) ) + return MHD_NO; + MHD_increment_response_rc (response); + connection->response = response; + connection->responseCode = status_code; + if ( (NULL != connection->method) && + (MHD_str_equal_caseless_ (connection->method, MHD_HTTP_METHOD_HEAD)) ) + { + /* if this is a "HEAD" request, pretend that we + have already sent the full message body */ + connection->response_write_position = response->total_size; + } + if ( (MHD_CONNECTION_HEADERS_PROCESSED == connection->state) && + (NULL != connection->method) && + ( (MHD_str_equal_caseless_ (connection->method, + MHD_HTTP_METHOD_POST)) || + (MHD_str_equal_caseless_ (connection->method, + MHD_HTTP_METHOD_PUT))) ) + { + /* response was queued "early", refuse to read body / footers or + further requests! */ + connection->read_closed = MHD_YES; + connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; + } + if (MHD_NO == connection->in_idle) + (void) MHD_connection_handle_idle (connection); + return MHD_YES; +} + + +/* end of connection.c */ |