mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
1386 lines
44 KiB
C
1386 lines
44 KiB
C
/*
|
|
This file is part of libmicrohttpd
|
|
(C) 2007 Daniel Pittman and Christian Grothoff
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
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 "connection.h"
|
|
#include "memorypool.h"
|
|
#include "response.h"
|
|
#include "reason_phrase.h"
|
|
|
|
#ifndef LINUX
|
|
#ifndef MSG_NOSIGNAL
|
|
#define MSG_NOSIGNAL 0
|
|
#endif
|
|
#endif
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
#define REQUEST_TOO_BIG ""
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
#define REQUEST_LACKS_HOST ""
|
|
|
|
/**
|
|
* 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 iterator callback to call on each header;
|
|
* maybe NULL (then just count headers)
|
|
* @param iterator_cls extra argument to iterator
|
|
* @return number of entries iterated over
|
|
*/
|
|
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 (connection == NULL)
|
|
return -1;
|
|
ret = 0;
|
|
pos = connection->headers_received;
|
|
while (pos != NULL)
|
|
{
|
|
if (0 != (pos->kind & kind))
|
|
{
|
|
ret++;
|
|
if ((iterator != NULL) &&
|
|
(MHD_YES != iterator (iterator_cls,
|
|
kind, pos->header, pos->value)))
|
|
return ret;
|
|
}
|
|
pos = pos->next;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get a particular header value. If multiple
|
|
* values match the kind, return any one of them.
|
|
*
|
|
* @param key the header to look for
|
|
* @return NULL if no such item was found
|
|
*/
|
|
const char *
|
|
MHD_lookup_connection_value (struct MHD_Connection *connection,
|
|
enum MHD_ValueKind kind, const char *key)
|
|
{
|
|
struct MHD_HTTP_Header *pos;
|
|
|
|
if (connection == NULL)
|
|
return NULL;
|
|
pos = connection->headers_received;
|
|
while (pos != NULL)
|
|
{
|
|
if ((0 != (pos->kind & kind)) && (0 == strcasecmp (key, pos->header)))
|
|
return pos->value;
|
|
pos = pos->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Queue a response to be transmitted to the client (as soon as
|
|
* possible).
|
|
*
|
|
* @param connection the connection identifying the client
|
|
* @param status_code HTTP status code (i.e. 200 for 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
|
|
*/
|
|
int
|
|
MHD_queue_response (struct MHD_Connection *connection,
|
|
unsigned int status_code, struct MHD_Response *response)
|
|
{
|
|
if ((connection == NULL) ||
|
|
(response == NULL) ||
|
|
(connection->response != NULL) ||
|
|
(connection->have_received_body == MHD_NO) || (connection->have_received_headers == MHD_NO))
|
|
return MHD_NO;
|
|
MHD_increment_response_rc (response);
|
|
connection->response = response;
|
|
connection->responseCode = status_code;
|
|
if ((connection->method != NULL) &&
|
|
(0 == strcasecmp (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;
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Do we (still) need to send a 100 continue
|
|
* message for this connection?
|
|
*/
|
|
static int
|
|
MHD_need_100_continue (struct MHD_Connection *connection)
|
|
{
|
|
const char *expect;
|
|
|
|
return ((connection->version != NULL) &&
|
|
(0 == strcasecmp (connection->version,
|
|
MHD_HTTP_VERSION_1_1)) &&
|
|
(connection->have_received_headers == MHD_YES) &&
|
|
(NULL != (expect = MHD_lookup_connection_value (connection,
|
|
MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_EXPECT)))
|
|
&& (0 == strcasecmp (expect, "100-continue"))
|
|
&& (connection->continue_message_write_offset < strlen (HTTP_100_CONTINUE)));
|
|
}
|
|
|
|
/**
|
|
* A serious error occured, close the
|
|
* connection (and notify the application).
|
|
*/
|
|
static void
|
|
connection_close_error (struct MHD_Connection *connection)
|
|
{
|
|
SHUTDOWN (connection->socket_fd, SHUT_RDWR);
|
|
CLOSE (connection->socket_fd);
|
|
connection->socket_fd = -1;
|
|
if (connection->daemon->notify_completed != NULL)
|
|
connection->daemon->notify_completed (connection->daemon->
|
|
notify_completed_cls, connection,
|
|
&connection->client_context,
|
|
MHD_REQUEST_TERMINATED_WITH_ERROR);
|
|
}
|
|
|
|
/**
|
|
* Prepare the response buffer of this connection for
|
|
* sending. Assumes that the response mutex is
|
|
* already held. If the transmission is complete,
|
|
* this function may close the socket (and return
|
|
* MHD_NO).
|
|
*
|
|
* @return MHD_NO if readying the response failed
|
|
*/
|
|
static int
|
|
ready_response (struct MHD_Connection *connection)
|
|
{
|
|
int ret;
|
|
struct MHD_Response *response;
|
|
|
|
response = connection->response;
|
|
ret = response->crc (response->crc_cls,
|
|
connection->response_write_position,
|
|
response->data,
|
|
MIN (response->data_buffer_size,
|
|
response->total_size - connection->response_write_position));
|
|
if (ret == -1)
|
|
{
|
|
/* end of message, signal other side by closing! */
|
|
#if DEBUG_CLOSE
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon, "Closing connection (end of response)\n");
|
|
#endif
|
|
#endif
|
|
response->total_size = connection->response_write_position;
|
|
connection_close_error (connection);
|
|
return MHD_NO;
|
|
}
|
|
response->data_start = connection->response_write_position;
|
|
response->data_size = ret;
|
|
if (ret == 0)
|
|
{
|
|
/* avoid busy-waiting when using external select
|
|
(we assume that the main application will
|
|
wake up the external select once more data
|
|
is ready). With internal selects, we
|
|
have no choice; if the app uses a thread
|
|
per connection, ret==0 is likely a bug --
|
|
the application should block until data
|
|
is ready! */
|
|
if ((0 ==
|
|
(connection->daemon->
|
|
options & (MHD_USE_SELECT_INTERNALLY |
|
|
MHD_USE_THREAD_PER_CONNECTION))))
|
|
connection->response_unready = MHD_YES;
|
|
return MHD_NO;
|
|
}
|
|
connection->response_unready = MHD_NO;
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Obtain the select sets for this connection
|
|
*
|
|
* @return MHD_YES on success
|
|
*/
|
|
int
|
|
MHD_connection_get_fdset (struct MHD_Connection *connection,
|
|
fd_set * read_fd_set,
|
|
fd_set * write_fd_set,
|
|
fd_set * except_fd_set, int *max_fd)
|
|
{
|
|
int fd;
|
|
void *buf;
|
|
|
|
fd = connection->socket_fd;
|
|
if (fd == -1)
|
|
return MHD_YES;
|
|
if ((connection->read_close == MHD_NO) &&
|
|
((connection->have_received_headers == MHD_NO) ||
|
|
(connection->read_buffer_offset < connection->read_buffer_size)))
|
|
{
|
|
FD_SET (fd, read_fd_set);
|
|
if (fd > *max_fd)
|
|
*max_fd = fd;
|
|
}
|
|
else
|
|
{
|
|
if ((connection->read_close == MHD_NO) &&
|
|
((connection->have_received_headers == MHD_YES) &&
|
|
(connection->read_buffer_offset == connection->read_buffer_size)))
|
|
{
|
|
/* try growing the read buffer, just in case */
|
|
buf = MHD_pool_reallocate (connection->pool,
|
|
connection->read_buffer,
|
|
connection->read_buffer_size,
|
|
connection->read_buffer_size * 2 +
|
|
MHD_BUF_INC_SIZE);
|
|
if (buf != NULL)
|
|
{
|
|
/* we can actually grow the buffer, do it! */
|
|
connection->read_buffer = buf;
|
|
connection->read_buffer_size =
|
|
connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE;
|
|
FD_SET (fd, read_fd_set);
|
|
if (fd > *max_fd)
|
|
*max_fd = fd;
|
|
}
|
|
}
|
|
}
|
|
if ((connection->response != NULL) &&
|
|
(connection->response_unready == MHD_YES))
|
|
{
|
|
pthread_mutex_lock (&connection->response->mutex);
|
|
ready_response (connection);
|
|
pthread_mutex_unlock (&connection->response->mutex);
|
|
}
|
|
if (((connection->response != NULL) &&
|
|
(connection->response_unready == MHD_NO)) ||
|
|
MHD_need_100_continue (connection))
|
|
{
|
|
FD_SET (fd, write_fd_set);
|
|
if (fd > *max_fd)
|
|
*max_fd = fd;
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* We ran out of memory processing the
|
|
* header. Handle it properly by stopping to read data
|
|
* and sending a HTTP 413 or HTTP 414 response.
|
|
*
|
|
* @param status_code the response code to send (413 or 414)
|
|
*/
|
|
static void
|
|
MHD_excessive_data_handler (struct MHD_Connection *connection,
|
|
unsigned int status_code)
|
|
{
|
|
struct MHD_Response *response;
|
|
|
|
/* die, header far too long to be reasonable */
|
|
connection->read_close = MHD_YES;
|
|
connection->have_received_headers = MHD_YES;
|
|
connection->have_received_body = MHD_YES;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Received excessively long header, closing connection.\n");
|
|
#endif
|
|
response = MHD_create_response_from_data (strlen (REQUEST_TOO_BIG),
|
|
REQUEST_TOO_BIG, MHD_NO, MHD_NO);
|
|
MHD_queue_response (connection, status_code, response);
|
|
MHD_destroy_response (response);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static char *
|
|
MHD_get_next_header_line (struct MHD_Connection *connection)
|
|
{
|
|
char *rbuf;
|
|
size_t pos;
|
|
|
|
if (connection->read_buffer_offset == 0)
|
|
return NULL;
|
|
pos = 0;
|
|
rbuf = connection->read_buffer;
|
|
while ((pos < connection->read_buffer_offset - 1) &&
|
|
(rbuf[pos] != '\r') && (rbuf[pos] != '\n'))
|
|
pos++;
|
|
if (pos == connection->read_buffer_offset - 1)
|
|
{
|
|
/* not found, consider growing... */
|
|
if (connection->read_buffer_offset == connection->read_buffer_size)
|
|
{
|
|
rbuf = MHD_pool_reallocate (connection->pool,
|
|
connection->read_buffer,
|
|
connection->read_buffer_size,
|
|
connection->read_buffer_size * 2 +
|
|
MHD_BUF_INC_SIZE);
|
|
if (rbuf == NULL)
|
|
{
|
|
MHD_excessive_data_handler (connection,
|
|
(connection->url != NULL)
|
|
? MHD_HTTP_REQUEST_ENTITY_TOO_LARGE
|
|
: MHD_HTTP_REQUEST_URI_TOO_LONG);
|
|
}
|
|
else
|
|
{
|
|
connection->read_buffer_size =
|
|
connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE;
|
|
connection->read_buffer = rbuf;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
/* found, check if we have proper CRLF */
|
|
if ((rbuf[pos] == '\r') && (rbuf[pos + 1] == '\n'))
|
|
rbuf[pos++] = '\0'; /* skip both r and n */
|
|
rbuf[pos++] = '\0';
|
|
connection->read_buffer += pos;
|
|
connection->read_buffer_size -= pos;
|
|
connection->read_buffer_offset -= pos;
|
|
return rbuf;
|
|
}
|
|
|
|
/**
|
|
* @return MHD_NO on failure (out of memory), MHD_YES for success
|
|
*/
|
|
static int
|
|
MHD_connection_add_header (struct MHD_Connection *connection,
|
|
char *key, char *value, enum MHD_ValueKind kind)
|
|
{
|
|
struct MHD_HTTP_Header *hdr;
|
|
|
|
hdr = MHD_pool_allocate (connection->pool,
|
|
sizeof (struct MHD_HTTP_Header), MHD_YES);
|
|
if (hdr == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Not enough memory to allocate header record!\n");
|
|
#endif
|
|
MHD_excessive_data_handler (connection,
|
|
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
|
return MHD_NO;
|
|
}
|
|
hdr->next = connection->headers_received;
|
|
hdr->header = key;
|
|
hdr->value = value;
|
|
hdr->kind = kind;
|
|
connection->headers_received = hdr;
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* @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 (args != NULL)
|
|
{
|
|
equals = strstr (args, "=");
|
|
if (equals == NULL)
|
|
return MHD_NO; /* invalid, ignore */
|
|
equals[0] = '\0';
|
|
equals++;
|
|
amper = strstr (equals, "&");
|
|
if (amper != NULL)
|
|
{
|
|
amper[0] = '\0';
|
|
amper++;
|
|
}
|
|
MHD_http_unescape (args);
|
|
MHD_http_unescape (equals);
|
|
if (MHD_NO == MHD_connection_add_header (connection,
|
|
args, equals, kind))
|
|
return MHD_NO;
|
|
args = amper;
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Parse the cookie header (see RFC 2109).
|
|
*
|
|
* @return MHD_YES for success, MHD_NO for failure (malformed, out of memory)
|
|
*/
|
|
static int
|
|
MHD_parse_cookie_header (struct MHD_Connection *connection)
|
|
{
|
|
const char *hdr;
|
|
char *cpy;
|
|
char *pos;
|
|
char *semicolon;
|
|
char *equals;
|
|
int quotes;
|
|
|
|
hdr = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Cookie");
|
|
if (hdr == NULL)
|
|
return MHD_YES;
|
|
cpy = MHD_pool_allocate (connection->pool, strlen (hdr) + 1, MHD_YES);
|
|
if (cpy == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon, "Not enough memory to parse cookies!\n");
|
|
#endif
|
|
MHD_excessive_data_handler (connection,
|
|
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
|
return MHD_NO;
|
|
}
|
|
memcpy (cpy, hdr, strlen (hdr) + 1);
|
|
pos = cpy;
|
|
while (pos != NULL)
|
|
{
|
|
equals = strstr (pos, "=");
|
|
if (equals == NULL)
|
|
break;
|
|
equals[0] = '\0';
|
|
equals++;
|
|
quotes = 0;
|
|
semicolon = equals;
|
|
while ((semicolon[0] != '\0') &&
|
|
((quotes != 0) ||
|
|
((semicolon[0] != ';') && (semicolon[0] != ','))))
|
|
{
|
|
if (semicolon[0] == '"')
|
|
quotes = (quotes + 1) & 1;
|
|
semicolon++;
|
|
}
|
|
if (semicolon[0] == '\0')
|
|
semicolon = NULL;
|
|
if (semicolon != NULL)
|
|
{
|
|
semicolon[0] = '\0';
|
|
semicolon++;
|
|
}
|
|
/* remove quotes */
|
|
if ((equals[0] == '"') && (equals[strlen (equals) - 1] == '"'))
|
|
{
|
|
equals[strlen (equals) - 1] = '\0';
|
|
equals++;
|
|
}
|
|
if (MHD_NO == MHD_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 *httpVersion;
|
|
char *args;
|
|
|
|
uri = strstr (line, " ");
|
|
if (uri == NULL)
|
|
return MHD_NO; /* serious error */
|
|
uri[0] = '\0';
|
|
connection->method = line;
|
|
uri++;
|
|
while (uri[0] == ' ')
|
|
uri++;
|
|
httpVersion = strstr (uri, " ");
|
|
if (httpVersion != NULL)
|
|
{
|
|
httpVersion[0] = '\0';
|
|
httpVersion++;
|
|
}
|
|
args = strstr (uri, "?");
|
|
if (args != NULL)
|
|
{
|
|
args[0] = '\0';
|
|
args++;
|
|
parse_arguments (MHD_GET_ARGUMENT_KIND, connection, args);
|
|
}
|
|
connection->url = uri;
|
|
if (httpVersion == NULL)
|
|
connection->version = "";
|
|
else
|
|
connection->version = httpVersion;
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* This function is designed to parse the input buffer of a given
|
|
* connection for HTTP headers -- and in the case of chunked encoding,
|
|
* also for HTTP "footers".
|
|
*
|
|
* Once the header is complete, it should have set the
|
|
* headers_received, url and method values and set
|
|
* have_received_headers to MHD_YES. If no body is expected, it
|
|
* should also set "have_received_body" to MHD_YES. Otherwise, it
|
|
* should set "remaining_upload_size" to the expected size of the
|
|
* body. If the size of the body is unknown, it should be set to -1.
|
|
*/
|
|
static void
|
|
MHD_parse_connection_headers (struct MHD_Connection *connection)
|
|
{
|
|
char *last;
|
|
char *line;
|
|
char *colon;
|
|
char *tmp;
|
|
const char *clen;
|
|
unsigned long long cval;
|
|
struct MHD_Response *response;
|
|
|
|
if ( ( (connection->have_received_body == MHD_YES) &&
|
|
(connection->have_chunked_upload == MHD_NO) ) ||
|
|
(connection->have_received_headers == MHD_YES) )
|
|
abort ();
|
|
colon = NULL; /* make gcc happy */
|
|
last = NULL;
|
|
while (NULL != (line = MHD_get_next_header_line (connection)))
|
|
{
|
|
if (last != NULL)
|
|
{
|
|
if ((line[0] == ' ') || (line[0] == '\t'))
|
|
{
|
|
/* value was continued on the next line, see
|
|
http://www.jmarshall.com/easy/http/ */
|
|
last = MHD_pool_reallocate (connection->pool,
|
|
last,
|
|
strlen (last) + 1,
|
|
strlen (line) + strlen (last) + 1);
|
|
if (last == NULL)
|
|
{
|
|
MHD_excessive_data_handler (connection,
|
|
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
|
break;
|
|
}
|
|
tmp = line;
|
|
while ((tmp[0] == ' ') || (tmp[0] == '\t'))
|
|
tmp++; /* skip whitespace at start of 2nd line */
|
|
strcat (last, tmp);
|
|
continue; /* possibly more than 2 lines... */
|
|
}
|
|
else
|
|
{
|
|
if (MHD_NO == MHD_connection_add_header (connection,
|
|
last,
|
|
colon,
|
|
MHD_HEADER_KIND))
|
|
return;
|
|
last = NULL;
|
|
}
|
|
}
|
|
if (connection->url == NULL)
|
|
{
|
|
/* line must be request line (first line of header) */
|
|
if (MHD_NO == parse_initial_message_line (connection, line))
|
|
goto DIE;
|
|
continue;
|
|
}
|
|
/* check if this is the end of the header */
|
|
if (strlen (line) == 0)
|
|
{
|
|
/* end of header */
|
|
connection->have_received_headers = MHD_YES;
|
|
clen = MHD_lookup_connection_value (connection,
|
|
MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_CONTENT_LENGTH);
|
|
if (clen != NULL)
|
|
{
|
|
if (1 != sscanf (clen, "%llu", &cval))
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Failed to parse `%s' header `%s', closing connection.\n",
|
|
MHD_HTTP_HEADER_CONTENT_LENGTH, clen);
|
|
#endif
|
|
goto DIE;
|
|
}
|
|
connection->remaining_upload_size = cval;
|
|
connection->have_received_body = cval == 0 ? MHD_YES : MHD_NO;
|
|
}
|
|
else
|
|
{
|
|
if (NULL == MHD_lookup_connection_value (connection,
|
|
MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_TRANSFER_ENCODING))
|
|
{
|
|
/* this request does not have a body */
|
|
connection->remaining_upload_size = 0;
|
|
connection->have_received_body = MHD_YES;
|
|
}
|
|
else
|
|
{
|
|
if (connection->have_chunked_upload == MHD_NO)
|
|
{
|
|
connection->remaining_upload_size = -1; /* unknown size */
|
|
if (0 == strcasecmp(MHD_lookup_connection_value(connection,
|
|
MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_TRANSFER_ENCODING),
|
|
"chunked"))
|
|
connection->have_chunked_upload = MHD_YES;
|
|
}
|
|
else
|
|
{
|
|
/* we were actually processing the footers at the
|
|
END of a chunked encoding; give connection
|
|
handler an extra chance... */
|
|
connection->have_chunked_upload = MHD_NO; /* no more! */
|
|
MHD_call_connection_handler(connection);
|
|
}
|
|
}
|
|
}
|
|
if ((0 != (MHD_USE_PEDANTIC_CHECKS & connection->daemon->options))
|
|
&& (NULL != connection->version)
|
|
&& (0 == strcasecmp (MHD_HTTP_VERSION_1_1, connection->version))
|
|
&& (NULL ==
|
|
MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_HOST)))
|
|
{
|
|
/* die, http 1.1 request without host and we are pedantic */
|
|
connection->have_received_body = MHD_YES;
|
|
connection->read_close = MHD_YES;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Received `%s' request without `%s' header.\n",
|
|
MHD_HTTP_VERSION_1_1, MHD_HTTP_HEADER_HOST);
|
|
#endif
|
|
response =
|
|
MHD_create_response_from_data (strlen (REQUEST_LACKS_HOST),
|
|
REQUEST_LACKS_HOST, MHD_NO,
|
|
MHD_NO);
|
|
MHD_queue_response (connection, MHD_HTTP_BAD_REQUEST, response);
|
|
MHD_destroy_response (response);
|
|
}
|
|
break;
|
|
}
|
|
/* line should be normal header line, find colon */
|
|
colon = strstr (line, ":");
|
|
if (colon == NULL)
|
|
{
|
|
/* error in header line, die hard */
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Received malformed line (no colon), closing connection.\n");
|
|
#endif
|
|
goto DIE;
|
|
}
|
|
/* zero-terminate header */
|
|
colon[0] = '\0';
|
|
colon++; /* advance to value */
|
|
while ((colon[0] != '\0') && ((colon[0] == ' ') || (colon[0] == '\t')))
|
|
colon++;
|
|
/* we do the actual adding of the connection
|
|
header at the beginning of the while
|
|
loop since we need to be able to inspect
|
|
the *next* header line (in case it starts
|
|
with a space...) */
|
|
last = line;
|
|
}
|
|
if ((last != NULL) &&
|
|
(MHD_NO == MHD_connection_add_header (connection,
|
|
last, colon, MHD_HEADER_KIND)))
|
|
return; /* error */
|
|
MHD_parse_cookie_header (connection);
|
|
return;
|
|
DIE:
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Closing connection (problem parsing headers)\n");
|
|
#endif
|
|
connection_close_error (connection);
|
|
}
|
|
|
|
|
|
/**
|
|
* Find the handler responsible for this request.
|
|
*/
|
|
static struct MHD_Access_Handler *
|
|
MHD_find_access_handler (struct MHD_Connection *connection)
|
|
{
|
|
struct MHD_Access_Handler *pos;
|
|
|
|
pos = connection->daemon->handlers;
|
|
while (pos != NULL)
|
|
{
|
|
if (0 == strcmp (connection->url, pos->uri_prefix))
|
|
return pos;
|
|
pos = pos->next;
|
|
}
|
|
return &connection->daemon->default_handler;
|
|
}
|
|
|
|
|
|
/**
|
|
* Call the handler of the application for this
|
|
* connection.
|
|
*/
|
|
void
|
|
MHD_call_connection_handler (struct MHD_Connection *connection)
|
|
{
|
|
struct MHD_Access_Handler *ah;
|
|
unsigned int processed;
|
|
unsigned int available;
|
|
unsigned int used;
|
|
int instant_retry;
|
|
unsigned int i;
|
|
|
|
if (connection->response != NULL)
|
|
return; /* already queued a response */
|
|
if (connection->have_received_headers == MHD_NO)
|
|
abort (); /* bad timing... */
|
|
do
|
|
{
|
|
instant_retry = MHD_NO;
|
|
available = connection->read_buffer_offset;
|
|
if (connection->have_chunked_upload == MHD_YES)
|
|
{
|
|
if ( (connection->current_chunk_offset == connection->current_chunk_size) &&
|
|
(connection->current_chunk_offset != 0) &&
|
|
(available >= 2) )
|
|
{
|
|
/* skip new line at the *end* of a chunk */
|
|
i = 0;
|
|
if ( (connection->read_buffer[i] == '\r') ||
|
|
(connection->read_buffer[i] == '\n') )
|
|
i++; /* skip 1st part of line feed */
|
|
if ( (connection->read_buffer[i] == '\r') ||
|
|
(connection->read_buffer[i] == '\n') )
|
|
i++; /* skip 2nd part of line feed */
|
|
if (i == 0)
|
|
{
|
|
/* malformed encoding */
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Received malformed HTTP request (bad chunked encoding), closing connection.\n");
|
|
#endif
|
|
connection_close_error (connection);
|
|
return;
|
|
}
|
|
connection->read_buffer_offset -= i;
|
|
available -= i;
|
|
memmove(connection->read_buffer,
|
|
&connection->read_buffer[i],
|
|
available);
|
|
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;
|
|
available -= processed;
|
|
if (available > 0)
|
|
instant_retry = MHD_YES;
|
|
}
|
|
else
|
|
{
|
|
/* we need to read chunk boundaries */
|
|
i = 0;
|
|
while (i < available)
|
|
{
|
|
if ( (connection->read_buffer[i] == '\r') ||
|
|
(connection->read_buffer[i] == '\n') )
|
|
break;
|
|
i++;
|
|
if (i >= 6)
|
|
break;
|
|
}
|
|
if (i >= available)
|
|
return; /* need more data... */
|
|
/* The following if-statement is a bit crazy -- we
|
|
use the second clause only for the side-effect,
|
|
0-terminating the buffer for the following sscanf
|
|
attempts; yes, there should be only a single
|
|
"="-sign (assignment!) in the read_buffer[i]-line. */
|
|
if ( (i >= 6) ||
|
|
((connection->read_buffer[i] = '\0')) ||
|
|
( (1 != sscanf(connection->read_buffer,
|
|
"%X",
|
|
&connection->current_chunk_size)) &&
|
|
(1 != sscanf(connection->read_buffer,
|
|
"%x",
|
|
&connection->current_chunk_size)) ) )
|
|
{
|
|
/* malformed encoding */
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Received malformed HTTP request (bad chunked encoding), closing connection.\n");
|
|
#endif
|
|
connection_close_error (connection);
|
|
return;
|
|
}
|
|
i++;
|
|
if ( (connection->read_buffer[i] == '\r') ||
|
|
(connection->read_buffer[i] == '\n') )
|
|
i++; /* skip 2nd part of line feed */
|
|
memmove(connection->read_buffer,
|
|
&connection->read_buffer[i],
|
|
available - i);
|
|
connection->read_buffer_offset -= i;
|
|
connection->current_chunk_offset = 0;
|
|
instant_retry = MHD_YES;
|
|
if (connection->current_chunk_size == 0)
|
|
{
|
|
/* we're back to reading HEADERS (footers!) */
|
|
connection->have_received_body = MHD_YES;
|
|
connection->remaining_upload_size = 0;
|
|
connection->have_received_headers = MHD_NO;
|
|
MHD_parse_connection_headers(connection);
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* no chunked encoding, give all to the client */
|
|
processed = available;
|
|
available = 0;
|
|
}
|
|
used = processed;
|
|
ah = MHD_find_access_handler (connection);
|
|
if (MHD_NO == ah->dh (ah->dh_cls,
|
|
connection,
|
|
connection->url,
|
|
connection->method,
|
|
connection->version,
|
|
connection->read_buffer, &processed,
|
|
&connection->client_context))
|
|
{
|
|
/* serious internal error, close connection */
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Internal application error, closing connection.\n");
|
|
#endif
|
|
connection_close_error (connection);
|
|
return;
|
|
}
|
|
if (processed != 0)
|
|
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... */
|
|
if (used > 0)
|
|
memmove (connection->read_buffer,
|
|
&connection->read_buffer[used],
|
|
processed + available);
|
|
if (connection->remaining_upload_size != -1)
|
|
connection->remaining_upload_size -= used;
|
|
connection->read_buffer_offset = processed + available;
|
|
if ((connection->remaining_upload_size == 0) ||
|
|
((connection->read_buffer_offset == 0) &&
|
|
(connection->remaining_upload_size == -1) && (connection->socket_fd == -1)))
|
|
{
|
|
connection->have_received_body = MHD_YES;
|
|
if (connection->read_buffer != NULL)
|
|
MHD_pool_reallocate (connection->pool,
|
|
connection->read_buffer,
|
|
(connection->read_buffer ==
|
|
NULL) ? 0 : connection->read_buffer_size + 1,
|
|
0);
|
|
connection->read_buffer_offset = 0;
|
|
connection->read_buffer_size = 0;
|
|
connection->read_buffer = NULL;
|
|
}
|
|
} while (instant_retry == MHD_YES);
|
|
}
|
|
|
|
/**
|
|
* This function handles a particular connection when it has been
|
|
* determined that there is data to be read off a socket. All implementations
|
|
* (multithreaded, external select, internal select) call this function
|
|
* to handle reads.
|
|
*/
|
|
int
|
|
MHD_connection_handle_read (struct MHD_Connection *connection)
|
|
{
|
|
int bytes_read;
|
|
void *tmp;
|
|
|
|
if (connection->pool == NULL)
|
|
connection->pool = MHD_pool_create (connection->daemon->pool_size);
|
|
if (connection->pool == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon, "Failed to create memory pool!\n");
|
|
#endif
|
|
connection_close_error (connection);
|
|
return MHD_NO;
|
|
}
|
|
if ((connection->read_buffer_offset >= connection->read_buffer_size) &&
|
|
(connection->have_received_headers == MHD_NO))
|
|
{
|
|
/* need to grow read buffer */
|
|
tmp = MHD_pool_reallocate (connection->pool,
|
|
connection->read_buffer,
|
|
connection->read_buffer_size,
|
|
connection->read_buffer_size * 2 +
|
|
MHD_BUF_INC_SIZE + 1);
|
|
if (tmp == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Not enough memory for reading headers!\n");
|
|
#endif
|
|
MHD_excessive_data_handler (connection,
|
|
MHD_HTTP_REQUEST_ENTITY_TOO_LARGE);
|
|
return MHD_NO;
|
|
}
|
|
connection->read_buffer = tmp;
|
|
connection->read_buffer_size =
|
|
connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE;
|
|
}
|
|
if (connection->read_buffer_offset >= connection->read_buffer_size)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon, "Unexpected call to %s.\n", __FUNCTION__);
|
|
#endif
|
|
return MHD_NO;
|
|
}
|
|
bytes_read = RECV (connection->socket_fd,
|
|
&connection->read_buffer[connection->read_buffer_offset],
|
|
connection->read_buffer_size - connection->read_buffer_offset,
|
|
MSG_NOSIGNAL);
|
|
if (bytes_read < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
return MHD_NO;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Failed to receive data: %s\n", STRERROR (errno));
|
|
#endif
|
|
connection_close_error (connection);
|
|
return MHD_YES;
|
|
}
|
|
if (bytes_read == 0)
|
|
{
|
|
/* other side closed connection */
|
|
connection->read_close = MHD_YES;
|
|
if ((connection->have_received_headers == MHD_YES) && (connection->read_buffer_offset > 0))
|
|
MHD_call_connection_handler (connection);
|
|
#if DEBUG_CLOSE
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Shutting down connection for reading (other side closed connection)\n");
|
|
#endif
|
|
#endif
|
|
shutdown (connection->socket_fd, SHUT_RD);
|
|
if ( (connection->have_received_headers == MHD_NO) ||
|
|
(connection->have_received_body == MHD_NO) ) {
|
|
/* no request => no response! */
|
|
CLOSE (connection->socket_fd);
|
|
connection->socket_fd = -1;
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
connection->read_buffer_offset += bytes_read;
|
|
if (connection->have_received_headers == MHD_NO)
|
|
MHD_parse_connection_headers (connection);
|
|
if ((connection->have_received_headers == MHD_YES) && (connection->method != NULL))
|
|
MHD_call_connection_handler (connection);
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Check if we need to set some additional headers
|
|
* for http-compiliance.
|
|
*/
|
|
static void
|
|
MHD_add_extra_headers (struct MHD_Connection *connection)
|
|
{
|
|
const char *have;
|
|
char buf[128];
|
|
|
|
if (connection->response->total_size == -1)
|
|
{
|
|
have = MHD_get_response_header (connection->response,
|
|
MHD_HTTP_HEADER_CONNECTION);
|
|
if (have == NULL)
|
|
MHD_add_response_header (connection->response,
|
|
MHD_HTTP_HEADER_CONNECTION, "close");
|
|
}
|
|
else if (NULL == MHD_get_response_header (connection->response,
|
|
MHD_HTTP_HEADER_CONTENT_LENGTH))
|
|
{
|
|
_REAL_SNPRINTF (buf,
|
|
128,
|
|
"%llu",
|
|
(unsigned long long) connection->response->total_size);
|
|
MHD_add_response_header (connection->response,
|
|
MHD_HTTP_HEADER_CONTENT_LENGTH, buf);
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_date_string (char *date, unsigned int max)
|
|
{
|
|
static const char *days[] =
|
|
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
|
static const char *mons[] =
|
|
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
|
|
"Nov", "Dec"
|
|
};
|
|
struct tm now;
|
|
time_t t;
|
|
|
|
time (&t);
|
|
gmtime_r (&t, &now);
|
|
snprintf (date,
|
|
max - 1,
|
|
"Date: %3s, %02u %3s %04u %02u:%02u:%02u GMT\r\n",
|
|
days[now.tm_wday % 7],
|
|
now.tm_mday,
|
|
mons[now.tm_mon % 12],
|
|
now.tm_year, now.tm_hour, now.tm_min, now.tm_sec);
|
|
}
|
|
|
|
/**
|
|
* Allocate the connection's write buffer and
|
|
* fill it with all of the headers from the
|
|
* HTTPd's response.
|
|
*/
|
|
static int
|
|
MHD_build_header_response (struct MHD_Connection *connection)
|
|
{
|
|
size_t size;
|
|
size_t off;
|
|
struct MHD_HTTP_Header *pos;
|
|
char code[128];
|
|
char date[128];
|
|
char *data;
|
|
|
|
MHD_add_extra_headers (connection);
|
|
const char* reason_phrase = MHD_get_reason_phrase_for(connection->responseCode);
|
|
_REAL_SNPRINTF (code, 128, "%s %u %s\r\n", MHD_HTTP_VERSION_1_1,
|
|
connection->responseCode, reason_phrase);
|
|
off = strlen (code);
|
|
/* estimate size */
|
|
size = off + 2; /* extra \r\n at the end */
|
|
pos = connection->response->first_header;
|
|
while (pos != NULL)
|
|
{
|
|
size += strlen (pos->header) + strlen (pos->value) + 4; /* colon, space, linefeeds */
|
|
pos = pos->next;
|
|
}
|
|
if (NULL == MHD_get_response_header (connection->response,
|
|
MHD_HTTP_HEADER_DATE))
|
|
get_date_string (date, sizeof (date));
|
|
else
|
|
date[0] = '\0';
|
|
size += strlen (date);
|
|
/* produce data */
|
|
data = MHD_pool_allocate (connection->pool, size + 1, MHD_YES);
|
|
if (data == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon, "Not enough memory for write!\n");
|
|
#endif
|
|
return MHD_NO;
|
|
}
|
|
memcpy (data, code, off);
|
|
pos = connection->response->first_header;
|
|
while (pos != NULL)
|
|
{
|
|
SPRINTF (&data[off], "%s: %s\r\n", pos->header, pos->value);
|
|
off += strlen (pos->header) + strlen (pos->value) + 4;
|
|
pos = pos->next;
|
|
}
|
|
strcpy (&data[off], date);
|
|
off += strlen (date);
|
|
sprintf (&data[off], "\r\n");
|
|
off += 2;
|
|
if (off != size)
|
|
abort ();
|
|
connection->write_buffer = data;
|
|
connection->write_buffer_append_offset = size;
|
|
connection->write_buffer_send_offset = 0;
|
|
connection->write_buffer_size = size + 1;
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* This function was created to handle writes to sockets when it has
|
|
* been determined that the socket can be written to. All
|
|
* implementations (multithreaded, external select, internal select)
|
|
* call this function
|
|
*/
|
|
int
|
|
MHD_connection_handle_write (struct MHD_Connection *connection)
|
|
{
|
|
struct MHD_Response *response;
|
|
int ret;
|
|
const char * end;
|
|
|
|
if (MHD_need_100_continue (connection))
|
|
{
|
|
ret = SEND (connection->socket_fd,
|
|
&HTTP_100_CONTINUE[connection->continue_message_write_offset],
|
|
strlen (HTTP_100_CONTINUE) - connection->continue_message_write_offset,
|
|
MSG_NOSIGNAL);
|
|
if (ret < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
return MHD_YES;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Failed to send data: %s\n", STRERROR (errno));
|
|
#endif
|
|
connection_close_error (connection);
|
|
return MHD_YES;
|
|
}
|
|
#if DEBUG_SEND_DATA
|
|
fprintf (stderr,
|
|
"Sent 100 continue response: `%.*s'\n",
|
|
ret, &HTTP_100_CONTINUE[connection->continue_message_write_offset]);
|
|
#endif
|
|
connection->continue_message_write_offset += ret;
|
|
return MHD_YES;
|
|
}
|
|
response = connection->response;
|
|
if (response == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon, "Unexpected call to %s.\n", __FUNCTION__);
|
|
#endif
|
|
return MHD_NO;
|
|
}
|
|
if (MHD_NO == connection->have_sent_headers)
|
|
{
|
|
if ((connection->write_buffer == NULL) &&
|
|
(MHD_NO == MHD_build_header_response (connection)))
|
|
{
|
|
/* oops - close! */
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Closing connection (failed to create response header)\n");
|
|
#endif
|
|
connection_close_error (connection);
|
|
return MHD_NO;
|
|
}
|
|
ret = SEND (connection->socket_fd,
|
|
&connection->write_buffer[connection->write_buffer_send_offset],
|
|
connection->write_buffer_append_offset - connection->write_buffer_send_offset,
|
|
MSG_NOSIGNAL);
|
|
if (ret < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
return MHD_YES;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Failed to send data: %s\n", STRERROR (errno));
|
|
#endif
|
|
connection_close_error (connection);
|
|
return MHD_YES;
|
|
}
|
|
#if DEBUG_SEND_DATA
|
|
fprintf (stderr,
|
|
"Sent HEADER response: `%.*s'\n",
|
|
ret, &connection->write_buffer[connection->write_buffer_send_offset]);
|
|
#endif
|
|
connection->write_buffer_send_offset += ret;
|
|
if (connection->write_buffer_append_offset == connection->write_buffer_send_offset)
|
|
{
|
|
connection->write_buffer_append_offset = 0;
|
|
connection->write_buffer_send_offset = 0;
|
|
connection->have_sent_headers = MHD_YES;
|
|
MHD_pool_reallocate (connection->pool,
|
|
connection->write_buffer,
|
|
connection->write_buffer_size, 0);
|
|
connection->write_buffer = NULL;
|
|
connection->write_buffer_size = 0;
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
if (response->total_size < connection->response_write_position)
|
|
abort (); /* internal error */
|
|
if (response->crc != NULL)
|
|
pthread_mutex_lock (&response->mutex);
|
|
|
|
/* prepare send buffer */
|
|
if ((response->crc != NULL) &&
|
|
((response->data_start > connection->response_write_position) ||
|
|
(response->data_start + response->data_size <=
|
|
connection->response_write_position)) && (MHD_YES != ready_response (connection)))
|
|
{
|
|
pthread_mutex_unlock (&response->mutex);
|
|
return MHD_YES;
|
|
}
|
|
/* transmit */
|
|
ret = SEND (connection->socket_fd,
|
|
&response->data[connection->response_write_position - response->data_start],
|
|
response->data_size - (connection->response_write_position -
|
|
response->data_start),
|
|
MSG_NOSIGNAL);
|
|
if (response->crc != NULL)
|
|
pthread_mutex_unlock (&response->mutex);
|
|
if (ret < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
return MHD_YES;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Failed to send data: %s\n", STRERROR (errno));
|
|
#endif
|
|
connection_close_error (connection);
|
|
return MHD_YES;
|
|
}
|
|
#if DEBUG_SEND_DATA
|
|
fprintf (stderr,
|
|
"Sent DATA response: `%.*s'\n",
|
|
ret,
|
|
&response->data[connection->response_write_position - response->data_start]);
|
|
#endif
|
|
connection->response_write_position += ret;
|
|
if (connection->response_write_position > response->total_size)
|
|
abort (); /* internal error */
|
|
if (connection->response_write_position == response->total_size)
|
|
{
|
|
if ((connection->have_received_body == MHD_NO) ||
|
|
(connection->have_received_headers == MHD_NO))
|
|
abort (); /* internal error */
|
|
MHD_destroy_response (response);
|
|
if (connection->daemon->notify_completed != NULL)
|
|
connection->daemon->notify_completed (connection->daemon->
|
|
notify_completed_cls,
|
|
connection,
|
|
&connection->client_context,
|
|
MHD_REQUEST_TERMINATED_COMPLETED_OK);
|
|
end = MHD_lookup_connection_value (connection,
|
|
MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_CONNECTION);
|
|
connection->client_context = NULL;
|
|
connection->continue_message_write_offset = 0;
|
|
connection->responseCode = 0;
|
|
connection->response = NULL;
|
|
connection->headers_received = NULL;
|
|
connection->have_received_headers = MHD_NO;
|
|
connection->have_sent_headers = MHD_NO;
|
|
connection->have_received_body = MHD_NO;
|
|
connection->response_write_position = 0;
|
|
connection->have_chunked_upload = MHD_NO;
|
|
connection->method = NULL;
|
|
connection->url = NULL;
|
|
if ((end != NULL) && (0 == strcasecmp (end, "close")))
|
|
{
|
|
/* other side explicitly requested
|
|
that we close the connection after
|
|
this request */
|
|
connection->read_close = MHD_YES;
|
|
}
|
|
if ((connection->read_close == MHD_YES) ||
|
|
(0 != strcasecmp (MHD_HTTP_VERSION_1_1, connection->version)))
|
|
{
|
|
/* closed for reading => close for good! */
|
|
if (connection->socket_fd != -1)
|
|
{
|
|
#if DEBUG_CLOSE
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Closing connection (http 1.0 or end-of-stream for unknown content length)\n");
|
|
#endif
|
|
#endif
|
|
SHUTDOWN (connection->socket_fd, SHUT_RDWR);
|
|
CLOSE (connection->socket_fd);
|
|
}
|
|
connection->socket_fd = -1;
|
|
}
|
|
connection->version = NULL;
|
|
connection->read_buffer = NULL;
|
|
connection->write_buffer = NULL;
|
|
connection->read_buffer_size = 0;
|
|
connection->read_buffer_offset = 0;
|
|
connection->write_buffer_size = 0;
|
|
connection->write_buffer_send_offset = 0;
|
|
connection->write_buffer_append_offset = 0;
|
|
MHD_pool_destroy (connection->pool);
|
|
connection->pool = NULL;
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
/* end of connection.c */
|