aboutsummaryrefslogtreecommitdiff
path: root/src/microhttpd/connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/microhttpd/connection.c')
-rw-r--r--src/microhttpd/connection.c2919
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>&quot;Host:&quot; header required</title></head><body>In HTTP 1.1, requests must include a &quot;Host:&quot; 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 */