/*
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 (SSL_want_read (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 = SSL_CIPHER_get_name (SSL_get_current_cipher (connection->tls_session));
return (const union MHD_ConnectionInfo *) &connection->cipher;
case MHD_CONNECTION_INFO_PROTOCOL:
if (connection->tls_session == NULL)
return NULL;
connection->protocol = SSL_CIPHER_get_version (SSL_get_current_cipher (connection->tls_session));
return (const union MHD_ConnectionInfo *) &connection->protocol;
case MHD_CONNECTION_INFO_TLS_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 */