libmicrohttpd

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

commit baeae167a103b40a032e005b50ae13e49ac0ceed
parent 7bf6af91463cd9f514ef98f87726cb6701af2245
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 16 Dec 2007 10:27:29 +0000

fixing Mantis 1260

Diffstat:
MChangeLog | 8++++++++
MREADME | 2+-
Msrc/daemon/Makefile.am | 7+++++++
Msrc/daemon/connection.c | 380++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/daemon/daemon.c | 4++--
Asrc/daemon/daemontest_put_chunked.c | 382+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/daemon/internal.h | 44++++++++++++++++++++++++++++++++++----------
7 files changed, 695 insertions(+), 132 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,11 @@ +Sun Dec 16 03:24:13 MST 2007 + Implemented handling of chunked (HTTP 1.1) uploads. + Note that the upload callback must be able to + process chunks in the size uploaded by the client, + MHD will not "join" small chunks into a big + contiguous block of memory (even if buffer space + would be available). - CG + Wed Dec 5 21:39:35 MST 2007 Fixed race in multi-threaded server mode. Fixed handling of POST data when receiving a diff --git a/README b/README @@ -53,7 +53,7 @@ feature. For http/1.1-compliance: ======================== connection.c: -- support chunked requests from clients (#1260, ARCH, TEST) +- support sending of chunked responses (#1260, TEST) For POST: ========= diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am @@ -48,6 +48,7 @@ check_PROGRAMS = \ daemontest_postform \ daemontest_post_loop \ daemontest_put \ + daemontest_put_chunked \ daemontest_large_put \ daemontest_get11 \ daemontest_post11 \ @@ -94,6 +95,12 @@ daemontest_put_LDADD = \ $(top_builddir)/src/daemon/libmicrohttpd.la \ @LIBCURL@ +daemontest_put_chunked_SOURCES = \ + daemontest_put_chunked.c +daemontest_put_chunked_LDADD = \ + $(top_builddir)/src/daemon/libmicrohttpd.la \ + @LIBCURL@ + daemontest_get11_SOURCES = \ daemontest_get.c daemontest_get11_LDADD = \ diff --git a/src/daemon/connection.c b/src/daemon/connection.c @@ -152,7 +152,7 @@ MHD_queue_response (struct MHD_Connection *connection, if ((connection == NULL) || (response == NULL) || (connection->response != NULL) || - (connection->bodyReceived == MHD_NO) || (connection->headersReceived == MHD_NO)) + (connection->have_received_body == MHD_NO) || (connection->have_received_headers == MHD_NO)) return MHD_NO; MHD_increment_response_rc (response); connection->response = response; @@ -162,7 +162,7 @@ MHD_queue_response (struct MHD_Connection *connection, { /* if this is a "HEAD" request, pretend that we have already sent the full message body */ - connection->messagePos = response->total_size; + connection->response_write_position = response->total_size; } return MHD_YES; } @@ -179,12 +179,12 @@ MHD_need_100_continue (struct MHD_Connection *connection) return ((connection->version != NULL) && (0 == strcasecmp (connection->version, MHD_HTTP_VERSION_1_1)) && - (connection->headersReceived == MHD_YES) && + (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->continuePos < strlen (HTTP_100_CONTINUE))); + && (connection->continue_message_write_offset < strlen (HTTP_100_CONTINUE))); } /** @@ -221,10 +221,10 @@ ready_response (struct MHD_Connection *connection) response = connection->response; ret = response->crc (response->crc_cls, - connection->messagePos, + connection->response_write_position, response->data, MIN (response->data_buffer_size, - response->total_size - connection->messagePos)); + response->total_size - connection->response_write_position)); if (ret == -1) { /* end of message, signal other side by closing! */ @@ -233,11 +233,11 @@ ready_response (struct MHD_Connection *connection) MHD_DLOG (connection->daemon, "Closing connection (end of response)\n"); #endif #endif - response->total_size = connection->messagePos; + response->total_size = connection->response_write_position; connection_close_error (connection); return MHD_NO; } - response->data_start = connection->messagePos; + response->data_start = connection->response_write_position; response->data_size = ret; if (ret == 0) { @@ -278,8 +278,8 @@ MHD_connection_get_fdset (struct MHD_Connection *connection, if (fd == -1) return MHD_YES; if ((connection->read_close == MHD_NO) && - ((connection->headersReceived == MHD_NO) || - (connection->readLoc < connection->read_buffer_size))) + ((connection->have_received_headers == MHD_NO) || + (connection->read_buffer_offset < connection->read_buffer_size))) { FD_SET (fd, read_fd_set); if (fd > *max_fd) @@ -288,8 +288,8 @@ MHD_connection_get_fdset (struct MHD_Connection *connection, else { if ((connection->read_close == MHD_NO) && - ((connection->headersReceived == MHD_YES) && - (connection->readLoc == connection->read_buffer_size))) + ((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, @@ -342,8 +342,8 @@ MHD_excessive_data_handler (struct MHD_Connection *connection, /* die, header far too long to be reasonable */ connection->read_close = MHD_YES; - connection->headersReceived = MHD_YES; - connection->bodyReceived = 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"); @@ -368,17 +368,17 @@ MHD_get_next_header_line (struct MHD_Connection *connection) char *rbuf; size_t pos; - if (connection->readLoc == 0) + if (connection->read_buffer_offset == 0) return NULL; pos = 0; rbuf = connection->read_buffer; - while ((pos < connection->readLoc - 1) && + while ((pos < connection->read_buffer_offset - 1) && (rbuf[pos] != '\r') && (rbuf[pos] != '\n')) pos++; - if (pos == connection->readLoc - 1) + if (pos == connection->read_buffer_offset - 1) { /* not found, consider growing... */ - if (connection->readLoc == connection->read_buffer_size) + if (connection->read_buffer_offset == connection->read_buffer_size) { rbuf = MHD_pool_reallocate (connection->pool, connection->read_buffer, @@ -407,7 +407,7 @@ MHD_get_next_header_line (struct MHD_Connection *connection) rbuf[pos++] = '\0'; connection->read_buffer += pos; connection->read_buffer_size -= pos; - connection->readLoc -= pos; + connection->read_buffer_offset -= pos; return rbuf; } @@ -586,14 +586,16 @@ parse_initial_message_line (struct MHD_Connection *connection, char *line) /** - * This function is designed to parse the input buffer of a given connection. + * 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 - * headersReceived to MHD_YES. If no body is expected, it should - * also set "bodyReceived" to MHD_YES. Otherwise, it should - * set "uploadSize" to the expected size of the body. If the - * size of the body is unknown, it should be set to -1. + * 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) @@ -606,8 +608,9 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) unsigned long long cval; struct MHD_Response *response; - if ( (connection->bodyReceived == MHD_YES) || - (connection->headersReceived == MHD_YES) ) + 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; @@ -656,7 +659,7 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) if (strlen (line) == 0) { /* end of header */ - connection->headersReceived = MHD_YES; + connection->have_received_headers = MHD_YES; clen = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH); @@ -671,8 +674,8 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) #endif goto DIE; } - connection->uploadSize = cval; - connection->bodyReceived = cval == 0 ? MHD_YES : MHD_NO; + connection->remaining_upload_size = cval; + connection->have_received_body = cval == 0 ? MHD_YES : MHD_NO; } else { @@ -681,14 +684,29 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) MHD_HTTP_HEADER_TRANSFER_ENCODING)) { /* this request does not have a body */ - connection->uploadSize = 0; - connection->bodyReceived = MHD_YES; + connection->remaining_upload_size = 0; + connection->have_received_body = MHD_YES; } else { - connection->uploadSize = -1; /* unknown size */ - connection->bodyReceived = MHD_NO; - } + 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) @@ -698,7 +716,7 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) MHD_HTTP_HEADER_HOST))) { /* die, http 1.1 request without host and we are pedantic */ - connection->bodyReceived = MHD_YES; + connection->have_received_body = MHD_YES; connection->read_close = MHD_YES; #if HAVE_MESSAGES MHD_DLOG (connection->daemon, @@ -780,54 +798,177 @@ 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->headersReceived == MHD_NO) + if (connection->have_received_headers == MHD_NO) abort (); /* bad timing... */ - ah = MHD_find_access_handler (connection); - processed = connection->readLoc; - if (MHD_NO == ah->dh (ah->dh_cls, - connection, - connection->url, - connection->method, - connection->version, - connection->read_buffer, &processed, - &connection->client_context)) + do { - /* serious internal error, close connection */ + 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, - "Internal application error, closing connection.\n"); + MHD_DLOG (connection->daemon, + "Received malformed HTTP request (bad chunked encoding), closing connection.\n"); #endif - connection_close_error (connection); - return; - } - /* dh left "processed" bytes in buffer for next time... */ - memmove (connection->read_buffer, - &connection->read_buffer[connection->readLoc - processed], - processed); - if (connection->uploadSize != -1) - connection->uploadSize -= (connection->readLoc - processed); - connection->readLoc = processed; - if ((connection->uploadSize == 0) || - ((connection->readLoc == 0) && - (connection->uploadSize == -1) && (connection->socket_fd == -1))) - { - connection->bodyReceived = 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->readLoc = 0; - connection->read_buffer_size = 0; - connection->read_buffer = NULL; - } + 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 @@ -850,8 +991,8 @@ MHD_connection_handle_read (struct MHD_Connection *connection) connection_close_error (connection); return MHD_NO; } - if ((connection->readLoc >= connection->read_buffer_size) && - (connection->headersReceived == 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, @@ -873,7 +1014,7 @@ MHD_connection_handle_read (struct MHD_Connection *connection) connection->read_buffer_size = connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE; } - if (connection->readLoc >= connection->read_buffer_size) + if (connection->read_buffer_offset >= connection->read_buffer_size) { #if HAVE_MESSAGES MHD_DLOG (connection->daemon, "Unexpected call to %s.\n", __FUNCTION__); @@ -881,8 +1022,8 @@ MHD_connection_handle_read (struct MHD_Connection *connection) return MHD_NO; } bytes_read = RECV (connection->socket_fd, - &connection->read_buffer[connection->readLoc], - connection->read_buffer_size - connection->readLoc, + &connection->read_buffer[connection->read_buffer_offset], + connection->read_buffer_size - connection->read_buffer_offset, MSG_NOSIGNAL); if (bytes_read < 0) { @@ -899,7 +1040,7 @@ MHD_connection_handle_read (struct MHD_Connection *connection) { /* other side closed connection */ connection->read_close = MHD_YES; - if ((connection->headersReceived == MHD_YES) && (connection->readLoc > 0)) + if ((connection->have_received_headers == MHD_YES) && (connection->read_buffer_offset > 0)) MHD_call_connection_handler (connection); #if DEBUG_CLOSE #if HAVE_MESSAGES @@ -908,18 +1049,18 @@ MHD_connection_handle_read (struct MHD_Connection *connection) #endif #endif shutdown (connection->socket_fd, SHUT_RD); - if ( (connection->headersReceived == MHD_NO) || - (connection->bodyReceived == MHD_NO) ) { + 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->readLoc += bytes_read; - if (connection->headersReceived == MHD_NO) + connection->read_buffer_offset += bytes_read; + if (connection->have_received_headers == MHD_NO) MHD_parse_connection_headers (connection); - if ((connection->headersReceived == MHD_YES) && (connection->method != NULL)) + if ((connection->have_received_headers == MHD_YES) && (connection->method != NULL)) MHD_call_connection_handler (connection); return MHD_YES; } @@ -1035,8 +1176,8 @@ MHD_build_header_response (struct MHD_Connection *connection) if (off != size) abort (); connection->write_buffer = data; - connection->writeLoc = size; - connection->writePos = 0; + connection->write_buffer_append_offset = size; + connection->write_buffer_send_offset = 0; connection->write_buffer_size = size + 1; return MHD_YES; } @@ -1057,8 +1198,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection) if (MHD_need_100_continue (connection)) { ret = SEND (connection->socket_fd, - &HTTP_100_CONTINUE[connection->continuePos], - strlen (HTTP_100_CONTINUE) - connection->continuePos, + &HTTP_100_CONTINUE[connection->continue_message_write_offset], + strlen (HTTP_100_CONTINUE) - connection->continue_message_write_offset, MSG_NOSIGNAL); if (ret < 0) { @@ -1074,9 +1215,9 @@ MHD_connection_handle_write (struct MHD_Connection *connection) #if DEBUG_SEND_DATA fprintf (stderr, "Sent 100 continue response: `%.*s'\n", - ret, &HTTP_100_CONTINUE[connection->continuePos]); + ret, &HTTP_100_CONTINUE[connection->continue_message_write_offset]); #endif - connection->continuePos += ret; + connection->continue_message_write_offset += ret; return MHD_YES; } response = connection->response; @@ -1087,7 +1228,7 @@ MHD_connection_handle_write (struct MHD_Connection *connection) #endif return MHD_NO; } - if (MHD_NO == connection->headersSent) + if (MHD_NO == connection->have_sent_headers) { if ((connection->write_buffer == NULL) && (MHD_NO == MHD_build_header_response (connection))) @@ -1101,8 +1242,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection) return MHD_NO; } ret = SEND (connection->socket_fd, - &connection->write_buffer[connection->writePos], - connection->writeLoc - connection->writePos, + &connection->write_buffer[connection->write_buffer_send_offset], + connection->write_buffer_append_offset - connection->write_buffer_send_offset, MSG_NOSIGNAL); if (ret < 0) { @@ -1118,14 +1259,14 @@ MHD_connection_handle_write (struct MHD_Connection *connection) #if DEBUG_SEND_DATA fprintf (stderr, "Sent HEADER response: `%.*s'\n", - ret, &connection->write_buffer[connection->writePos]); + ret, &connection->write_buffer[connection->write_buffer_send_offset]); #endif - connection->writePos += ret; - if (connection->writeLoc == connection->writePos) + connection->write_buffer_send_offset += ret; + if (connection->write_buffer_append_offset == connection->write_buffer_send_offset) { - connection->writeLoc = 0; - connection->writePos = 0; - connection->headersSent = MHD_YES; + 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); @@ -1134,24 +1275,24 @@ MHD_connection_handle_write (struct MHD_Connection *connection) } return MHD_YES; } - if (response->total_size < connection->messagePos) + 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->messagePos) || + ((response->data_start > connection->response_write_position) || (response->data_start + response->data_size <= - connection->messagePos)) && (MHD_YES != ready_response (connection))) + 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->messagePos - response->data_start], - response->data_size - (connection->messagePos - + &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) @@ -1171,15 +1312,15 @@ MHD_connection_handle_write (struct MHD_Connection *connection) fprintf (stderr, "Sent DATA response: `%.*s'\n", ret, - &response->data[connection->messagePos - response->data_start]); + &response->data[connection->response_write_position - response->data_start]); #endif - connection->messagePos += ret; - if (connection->messagePos > response->total_size) + connection->response_write_position += ret; + if (connection->response_write_position > response->total_size) abort (); /* internal error */ - if (connection->messagePos == response->total_size) + if (connection->response_write_position == response->total_size) { - if ((connection->bodyReceived == MHD_NO) || - (connection->headersReceived == MHD_NO)) + 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) @@ -1192,14 +1333,15 @@ MHD_connection_handle_write (struct MHD_Connection *connection) MHD_HEADER_KIND, MHD_HTTP_HEADER_CONNECTION); connection->client_context = NULL; - connection->continuePos = 0; + connection->continue_message_write_offset = 0; connection->responseCode = 0; connection->response = NULL; connection->headers_received = NULL; - connection->headersReceived = MHD_NO; - connection->headersSent = MHD_NO; - connection->bodyReceived = MHD_NO; - connection->messagePos = 0; + 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"))) @@ -1230,10 +1372,10 @@ MHD_connection_handle_write (struct MHD_Connection *connection) connection->read_buffer = NULL; connection->write_buffer = NULL; connection->read_buffer_size = 0; - connection->readLoc = 0; + connection->read_buffer_offset = 0; connection->write_buffer_size = 0; - connection->writePos = 0; - connection->writeLoc = 0; + connection->write_buffer_send_offset = 0; + connection->write_buffer_append_offset = 0; MHD_pool_destroy (connection->pool); connection->pool = NULL; } diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c @@ -227,7 +227,7 @@ MHD_handle_connection (void *data) (FD_ISSET (con->socket_fd, &ws)) && (MHD_YES != MHD_connection_handle_write (con)))) break; - if ((con->headersReceived == MHD_YES) && (con->response == NULL)) + if ((con->have_received_headers == MHD_YES) && (con->response == NULL)) MHD_call_connection_handler (con); if ((con->socket_fd != -1) && ((FD_ISSET (con->socket_fd, &rs)) || @@ -438,7 +438,7 @@ MHD_cleanup_connections (struct MHD_Daemon *daemon) } if ( (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && - ((pos->headersReceived == MHD_YES) && (pos->response == NULL)) ) + ((pos->have_received_headers == MHD_YES) && (pos->response == NULL)) ) MHD_call_connection_handler (pos); prev = pos; diff --git a/src/daemon/daemontest_put_chunked.c b/src/daemon/daemontest_put_chunked.c @@ -0,0 +1,382 @@ +/* + This file is part of libmicrohttpd + (C) 2007 Christian Grothoff + + libmicrohttpd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + libmicrohttpd 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libmicrohttpd; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file daemontest_put_chunked.c + * @brief Testcase for libmicrohttpd PUT operations with chunked encoding + * for the upload data + * @author Christian Grothoff + */ + +#include "config.h" +#include <curl/curl.h> +#include <microhttpd.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifndef WINDOWS +#include <unistd.h> +#endif + +struct CBC +{ + char *buf; + size_t pos; + size_t size; +}; + +static size_t +putBuffer (void *stream, size_t size, size_t nmemb, void *ptr) +{ + unsigned int *pos = ptr; + unsigned int wrt; + + wrt = size * nmemb; + if (wrt > 8 - (*pos)) + wrt = 8 - (*pos); + if (wrt > 4) + wrt = 4; /* only send half at first => force multiple chunks! */ + memcpy (stream, &("Hello123"[*pos]), wrt); + (*pos) += wrt; + return wrt; +} + +static size_t +copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) +{ + struct CBC *cbc = ctx; + + if (cbc->pos + size * nmemb > cbc->size) + return 0; /* overflow */ + memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); + cbc->pos += size * nmemb; + return size * nmemb; +} + +static int +ahc_echo (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, unsigned int *upload_data_size, + void **unused) +{ + int *done = cls; + struct MHD_Response *response; + int ret; + int have; + + if (0 != strcmp ("PUT", method)) + return MHD_NO; /* unexpected method */ + if ((*done) < 8) + { + have = *upload_data_size; + if (have + *done > 8) + { + printf ("Invalid upload data `%8s'!\n", upload_data); + return MHD_NO; + } + if (0 == memcmp(upload_data, + &"Hello123"[*done], + have)) + { + *done += have; + *upload_data_size = 0; + } + else + { + printf ("Invalid upload data `%8s'!\n", upload_data); + return MHD_NO; + } + return MHD_YES; + } + response = MHD_create_response_from_data (strlen (url), + (void *) url, MHD_NO, MHD_YES); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} + + +static int +testInternalPut () +{ + struct MHD_Daemon *d; + CURL *c; + char buf[2048]; + struct CBC cbc; + unsigned int pos = 0; + int done_flag = 0; + CURLcode errornum; + + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, + 11080, + NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END); + if (d == NULL) + return 1; + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11080/hello_world"); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); + curl_easy_setopt (c, CURLOPT_READDATA, &pos); + curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); + /* + // by not giving the file size, we force chunking! + curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); + */ + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 15L); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L); + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 2; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != strlen ("/hello_world")) + return 4; + if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) + return 8; + return 0; +} + +static int +testMultithreadedPut () +{ + struct MHD_Daemon *d; + CURL *c; + char buf[2048]; + struct CBC cbc; + unsigned int pos = 0; + int done_flag = 0; + CURLcode errornum; + + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, + 11081, + NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END); + if (d == NULL) + return 16; + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world"); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); + curl_easy_setopt (c, CURLOPT_READDATA, &pos); + curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); + /* + // by not giving the file size, we force chunking! + curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); + */ + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 15L); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L); + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 32; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != strlen ("/hello_world")) + return 64; + if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) + return 128; + + return 0; +} + + +static int +testExternalPut () +{ + struct MHD_Daemon *d; + CURL *c; + char buf[2048]; + struct CBC cbc; + CURLM *multi; + CURLMcode mret; + fd_set rs; + fd_set ws; + fd_set es; + int max; + int running; + struct CURLMsg *msg; + time_t start; + struct timeval tv; + unsigned int pos = 0; + int done_flag = 0; + + multi = NULL; + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_DEBUG, + 11082, + NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END); + if (d == NULL) + return 256; + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11082/hello_world"); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); + curl_easy_setopt (c, CURLOPT_READDATA, &pos); + curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); + /* + // by not giving the file size, we force chunking! + curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); + */ + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 15L); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L); + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + + + multi = curl_multi_init (); + if (multi == NULL) + { + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 512; + } + mret = curl_multi_add_handle (multi, c); + if (mret != CURLM_OK) + { + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 1024; + } + start = time (NULL); + while ((time (NULL) - start < 5) && (multi != NULL)) + { + max = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + curl_multi_perform (multi, &running); + mret = curl_multi_fdset (multi, &rs, &ws, &es, &max); + if (mret != CURLM_OK) + { + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 2048; + } + if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) + { + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 4096; + } + tv.tv_sec = 0; + tv.tv_usec = 1000; + select (max + 1, &rs, &ws, &es, &tv); + curl_multi_perform (multi, &running); + if (running == 0) + { + msg = curl_multi_info_read (multi, &running); + if (msg == NULL) + break; + if (msg->msg == CURLMSG_DONE) + { + if (msg->data.result != CURLE_OK) + printf ("%s failed at %s:%d: `%s'\n", + "curl_multi_perform", + __FILE__, + __LINE__, curl_easy_strerror (msg->data.result)); + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + c = NULL; + multi = NULL; + } + } + MHD_run (d); + } + if (multi != NULL) + { + curl_multi_remove_handle (multi, c); + curl_easy_cleanup (c); + curl_multi_cleanup (multi); + } + MHD_stop_daemon (d); + if (cbc.pos != strlen ("/hello_world")) + return 8192; + if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) + return 16384; + return 0; +} + + + +int +main (int argc, char *const *argv) +{ + unsigned int errorCount = 0; + + if (0 != curl_global_init (CURL_GLOBAL_WIN32)) + return 2; + errorCount += testInternalPut (); + if (0) + { + errorCount += testMultithreadedPut (); + errorCount += testExternalPut (); + } + if (errorCount != 0) + fprintf (stderr, "Error (code: %u)\n", errorCount); + curl_global_cleanup (); + return errorCount != 0; /* 0 == pass */ +} diff --git a/src/daemon/internal.h b/src/daemon/internal.h @@ -273,7 +273,7 @@ struct MHD_Connection * Position where we currently append data in * read_buffer (last valid position). */ - size_t readLoc; + size_t read_buffer_offset; /** * Size of write_buffer (in bytes). @@ -283,32 +283,33 @@ struct MHD_Connection /** * Offset where we are with sending from write_buffer. */ - size_t writePos; + size_t write_buffer_send_offset; /** - * Last valid location in write_buffer. + * Last valid location in write_buffer (where do we + * append and up to where is it safe to send?) */ - size_t writeLoc; + size_t write_buffer_append_offset; /** * Current write position in the actual response * (excluding headers, content only; should be 0 * while sending headers). */ - size_t messagePos; + size_t response_write_position; /** * Remaining (!) number of bytes in the upload. * Set to -1 for unknown (connection will close * to indicate end of upload). */ - size_t uploadSize; + size_t remaining_upload_size; /** * Position in the 100 CONTINUE message that * we need to send when receiving http 1.1 requests. */ - size_t continuePos; + size_t continue_message_write_offset; /** * Length of the foreign address. @@ -343,18 +344,18 @@ struct MHD_Connection * possible that the NEXT request is already * (partially) waiting in the read buffer. */ - int headersReceived; + int have_received_headers; /** * Have we finished receiving the data from a * potential file-upload? */ - int bodyReceived; + int have_received_body; /** * Have we finished sending all of the headers yet? */ - int headersSent; + int have_sent_headers; /** * HTTP response code. Only valid if response object @@ -371,6 +372,29 @@ struct MHD_Connection */ int response_unready; + /** + * Are we receiving with chunked encoding? This will be set to + * MHD_YES after we parse the headers and are processing the body + * with chunks. After we are done with the body and we are + * processing the footers; once the footers are also done, this will + * be set to MHD_NO again (before the final call to the handler). + */ + int have_chunked_upload; + + /** + * If we are receiving with chunked encoding, where are we right + * now? Set to 0 if we are waiting to receive the chunk size; + * otherwise, this is the size of the current chunk. A value of + * zero is also used when we're at the end of the chunks. + */ + unsigned int current_chunk_size; + + /** + * If we are receiving with chunked encoding, where are we currently + * with respect to the current chunk (at what offset / position)? + */ + unsigned int current_chunk_offset; + };