From 21acb929f578eb8f876c235669b78d6b213ec315 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 9 Sep 2007 03:56:10 +0000 Subject: incremental post processing API and implementation --- ChangeLog | 8 + README | 9 -- configure.ac | 4 +- src/daemon/Makefile.am | 3 +- src/daemon/connection.c | 299 ++++++++++-------------------------- src/daemon/daemon.c | 115 +++++++------- src/daemon/daemontest.c | 2 +- src/daemon/daemontest_get.c | 2 +- src/daemon/daemontest_large_put.c | 2 +- src/daemon/daemontest_long_header.c | 2 +- src/daemon/daemontest_post.c | 46 ++++-- src/daemon/daemontest_put.c | 11 +- src/daemon/fileserver_example.c | 3 +- src/daemon/internal.c | 23 +++ src/daemon/internal.h | 14 +- src/daemon/minimal_example.c | 3 +- src/include/microhttpd.h | 81 +++++++++- 17 files changed, 306 insertions(+), 321 deletions(-) diff --git a/ChangeLog b/ChangeLog index a7c7ec25..d95139e2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Sat Sep 8 21:54:04 MDT 2007 + Extended API to allow for incremental POST + processing. The new API is binary-compatible + as long as the app does not handle POSTs, but + since that maybe the case, we're strictly speaking + breaking backwards compatibility (since url-encoded + POST data is no longer obtained the same way). - CG + Thu Aug 30 00:59:24 MDT 2007 Improving API to allow clients to associate state with a connection and to be notified about request diff --git a/README b/README index a1216b26..1cf31703 100644 --- a/README +++ b/README @@ -18,17 +18,8 @@ connection.c: For POST: ========= -- find better way to handle POST data that does not fit into memory (#1221, API, TEST) - add support to decode multipart/form-data (#1221, TEST) -API Improvements: -================= -- allow clients to associate a void * with each connection (for easier client state - management) -- allow clients to register callback to be invoked when connection is - timed out or otherwise completed / aborted / closed (for easier client - state clean up) - For SSL: ======== microhttpd.h: diff --git a/configure.ac b/configure.ac index 44afee9e..1db29b83 100644 --- a/configure.ac +++ b/configure.ac @@ -21,8 +21,8 @@ # # AC_PREREQ(2.57) -AC_INIT([libmicrohttpd], [0.0.3],[libmicrohttpd@gnunet.org]) -AM_INIT_AUTOMAKE([libmicrohttpd], [0.0.3]) +AC_INIT([libmicrohttpd], [0.1.0],[libmicrohttpd@gnunet.org]) +AM_INIT_AUTOMAKE([libmicrohttpd], [0.1.0]) AM_CONFIG_HEADER([config.h]) AH_TOP([#define _GNU_SOURCE 1]) diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index e70583d3..3fa68c75 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -6,13 +6,14 @@ lib_LTLIBRARIES = \ libmicrohttpd.la libmicrohttpd_la_LDFLAGS = \ - -export-dynamic -version-info 1:0:0 + -export-dynamic -version-info 2:0:0 libmicrohttpd_la_SOURCES = \ connection.c connection.h \ daemon.c \ internal.c internal.h \ memorypool.c memorypool.h \ plibc.h \ + postprocessor.c \ response.c response.h # example programs diff --git a/src/daemon/connection.c b/src/daemon/connection.c index eef9e9e8..476d1c89 100644 --- a/src/daemon/connection.c +++ b/src/daemon/connection.c @@ -57,7 +57,7 @@ /** * Add extra debug messages with reasons for closing connections * (non-error reasons). - */ + */ #define DEBUG_CLOSE 0 @@ -184,17 +184,17 @@ MHD_need_100_continue (struct MHD_Connection *connection) * A serious error occured, close the * connection (and notify the application). */ -static void -connection_close_error(struct MHD_Connection * connection) +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); + if (connection->daemon->notify_completed != NULL) + connection->daemon->notify_completed (connection->daemon-> + notify_completed_cls, connection, + &connection->client_context, + MHD_REQUEST_TERMINATED_WITH_ERROR); } /** @@ -222,11 +222,10 @@ ready_response (struct MHD_Connection *connection) { /* end of message, signal other side by closing! */ #if DEBUG_CLOSE - MHD_DLOG (connection->daemon, - "Closing connection (end of response)\n"); + MHD_DLOG (connection->daemon, "Closing connection (end of response)\n"); #endif response->total_size = connection->messagePos; - connection_close_error(connection); + connection_close_error (connection); return MHD_NO; } response->data_start = connection->messagePos; @@ -281,7 +280,6 @@ MHD_connection_get_fdset (struct MHD_Connection *connection, { if ((connection->read_close == MHD_NO) && ((connection->headersReceived == 1) && - (connection->post_processed == MHD_NO) && (connection->readLoc == connection->read_buffer_size))) { /* try growing the read buffer, just in case */ @@ -297,7 +295,7 @@ MHD_connection_get_fdset (struct MHD_Connection *connection, connection->read_buffer_size = connection->read_buffer_size * 2 + MHD_BUF_INC_SIZE; FD_SET (fd, read_fd_set); - if (fd > *max_fd) + if (fd > *max_fd) *max_fd = fd; } } @@ -429,29 +427,6 @@ MHD_connection_add_header (struct MHD_Connection *connection, return MHD_YES; } -/** - * Process escape sequences ('+'=space, %HH) - */ -static void -MHD_http_unescape (char *val) -{ - char *esc; - unsigned int num; - - while (NULL != (esc = strstr (val, "+"))) - *esc = ' '; - while (NULL != (esc = strstr (val, "%"))) - { - if ((1 == sscanf (&esc[1], - "%2x", &num)) || (1 == sscanf (&esc[1], "%2X", &num))) - { - esc[0] = (unsigned char) num; - memmove (&esc[1], &esc[3], strlen (&esc[3])); - } - val = esc + 1; - } -} - /** * @return MHD_NO on failure (out of memory), MHD_YES for success */ @@ -615,10 +590,11 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) const char *clen; const char *end; unsigned long long cval; - struct MHD_Response * response; + struct MHD_Response *response; if (connection->bodyReceived == 1) abort (); + colon = NULL; /* make gcc happy */ last = NULL; while (NULL != (line = MHD_get_next_header_line (connection))) { @@ -707,29 +683,28 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) this request */ connection->read_close = MHD_YES; } - - 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->bodyReceived = MHD_YES; - connection->read_close = MHD_YES; - MHD_DLOG (connection->daemon, - "Received `%s' request without `%s' header.\n", - MHD_HTTP_VERSION_1_1, - MHD_HTTP_HEADER_HOST); - 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); - } - + + 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->bodyReceived = MHD_YES; + connection->read_close = MHD_YES; + MHD_DLOG (connection->daemon, + "Received `%s' request without `%s' header.\n", + MHD_HTTP_VERSION_1_1, MHD_HTTP_HEADER_HOST); + 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 */ @@ -761,8 +736,8 @@ MHD_parse_connection_headers (struct MHD_Connection *connection) return; DIE: MHD_DLOG (connection->daemon, - "Closing connection (problem parsing headers)\n"); - connection_close_error(connection); + "Closing connection (problem parsing headers)\n"); + connection_close_error (connection); } @@ -784,107 +759,6 @@ MHD_find_access_handler (struct MHD_Connection *connection) return &connection->daemon->default_handler; } -/** - * Test if we are able to process the POST data. - * This depends on available memory (enough to load - * all of the POST data into the pool) and the - * content encoding of the POST data. And of course, - * this requires that the request is actually a - * POST request. - * - * @return MHD_YES if so - */ -static int -MHD_test_post_data (struct MHD_Connection *connection) -{ - const char *encoding; - void *buf; - - if ((connection->method == NULL) || - (connection->response != NULL) || - (0 != strcasecmp (connection->method, MHD_HTTP_METHOD_POST))) - return MHD_NO; - encoding = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_CONTENT_TYPE); - if (encoding == NULL) - return MHD_NO; - if ((0 == strcasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, - encoding)) && (connection->uploadSize != -1)) - { - buf = MHD_pool_reallocate (connection->pool, - connection->read_buffer, - (connection->read_buffer == NULL) ? 0 : connection->read_buffer_size + 1, - connection->uploadSize + 1); - if (buf == NULL) - return MHD_NO; - connection->read_buffer_size = connection->uploadSize; - connection->read_buffer = buf; - return MHD_YES; - } - return MHD_NO; -} - -/** - * Process the POST data here (adding to headers). - * - * Needs to first check POST encoding and then do - * the right thing (TM). The POST data is in the - * connection's post_data buffer between the postPos - * and postLoc offsets. The POST message maybe - * incomplete. The existing buffer (allocated from - * the pool) can be used and modified but must then - * be properly removed from the struct. - * - * @return MHD_YES on success, MHD_NO on error (i.e. out of - * memory). - */ -static int -MHD_parse_post_data (struct MHD_Connection *connection) -{ - const char *encoding; - int ret; - - encoding = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_CONTENT_TYPE); - if (encoding == NULL) - return MHD_NO; - if (0 == strcasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, encoding)) - { - /* add 0-termination, that's why the actual buffer size - is always 1 more than what is actually required for the data! */ - connection->read_buffer[connection->readLoc] = '\0'; - ret = parse_arguments (MHD_POSTDATA_KIND, - connection, connection->read_buffer); - /* invalidate read buffer for other uses -- - in particular, do not give it to the - client; if this were to be needed, we would - have to make a copy, which would double memory - requirements */ - connection->read_buffer_size = 0; - connection->readLoc = 0; - connection->uploadSize = 0; - connection->read_buffer = NULL; - return ret; - } - if (0 == strcasecmp (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, encoding)) - { - /* this code should never been reached right now, - since the test_post_data function would already - return MHD_NO; code is here only for future - extensions... */ - /* see http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4 */ - MHD_DLOG (connection->daemon, - "Unsupported multipart encoding of POST data specified, not processing POST data.\n"); - return MHD_NO; - } - /* this should never be reached, just here for - error checking */ - MHD_DLOG (connection->daemon, - "Unknown encoding of POST data specified, not processing POST data.\n"); - return MHD_NO; -} /** * Call the handler of the application for this @@ -908,12 +782,12 @@ MHD_call_connection_handler (struct MHD_Connection *connection) connection->method, connection->version, connection->read_buffer, &processed, - &connection->client_context)) + &connection->client_context)) { /* serios internal error, close connection */ MHD_DLOG (connection->daemon, "Internal application error, closing connection.\n"); - connection_close_error(connection); + connection_close_error (connection); return; } /* dh left "processed" bytes in buffer for next time... */ @@ -929,10 +803,11 @@ MHD_call_connection_handler (struct MHD_Connection *connection) { connection->bodyReceived = 1; if (connection->read_buffer != NULL) - MHD_pool_reallocate(connection->pool, - connection->read_buffer, - (connection->read_buffer == NULL) ? 0 : connection->read_buffer_size + 1, - 0); + 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; @@ -957,7 +832,7 @@ MHD_connection_handle_read (struct MHD_Connection *connection) if (connection->pool == NULL) { MHD_DLOG (connection->daemon, "Failed to create memory pool!\n"); - connection_close_error(connection); + connection_close_error (connection); return MHD_NO; } if ((connection->readLoc >= connection->read_buffer_size) && @@ -995,15 +870,14 @@ MHD_connection_handle_read (struct MHD_Connection *connection) return MHD_NO; MHD_DLOG (connection->daemon, "Failed to receive data: %s\n", STRERROR (errno)); - connection_close_error(connection); + connection_close_error (connection); return MHD_YES; } if (bytes_read == 0) { /* other side closed connection */ connection->read_close = MHD_YES; - if ( (connection->headersReceived == 1) && - (connection->readLoc > 0) ) + if ((connection->headersReceived == 1) && (connection->readLoc > 0)) MHD_call_connection_handler (connection); #if DEBUG_CLOSE MHD_DLOG (connection->daemon, @@ -1014,24 +888,9 @@ MHD_connection_handle_read (struct MHD_Connection *connection) } connection->readLoc += bytes_read; if (connection->headersReceived == 0) - { - MHD_parse_connection_headers (connection); - if (connection->headersReceived == 1) - { - connection->post_processed = MHD_test_post_data (connection); - } - } - if (connection->headersReceived == 1) - { - if ((connection->post_processed == MHD_YES) && - (connection->uploadSize == connection->readLoc)) - if (MHD_NO == MHD_parse_post_data (connection)) - connection->post_processed = MHD_NO; - if (((connection->post_processed == MHD_NO) || - (connection->read_buffer_size == connection->readLoc)) && - (connection->method != NULL)) - MHD_call_connection_handler (connection); - } + MHD_parse_connection_headers (connection); + if ((connection->headersReceived == 1) && (connection->method != NULL)) + MHD_call_connection_handler (connection); return MHD_YES; } @@ -1171,14 +1030,13 @@ MHD_connection_handle_write (struct MHD_Connection *connection) return MHD_YES; MHD_DLOG (connection->daemon, "Failed to send data: %s\n", STRERROR (errno)); - connection_close_error(connection); + connection_close_error (connection); return MHD_YES; } #if DEBUG_SEND_DATA - fprintf(stderr, - "Sent 100 continue response: `%.*s'\n", - ret, - &HTTP_100_CONTINUE[connection->continuePos]); + fprintf (stderr, + "Sent 100 continue response: `%.*s'\n", + ret, &HTTP_100_CONTINUE[connection->continuePos]); #endif connection->continuePos += ret; return MHD_YES; @@ -1195,9 +1053,9 @@ MHD_connection_handle_write (struct MHD_Connection *connection) (MHD_NO == MHD_build_header_response (connection))) { /* oops - close! */ - MHD_DLOG (connection->daemon, - "Closing connection (failed to create response header)\n"); - connection_close_error(connection); + MHD_DLOG (connection->daemon, + "Closing connection (failed to create response header)\n"); + connection_close_error (connection); return MHD_NO; } ret = SEND (connection->socket_fd, @@ -1209,14 +1067,13 @@ MHD_connection_handle_write (struct MHD_Connection *connection) return MHD_YES; MHD_DLOG (connection->daemon, "Failed to send data: %s\n", STRERROR (errno)); - connection_close_error(connection); + connection_close_error (connection); return MHD_YES; } #if DEBUG_SEND_DATA - fprintf(stderr, - "Sent HEADER response: `%.*s'\n", - ret, - &connection->write_buffer[connection->writePos]); + fprintf (stderr, + "Sent HEADER response: `%.*s'\n", + ret, &connection->write_buffer[connection->writePos]); #endif connection->writePos += ret; if (connection->writeLoc == connection->writePos) @@ -1259,14 +1116,14 @@ MHD_connection_handle_write (struct MHD_Connection *connection) return MHD_YES; MHD_DLOG (connection->daemon, "Failed to send data: %s\n", STRERROR (errno)); - connection_close_error(connection); + connection_close_error (connection); return MHD_YES; } #if DEBUG_SEND_DATA - fprintf(stderr, - "Sent DATA response: `%.*s'\n", - ret, - &response->data[connection->messagePos - response->data_start]); + fprintf (stderr, + "Sent DATA response: `%.*s'\n", + ret, + &response->data[connection->messagePos - response->data_start]); #endif connection->messagePos += ret; if (connection->messagePos > response->total_size) @@ -1277,11 +1134,12 @@ MHD_connection_handle_write (struct MHD_Connection *connection) (connection->headersReceived == 0)) 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); + if (connection->daemon->notify_completed != NULL) + connection->daemon->notify_completed (connection->daemon-> + notify_completed_cls, + connection, + &connection->client_context, + MHD_REQUEST_TERMINATED_COMPLETED_OK); connection->client_context = NULL; connection->continuePos = 0; connection->responseCode = 0; @@ -1297,14 +1155,15 @@ MHD_connection_handle_write (struct MHD_Connection *connection) (0 != strcasecmp (MHD_HTTP_VERSION_1_1, connection->version))) { /* closed for reading => close for good! */ - if (connection->socket_fd != -1) { + if (connection->socket_fd != -1) + { #if DEBUG_CLOSE - MHD_DLOG (connection->daemon, - "Closing connection (http 1.0 or end-of-stream for unknown content length)\n"); + MHD_DLOG (connection->daemon, + "Closing connection (http 1.0 or end-of-stream for unknown content length)\n"); #endif - SHUTDOWN (connection->socket_fd, SHUT_RDWR); - CLOSE (connection->socket_fd); - } + SHUTDOWN (connection->socket_fd, SHUT_RDWR); + CLOSE (connection->socket_fd); + } connection->socket_fd = -1; } connection->version = NULL; diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c index 3b8b1111..346abedf 100644 --- a/src/daemon/daemon.c +++ b/src/daemon/daemon.c @@ -225,8 +225,8 @@ MHD_handle_connection (void *data) if (con->socket_fd != -1) { #if DEBUG_CLOSE - MHD_DLOG (con->daemon, - "Processing thread terminating, closing connection\n"); + MHD_DLOG (con->daemon, + "Processing thread terminating, closing connection\n"); #endif SHUTDOWN (con->socket_fd, SHUT_RDWR); CLOSE (con->socket_fd); @@ -259,10 +259,11 @@ MHD_accept_connection (struct MHD_Daemon *daemon) if ((s < 0) || (addrlen <= 0)) { MHD_DLOG (daemon, "Error accepting connection: %s\n", STRERROR (errno)); - if (s != -1) { - SHUTDOWN (s, SHUT_RDWR); - CLOSE (s); /* just in case */ - } + if (s != -1) + { + SHUTDOWN (s, SHUT_RDWR); + CLOSE (s); /* just in case */ + } return MHD_NO; } if (daemon->max_connections == 0) @@ -278,8 +279,7 @@ MHD_accept_connection (struct MHD_Daemon *daemon) (MHD_NO == daemon->apc (daemon->apc_cls, addr, addrlen))) { #if DEBUG_CLOSE - MHD_DLOG (daemon, - "Connection rejected, closing connection\n"); + MHD_DLOG (daemon, "Connection rejected, closing connection\n"); #endif SHUTDOWN (s, SHUT_RDWR); CLOSE (s); @@ -357,17 +357,16 @@ MHD_cleanup_connections (struct MHD_Daemon *daemon) if ((pos->last_activity < timeout) && (pos->socket_fd != -1)) { #if DEBUG_CLOSE - MHD_DLOG (daemon, - "Connection timed out, closing connection\n"); + MHD_DLOG (daemon, "Connection timed out, closing connection\n"); #endif - SHUTDOWN (pos->socket_fd, SHUT_RDWR); + SHUTDOWN (pos->socket_fd, SHUT_RDWR); CLOSE (pos->socket_fd); pos->socket_fd = -1; - if (pos->daemon->notify_completed != NULL) - pos->daemon->notify_completed(pos->daemon->notify_completed_cls, - pos, - &pos->client_context, - MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); + if (pos->daemon->notify_completed != NULL) + pos->daemon->notify_completed (pos->daemon->notify_completed_cls, + pos, + &pos->client_context, + MHD_REQUEST_TERMINATED_TIMEOUT_REACHED); } if (pos->socket_fd == -1) { @@ -472,19 +471,19 @@ MHD_select (struct MHD_Daemon *daemon, int may_block) FD_ZERO (&ws); FD_ZERO (&es); max = 0; - + if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) { /* single-threaded, go over everything */ if (MHD_NO == MHD_get_fdset (daemon, &rs, &ws, &es, &max)) - return MHD_NO; + return MHD_NO; } else { /* accept only, have one thread per connection */ max = daemon->socket_fd; if (max == -1) - return MHD_NO; + return MHD_NO; FD_SET (max, &rs); } if (may_block == MHD_NO) @@ -496,21 +495,20 @@ MHD_select (struct MHD_Daemon *daemon, int may_block) { /* ltimeout is in ms */ if (MHD_YES == MHD_get_timeout (daemon, <imeout)) - { - timeout.tv_usec = (ltimeout % 1000) * 1000; - timeout.tv_sec = ltimeout / 1000; - may_block = MHD_NO; - } + { + timeout.tv_usec = (ltimeout % 1000) * 1000; + timeout.tv_sec = ltimeout / 1000; + may_block = MHD_NO; + } } num_ready = SELECT (max + 1, - &rs, &ws, &es, - may_block == MHD_NO ? &timeout : NULL); + &rs, &ws, &es, may_block == MHD_NO ? &timeout : NULL); if (daemon->shutdown == MHD_YES) return MHD_NO; if (num_ready < 0) { if (errno == EINTR) - return MHD_YES; + return MHD_YES; MHD_DLOG (daemon, "Select failed: %s\n", STRERROR (errno)); return MHD_NO; } @@ -525,23 +523,23 @@ MHD_select (struct MHD_Daemon *daemon, int may_block) now = time (NULL); pos = daemon->connections; while (pos != NULL) - { - ds = pos->socket_fd; - if (ds != -1) - { - if (FD_ISSET (ds, &rs)) - { - pos->last_activity = now; - MHD_connection_handle_read (pos); - } - if (FD_ISSET (ds, &ws)) - { - pos->last_activity = now; - MHD_connection_handle_write (pos); - } - } - pos = pos->next; - } + { + ds = pos->socket_fd; + if (ds != -1) + { + if (FD_ISSET (ds, &rs)) + { + pos->last_activity = now; + MHD_connection_handle_read (pos); + } + if (FD_ISSET (ds, &ws)) + { + pos->last_activity = now; + MHD_connection_handle_write (pos); + } + } + pos = pos->next; + } } return MHD_YES; } @@ -694,10 +692,11 @@ MHD_start_daemon (unsigned int options, case MHD_OPTION_CONNECTION_TIMEOUT: retVal->connection_timeout = va_arg (ap, unsigned int); break; - case MHD_OPTION_NOTIFY_COMPLETED: - retVal->notify_completed = va_arg(ap, MHD_RequestCompletedCallback); - retVal->notify_completed_cls = va_arg(ap, void *); - break; + case MHD_OPTION_NOTIFY_COMPLETED: + retVal->notify_completed = + va_arg (ap, MHD_RequestCompletedCallback); + retVal->notify_completed_cls = va_arg (ap, void *); + break; default: fprintf (stderr, "Invalid MHD_OPTION argument! (Did you terminate the list with MHD_OPTION_END?)\n"); @@ -733,8 +732,7 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) fd = daemon->socket_fd; daemon->socket_fd = -1; #if DEBUG_CLOSE - MHD_DLOG (daemon, - "MHD shutdown, closing listen socket\n"); + MHD_DLOG (daemon, "MHD shutdown, closing listen socket\n"); #endif CLOSE (fd); if ((0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || @@ -748,16 +746,15 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) if (-1 != daemon->connections->socket_fd) { #if DEBUG_CLOSE - MHD_DLOG (daemon, - "MHD shutdown, closing active connections\n"); + MHD_DLOG (daemon, "MHD shutdown, closing active connections\n"); #endif - if (daemon->notify_completed != NULL) - daemon->notify_completed(daemon->notify_completed_cls, - daemon->connections, - &daemon->connections->client_context, - MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); - SHUTDOWN (daemon->connections->socket_fd, SHUT_RDWR); - CLOSE (daemon->connections->socket_fd); + if (daemon->notify_completed != NULL) + daemon->notify_completed (daemon->notify_completed_cls, + daemon->connections, + &daemon->connections->client_context, + MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); + SHUTDOWN (daemon->connections->socket_fd, SHUT_RDWR); + CLOSE (daemon->connections->socket_fd); daemon->connections->socket_fd = -1; } MHD_cleanup_connections (daemon); diff --git a/src/daemon/daemontest.c b/src/daemon/daemontest.c index 2ecb87f2..8048b438 100644 --- a/src/daemon/daemontest.c +++ b/src/daemon/daemontest.c @@ -65,7 +65,7 @@ ahc_nothing (void *cls, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, - void ** unused) + void **unused) { return MHD_NO; } diff --git a/src/daemon/daemontest_get.c b/src/daemon/daemontest_get.c index 79726a11..3f0b36ba 100644 --- a/src/daemon/daemontest_get.c +++ b/src/daemon/daemontest_get.c @@ -64,7 +64,7 @@ ahc_echo (void *cls, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, - void ** unused) + void **unused) { const char *me = cls; struct MHD_Response *response; diff --git a/src/daemon/daemontest_large_put.c b/src/daemon/daemontest_large_put.c index f4ae5583..2a9ad4fa 100644 --- a/src/daemon/daemontest_large_put.c +++ b/src/daemon/daemontest_large_put.c @@ -86,7 +86,7 @@ ahc_echo (void *cls, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, - void ** unused) + void **unused) { int *done = cls; struct MHD_Response *response; diff --git a/src/daemon/daemontest_long_header.c b/src/daemon/daemontest_long_header.c index 6a451136..22368e9c 100644 --- a/src/daemon/daemontest_long_header.c +++ b/src/daemon/daemontest_long_header.c @@ -70,7 +70,7 @@ ahc_echo (void *cls, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, - void ** unused) + void **unused) { const char *me = cls; struct MHD_Response *response; diff --git a/src/daemon/daemontest_post.c b/src/daemon/daemontest_post.c index 10f6ea97..00bec910 100644 --- a/src/daemon/daemontest_post.c +++ b/src/daemon/daemontest_post.c @@ -63,6 +63,27 @@ copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) return size * nmemb; } +/** + * Note that this post_iterator is not perfect + * in that it fails to support incremental processing. + * (to be fixed in the future) + */ +static int +post_iterator (void *cls, + enum MHD_ValueKind kind, + const char *key, const char *value, size_t off, size_t size) +{ + int *eok = cls; + + if ((0 == strcmp (key, "name")) && + (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size))) + (*eok) |= 1; + if ((0 == strcmp (key, "project")) && + (size == strlen ("curl")) && (0 == strncmp (value, "curl", size))) + (*eok) |= 2; + return MHD_YES; +} + static int ahc_echo (void *cls, struct MHD_Connection *connection, @@ -70,31 +91,38 @@ ahc_echo (void *cls, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, - void ** unused) + void **unused) { + static int eok; struct MHD_Response *response; + struct MHD_PostProcessor *pp; int ret; - const char *r1; - const char *r2; if (0 != strcmp ("POST", method)) { printf ("METHOD: %s\n", method); return MHD_NO; /* unexpected method */ } - r1 = MHD_lookup_connection_value (connection, MHD_POSTDATA_KIND, "name"); - r2 = MHD_lookup_connection_value (connection, MHD_POSTDATA_KIND, "project"); - if ((r1 != NULL) && - (r2 != NULL) && - (0 == strcmp ("daniel", r1)) && (0 == strcmp ("curl", r2))) + pp = *unused; + if (pp == NULL) + { + eok = 0; + pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok); + *unused = pp; + } + MHD_post_process (pp, upload_data, *upload_data_size); + if ((eok == 3) && (0 == *upload_data_size)) { 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 MHD_YES; /* done */ + MHD_destroy_post_processor (pp); + *unused = NULL; + return ret; } + *upload_data_size = 0; return MHD_YES; } diff --git a/src/daemon/daemontest_put.c b/src/daemon/daemontest_put.c index ad761025..2c3acfdb 100644 --- a/src/daemon/daemontest_put.c +++ b/src/daemon/daemontest_put.c @@ -77,7 +77,7 @@ ahc_echo (void *cls, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, - void ** unused) + void **unused) { int *done = cls; struct MHD_Response *response; @@ -363,10 +363,11 @@ main (int argc, char *const *argv) if (0 != curl_global_init (CURL_GLOBAL_WIN32)) return 2; errorCount += testInternalPut (); - if (0) { - errorCount += testMultithreadedPut (); - errorCount += testExternalPut (); - } + if (0) + { + errorCount += testMultithreadedPut (); + errorCount += testExternalPut (); + } if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); curl_global_cleanup (); diff --git a/src/daemon/fileserver_example.c b/src/daemon/fileserver_example.c index 15b6aa24..c5bf762b 100644 --- a/src/daemon/fileserver_example.c +++ b/src/daemon/fileserver_example.c @@ -52,8 +52,7 @@ ahc_echo (void *cls, const char *url, const char *method, const char *upload_data, - const char *version, unsigned int *upload_data_size, - void ** unused) + const char *version, unsigned int *upload_data_size, void **unused) { struct MHD_Response *response; int ret; diff --git a/src/daemon/internal.c b/src/daemon/internal.c index 0407cfa9..85c17b16 100644 --- a/src/daemon/internal.c +++ b/src/daemon/internal.c @@ -42,3 +42,26 @@ MHD_DLOG (const struct MHD_Daemon *daemon, const char *format, ...) VFPRINTF (stderr, format, va); va_end (va); } + +/** + * Process escape sequences ('+'=space, %HH) + */ +void +MHD_http_unescape (char *val) +{ + char *esc; + unsigned int num; + + while (NULL != (esc = strstr (val, "+"))) + *esc = ' '; + while (NULL != (esc = strstr (val, "%"))) + { + if ((1 == sscanf (&esc[1], + "%2x", &num)) || (1 == sscanf (&esc[1], "%2X", &num))) + { + esc[0] = (unsigned char) num; + memmove (&esc[1], &esc[3], strlen (&esc[3])); + } + val = esc + 1; + } +} diff --git a/src/daemon/internal.h b/src/daemon/internal.h index 125755d2..3f3f91b0 100644 --- a/src/daemon/internal.h +++ b/src/daemon/internal.h @@ -65,6 +65,11 @@ */ void MHD_DLOG (const struct MHD_Daemon *daemon, const char *format, ...); +/** + * Process escape sequences ('+'=space, %HH). + * Updates val in place. + */ +void MHD_http_unescape (char *val); /** * Header or cookie in HTTP request or response. @@ -208,7 +213,7 @@ struct MHD_Connection * store it. (MHD does not know or care what it * is). */ - void * client_context; + void *client_context; /** * Request method. Should be GET/POST/etc. Allocated @@ -349,11 +354,6 @@ struct MHD_Connection */ int headersSent; - /** - * Are we processing the POST data? - */ - int post_processed; - /** * HTTP response code. Only valid if response object * is already set. @@ -391,7 +391,7 @@ struct MHD_Daemon MHD_RequestCompletedCallback notify_completed; - void * notify_completed_cls; + void *notify_completed_cls; /** * PID of the select thread (if we have internal select) diff --git a/src/daemon/minimal_example.c b/src/daemon/minimal_example.c index 80442072..62ee5ae5 100644 --- a/src/daemon/minimal_example.c +++ b/src/daemon/minimal_example.c @@ -41,8 +41,7 @@ ahc_echo (void *cls, const char *url, const char *method, const char *upload_data, - const char *version, unsigned int *upload_data_size, - void ** unused) + const char *version, unsigned int *upload_data_size, void **unused) { const char *me = cls; struct MHD_Response *response; diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h index 3a46c085..c77ea6b9 100644 --- a/src/include/microhttpd.h +++ b/src/include/microhttpd.h @@ -84,7 +84,7 @@ extern "C" /** * Current version of the library. */ -#define MHD_VERSION 0x00000004 +#define MHD_VERSION 0x00000005 /** * MHD-internal return codes. @@ -427,6 +427,11 @@ struct MHD_Connection; */ struct MHD_Response; +/** + * Handle for POST processing. + */ +struct MHD_PostProcessor; + /** * Allow or deny a client to connect. * @@ -554,6 +559,29 @@ typedef int */ typedef void (*MHD_ContentReaderFreeCallback) (void *cls); +/** + * Iterator over key-value pairs where the value + * maybe made available in increments and/or may + * not be zero-terminated. + * + * @param cls user-specified closure + * @param kind type of the value + * @param 0-terminated key for the value + * @param value pointer to size bytes of data at the + * specified offset + * @param off offset of value in the overall data + * @param size number of bytes in value available + * @return MHD_YES to continue iterating, + * MHD_NO to abort the iteration + */ +typedef int + (*MHD_IncrementalKeyValueIterator) (void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *value, + size_t off, + size_t size); + /** * Start a webserver on the given port. * @param flags combination of MHD_FLAG values @@ -776,6 +804,57 @@ const char *MHD_get_response_header (struct MHD_Response *response, const char *key); +/** + * Create a PostProcessor. + * + * A PostProcessor can be used to (incrementally) + * parse the data portion of a POST request. + * + * @param connection the connection on which the POST is + * happening (used to determine the POST format) + * @param buffer_size maximum number of bytes to use for + * internal buffering (used only for the parsing, + * specifically the parsing of the keys). A + * tiny value (256-1024) should be sufficient. + * Do NOT use 0. + * @param ikvi iterator to be called with the parsed data, + * Must NOT be NULL. + * @param cls first argument to ikvi + * @return NULL on error (out of memory, unsupported encoding), + otherwise a PP handle + */ +struct MHD_PostProcessor * +MHD_create_post_processor(struct MHD_Connection * connection, + unsigned int buffer_size, + MHD_IncrementalKeyValueIterator ikvi, + void * cls); + +/** + * Parse and process POST data. + * Call this function when POST data is available + * (usually during an MHD_AccessHandlerCallback) + * with the upload_data and upload_data_size. + * Whenever possible, this will then cause calls + * to the MHD_IncrementalKeyValueIterator. + * + * @param pp the post processor + * @param post_data post_data_len bytes of POST data + * @param post_data_len length of post_data + * @return MHD_YES on success, MHD_NO on error + * (out-of-memory, iterator aborted, parse error) + */ +int +MHD_post_process(struct MHD_PostProcessor * pp, + const char * post_data, + unsigned int post_data_len); + +/** + * Release PostProcessor resources. + */ +void +MHD_destroy_post_processor(struct MHD_PostProcessor * pp); + + #if 0 /* keep Emacsens' auto-indent happy */ { #endif -- cgit v1.2.3