summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2016-09-03 22:32:29 +0000
committerChristian Grothoff <christian@grothoff.org>2016-09-03 22:32:29 +0000
commit352a07b01018a4379f4fd83b8277730f044afede (patch)
treee2e7624f083b3ae8a9adee3f9c36e216bc2a885e
parent6fa19ae05f226451db78316919908c44d6444ac4 (diff)
-first, crazy-pants version of test and implementation of HTTPS upgrade; FLUSH API still needs to change dramatically, neither the ioctl nor the busy waiting are really acceptable here
-rw-r--r--src/microhttpd/Makefile.am9
-rw-r--r--src/microhttpd/connection.c29
-rw-r--r--src/microhttpd/daemon.c26
-rw-r--r--src/microhttpd/mhd_sockets.c17
-rw-r--r--src/microhttpd/response.c65
-rw-r--r--src/microhttpd/test_upgrade.c11
-rw-r--r--src/microhttpd/test_upgrade_ssl.c472
7 files changed, 590 insertions, 39 deletions
diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am
index 9a32f598..b60ff81e 100644
--- a/src/microhttpd/Makefile.am
+++ b/src/microhttpd/Makefile.am
@@ -146,6 +146,10 @@ check_PROGRAMS = \
test_daemon \
test_upgrade
+if ENABLE_HTTPS
+ check_PROGRAMS += test_upgrade_ssl
+endif
+
if HAVE_POSTPROCESSOR
check_PROGRAMS += \
test_postprocessor \
@@ -171,6 +175,11 @@ test_upgrade_SOURCES = \
test_upgrade_LDADD = \
$(top_builddir)/src/microhttpd/libmicrohttpd.la
+test_upgrade_ssl_SOURCES = \
+ test_upgrade_ssl.c
+test_upgrade_ssl_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
test_postprocessor_SOURCES = \
test_postprocessor.c
test_postprocessor_CPPFLAGS = \
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
index 0c21bc57..16477538 100644
--- a/src/microhttpd/connection.c
+++ b/src/microhttpd/connection.c
@@ -2354,7 +2354,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection)
(void) MHD_mutex_unlock_ (&response->mutex);
if (ret < 0)
{
- if (MHD_SCKT_ERR_IS_EINTR_ (err) || MHD_SCKT_ERR_IS_EAGAIN_ (err))
+ if (MHD_SCKT_ERR_IS_EINTR_ (err) ||
+ MHD_SCKT_ERR_IS_EAGAIN_ (err))
return MHD_YES;
#ifdef HAVE_MESSAGES
MHD_DLOG (connection->daemon,
@@ -2515,13 +2516,17 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
}
break;
}
- if (MHD_NO == parse_initial_message_line (connection, line, line_len))
- CONNECTION_CLOSE_ERROR (connection, NULL);
+ if (MHD_NO == parse_initial_message_line (connection,
+ line,
+ line_len))
+ CONNECTION_CLOSE_ERROR (connection,
+ NULL);
else
connection->state = MHD_CONNECTION_URL_RECEIVED;
continue;
case MHD_CONNECTION_URL_RECEIVED:
- line = get_next_header_line (connection, NULL);
+ line = get_next_header_line (connection,
+ NULL);
if (NULL == line)
{
if (MHD_CONNECTION_URL_RECEIVED != connection->state)
@@ -2539,7 +2544,8 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
connection->state = MHD_CONNECTION_HEADERS_RECEIVED;
continue;
}
- if (MHD_NO == process_header_line (connection, line))
+ if (MHD_NO == process_header_line (connection,
+ line))
{
transmit_error_response (connection,
MHD_HTTP_BAD_REQUEST,
@@ -2645,7 +2651,8 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
}
break;
case MHD_CONNECTION_BODY_RECEIVED:
- line = get_next_header_line (connection, NULL);
+ line = get_next_header_line (connection,
+ NULL);
if (NULL == line)
{
if (connection->state != MHD_CONNECTION_BODY_RECEIVED)
@@ -2689,7 +2696,9 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
break;
}
if (MHD_NO ==
- process_broken_line (connection, line, MHD_FOOTER_KIND))
+ process_broken_line (connection,
+ line,
+ MHD_FOOTER_KIND))
continue;
if (0 == line[0])
{
@@ -3057,17 +3066,17 @@ MHD_get_connection_info (struct MHD_Connection *connection,
{
#if HTTPS_SUPPORT
case MHD_CONNECTION_INFO_CIPHER_ALGO:
- if (connection->tls_session == NULL)
+ if (NULL == connection->tls_session)
return NULL;
connection->cipher = gnutls_cipher_get (connection->tls_session);
return (const union MHD_ConnectionInfo *) &connection->cipher;
case MHD_CONNECTION_INFO_PROTOCOL:
- if (connection->tls_session == NULL)
+ if (NULL == connection->tls_session)
return NULL;
connection->protocol = gnutls_protocol_get_version (connection->tls_session);
return (const union MHD_ConnectionInfo *) &connection->protocol;
case MHD_CONNECTION_INFO_GNUTLS_SESSION:
- if (connection->tls_session == NULL)
+ if (NULL == connection->tls_session)
return NULL;
return (const union MHD_ConnectionInfo *) &connection->tls_session;
#endif
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c
index a298a1a4..62cd3ce8 100644
--- a/src/microhttpd/daemon.c
+++ b/src/microhttpd/daemon.c
@@ -659,19 +659,19 @@ urh_to_fdset (struct MHD_UpgradeResponseHandle *urh,
max_fd,
fd_setsize)) )
return MHD_NO;
- if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) &&
+ if ( (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) &&
(! MHD_add_to_fd_set_ (urh->mhd.socket,
ws,
max_fd,
fd_setsize)) )
return MHD_NO;
- if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) &&
+ if ( (0 == (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) &&
(! MHD_add_to_fd_set_ (urh->connection->socket_fd,
rs,
max_fd,
fd_setsize)) )
return MHD_NO;
- if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) &&
+ if ( (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) &&
(! MHD_add_to_fd_set_ (urh->connection->socket_fd,
ws,
max_fd,
@@ -2639,10 +2639,17 @@ MHD_select (struct MHD_Daemon *daemon,
may_block = MHD_NO;
/* single-threaded, go over everything */
- if (MHD_NO == MHD_get_fdset2 (daemon, &rs, &ws, &es, &maxsock, FD_SETSIZE))
+ if (MHD_NO ==
+ MHD_get_fdset2 (daemon,
+ &rs,
+ &ws,
+ &es,
+ &maxsock,
+ FD_SETSIZE))
{
#ifdef HAVE_MESSAGES
- MHD_DLOG (daemon, "Could not obtain daemon fdsets");
+ MHD_DLOG (daemon,
+ "Could not obtain daemon fdsets");
#endif
err_state = MHD_YES;
}
@@ -2657,7 +2664,8 @@ MHD_select (struct MHD_Daemon *daemon,
FD_SETSIZE)) )
{
#ifdef HAVE_MESSAGES
- MHD_DLOG (daemon, "Could not add listen socket to fdset");
+ MHD_DLOG (daemon,
+ "Could not add listen socket to fdset");
#endif
return MHD_NO;
}
@@ -2726,7 +2734,11 @@ MHD_select (struct MHD_Daemon *daemon,
timeout.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE)(ltimeout / 1000);
tv = &timeout;
}
- num_ready = MHD_SYS_select_ (maxsock + 1, &rs, &ws, &es, tv);
+ num_ready = MHD_SYS_select_ (maxsock + 1,
+ &rs,
+ &ws,
+ &es,
+ tv);
if (MHD_YES == daemon->shutdown)
return MHD_NO;
if (num_ready < 0)
diff --git a/src/microhttpd/mhd_sockets.c b/src/microhttpd/mhd_sockets.c
index 1023017f..1187a2cd 100644
--- a/src/microhttpd/mhd_sockets.c
+++ b/src/microhttpd/mhd_sockets.c
@@ -337,16 +337,21 @@ MHD_add_to_fd_set_ (MHD_socket fd,
MHD_socket *max_fd,
unsigned int fd_setsize)
{
- if (NULL == set || MHD_INVALID_SOCKET == fd)
+ if ( (NULL == set) ||
+ (MHD_INVALID_SOCKET == fd) )
return 0;
- if (!MHD_SCKT_FD_FITS_FDSET_SETSIZE_(fd, set, fd_setsize))
+ if (! MHD_SCKT_FD_FITS_FDSET_SETSIZE_ (fd,
+ set,
+ fd_setsize))
return 0;
- MHD_SCKT_ADD_FD_TO_FDSET_SETSIZE_(fd, set, fd_setsize);
+ MHD_SCKT_ADD_FD_TO_FDSET_SETSIZE_(fd,
+ set,
+ fd_setsize);
if ( (NULL != max_fd) &&
- ((fd > *max_fd) || (MHD_INVALID_SOCKET == *max_fd)) )
+ ( (fd > *max_fd) ||
+ (MHD_INVALID_SOCKET == *max_fd) ) )
*max_fd = fd;
-
- return !0;
+ return ! 0;
}
diff --git a/src/microhttpd/response.c b/src/microhttpd/response.c
index ca729765..aa79196d 100644
--- a/src/microhttpd/response.c
+++ b/src/microhttpd/response.c
@@ -16,7 +16,6 @@
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 response.c
* @brief Methods for managing response objects
@@ -34,6 +33,7 @@
#include "connection.h"
#include "memorypool.h"
+#include <sys/ioctl.h>
#if defined(_WIN32) && defined(MHD_W32_MUTEX_)
#ifndef WIN32_LEAN_AND_MEAN
@@ -202,7 +202,9 @@ MHD_get_response_headers (struct MHD_Response *response,
numHeaders++;
if ((NULL != iterator) &&
(MHD_YES != iterator (iterator_cls,
- pos->kind, pos->header, pos->value)))
+ pos->kind,
+ pos->header,
+ pos->value)))
break;
}
return numHeaders;
@@ -337,16 +339,23 @@ file_reader (void *cls,
return MHD_CONTENT_READER_END_WITH_ERROR; /* seek to required position is not possible */
#if defined(HAVE_LSEEK64)
- if (lseek64 (response->fd, offset64, SEEK_SET) != offset64)
+ if (lseek64 (response->fd,
+ offset64,
+ SEEK_SET) != offset64)
return MHD_CONTENT_READER_END_WITH_ERROR; /* can't seek to required position */
#elif defined(HAVE___LSEEKI64)
- if (_lseeki64 (response->fd, offset64, SEEK_SET) != offset64)
+ if (_lseeki64 (response->fd,
+ offset64,
+ SEEK_SET) != offset64)
return MHD_CONTENT_READER_END_WITH_ERROR; /* can't seek to required position */
#else /* !HAVE___LSEEKI64 */
- if (sizeof(off_t) < sizeof(uint64_t) && offset64 > (uint64_t)INT32_MAX)
+ if ( (sizeof(off_t) < sizeof (uint64_t)) &&
+ (offset64 > (uint64_t)INT32_MAX) )
return MHD_CONTENT_READER_END_WITH_ERROR; /* seek to required position is not possible */
- if (lseek (response->fd, (off_t)offset64, SEEK_SET) != (off_t)offset64)
+ if (lseek (response->fd,
+ (off_t) offset64,
+ SEEK_SET) != (off_t) offset64)
return MHD_CONTENT_READER_END_WITH_ERROR; /* can't seek to required position */
#endif
@@ -354,12 +363,16 @@ file_reader (void *cls,
if (max > SSIZE_MAX)
max = SSIZE_MAX;
- n = read (response->fd, buf, max);
+ n = read (response->fd,
+ buf,
+ max);
#else /* _WIN32 */
if (max > INT32_MAX)
max = INT32_MAX;
- n = read (response->fd, buf, (unsigned int)max);
+ n = read (response->fd,
+ buf,
+ (unsigned int)max);
#endif /* _WIN32 */
if (0 == n)
@@ -438,8 +451,10 @@ MHD_create_response_from_fd_at_offset64 (uint64_t size,
struct MHD_Response *response;
#if !defined(HAVE___LSEEKI64) && !defined(HAVE_LSEEK64)
- if (sizeof(uint64_t) > sizeof(off_t) &&
- (size > (uint64_t)INT32_MAX || offset > (uint64_t)INT32_MAX || (size + offset) >= (uint64_t)INT32_MAX))
+ if ( (sizeof(uint64_t) > sizeof(off_t)) &&
+ ( (size > (uint64_t)INT32_MAX) ||
+ (offset > (uint64_t)INT32_MAX) ||
+ ((size + offset) >= (uint64_t)INT32_MAX) ) )
return NULL;
#endif
if ( ((int64_t)size < 0) ||
@@ -530,7 +545,9 @@ MHD_create_response_from_data (size_t size,
return NULL;
if (NULL == (response = malloc (sizeof (struct MHD_Response))))
return NULL;
- memset (response, 0, sizeof (struct MHD_Response));
+ memset (response,
+ 0,
+ sizeof (struct MHD_Response));
response->fd = -1;
if (! MHD_mutex_init_ (&response->mutex))
{
@@ -675,8 +692,30 @@ MHD_upgrade_action (struct MHD_UpgradeResponseHandle *urh,
/* FIXME: not implemented */
return MHD_NO;
case MHD_UPGRADE_ACTION_FLUSH:
- /* FIXME: not implemented */
- return MHD_NO;
+#if HTTPS_SUPPORT
+ if (0 != (daemon->options & MHD_USE_SSL))
+ {
+ int avail;
+
+ /* First, check that our pipe is empty, to be sure we do
+ have it all in the buffer. */
+ if ( (0 ==
+#if WINDOWS
+ ioctlsocket
+#else
+ ioctl
+#endif
+ (urh->mhd.socket,
+ FIONREAD,
+ &avail)) &&
+ (0 != avail) )
+ return MHD_NO;
+ /* then, refuse 'flush' unless our buffer is empty */
+ if (0 != urh->out_buffer_off)
+ return MHD_NO;
+ }
+#endif
+ return MHD_YES;
default:
/* we don't understand this one */
return MHD_NO;
diff --git a/src/microhttpd/test_upgrade.c b/src/microhttpd/test_upgrade.c
index 2288d927..469dc581 100644
--- a/src/microhttpd/test_upgrade.c
+++ b/src/microhttpd/test_upgrade.c
@@ -298,7 +298,9 @@ test_upgrade_internal_select ()
sa.sin_family = AF_INET;
sa.sin_port = htons (1080);
sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
- if (0 != connect (sock, (struct sockaddr *) &sa, sizeof (sa)))
+ if (0 != connect (sock,
+ (struct sockaddr *) &sa,
+ sizeof (sa)))
abort ();
send_all (sock,
"GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n");
@@ -316,12 +318,15 @@ test_upgrade_internal_select ()
int
-main (int argc, char *const *argv)
+main (int argc,
+ char *const *argv)
{
int errorCount = 0;
errorCount += test_upgrade_internal_select ();
if (errorCount != 0)
- fprintf (stderr, "Error (code: %u)\n", errorCount);
+ fprintf (stderr,
+ "Error (code: %u)\n",
+ errorCount);
return errorCount != 0; /* 0 == pass */
}
diff --git a/src/microhttpd/test_upgrade_ssl.c b/src/microhttpd/test_upgrade_ssl.c
new file mode 100644
index 00000000..5c663dd9
--- /dev/null
+++ b/src/microhttpd/test_upgrade_ssl.c
@@ -0,0 +1,472 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2016 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., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/**
+ * @file test_upgrade_ssl.c
+ * @brief Testcase for libmicrohttpd upgrading a connection
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include "microhttpd.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef WINDOWS
+#include <unistd.h>
+#endif
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <pthread.h>
+#include "mhd_sockets.h"
+
+#include "../testcurl/https/tls_test_keys.h"
+
+
+/**
+ * Thread we use to run the interaction with the upgraded socket.
+ */
+static pthread_t pt;
+
+/**
+ * Will be set to the upgraded socket.
+ */
+static MHD_socket usock;
+
+/**
+ * Fork child that connects via OpenSSL to our @a port. Allows us to
+ * talk to our port over a socket in @a sp without having to worry
+ * about TLS.
+ *
+ * @param location where the socket is returned
+ * @return -1 on error, otherwise PID of SSL child process
+ */
+static pid_t
+openssl_connect (int *sock,
+ uint16_t port)
+{
+ pid_t chld;
+ int sp[2];
+ char destination[30];
+
+ if (0 != socketpair (AF_UNIX,
+ SOCK_STREAM,
+ 0,
+ sp))
+ return -1;
+ chld = fork ();
+ if (0 != chld)
+ {
+ *sock = sp[1];
+ MHD_socket_close_ (sp[0]);
+ return chld;
+ }
+ MHD_socket_close_ (sp[1]);
+ (void) close (0);
+ (void) close (1);
+ dup2 (sp[0], 0);
+ dup2 (sp[0], 1);
+ close (sp[0]);
+ sprintf (destination,
+ "localhost:%u",
+ (unsigned int) port);
+ execlp ("openssl",
+ "openssl",
+ "s_client",
+ "-connect",
+ destination,
+ "-verify",
+ "0",
+ // "-quiet",
+ (char *) NULL);
+ _exit (1);
+}
+
+
+/**
+ * Change itc FD options to be non-blocking.
+ *
+ * @param fd the FD to manipulate
+ * @return non-zero if succeeded, zero otherwise
+ */
+static void
+make_blocking (MHD_socket fd)
+{
+ int flags;
+
+ flags = fcntl (fd, F_GETFL);
+ if (-1 == flags)
+ return;
+ if ((flags & ~O_NONBLOCK) != flags)
+ fcntl (fd, F_SETFL, flags & ~O_NONBLOCK);
+}
+
+
+static void
+send_all (MHD_socket sock,
+ const char *text)
+{
+ size_t len = strlen (text);
+ ssize_t ret;
+
+ make_blocking (sock);
+ for (size_t off = 0; off < len; off += ret)
+ {
+ ret = write (sock,
+ &text[off],
+ len - off);
+ if (-1 == ret)
+ {
+ if (EAGAIN == errno)
+ {
+ ret = 0;
+ continue;
+ }
+ abort ();
+ }
+ }
+}
+
+
+/**
+ * Read character-by-character until we
+ * get '\r\n\r\n'.
+ */
+static void
+recv_hdr (MHD_socket sock)
+{
+ unsigned int i;
+ char next;
+ char c;
+ ssize_t ret;
+
+ make_blocking (sock);
+ next = '\r';
+ i = 0;
+ while (i < 4)
+ {
+ ret = read (sock,
+ &c,
+ 1);
+ if (0 == ret)
+ abort (); /* this is fatal */
+ if (-1 == ret)
+ {
+ if (EAGAIN == errno)
+ {
+ ret = 0;
+ continue;
+ }
+ abort ();
+ }
+ if (0 == ret)
+ continue;
+ if (c == next)
+ {
+ i++;
+ if (next == '\r')
+ next = '\n';
+ else
+ next = '\r';
+ continue;
+ }
+ if (c == '\r')
+ {
+ i = 1;
+ next = '\n';
+ continue;
+ }
+ i = 0;
+ next = '\r';
+ }
+}
+
+
+static void
+recv_all (MHD_socket sock,
+ const char *text)
+{
+ size_t len = strlen (text);
+ char buf[len];
+ ssize_t ret;
+
+ make_blocking (sock);
+ for (size_t off = 0; off < len; off += ret)
+ {
+ ret = read (sock,
+ &buf[off],
+ len - off);
+ if (0 == ret)
+ abort (); /* this is fatal */
+ if (-1 == ret)
+ {
+ if (EAGAIN == errno)
+ {
+ ret = 0;
+ continue;
+ }
+ abort ();
+ }
+ }
+ if (0 != strncmp (text, buf, len))
+ abort();
+}
+
+
+/**
+ * Main function for the thread that runs the interaction with
+ * the upgraded socket.
+ *
+ * @param cls the handle for the upgrade
+ */
+static void *
+run_usock (void *cls)
+{
+ struct MHD_UpgradeResponseHandle *urh = cls;
+
+ fprintf (stderr,
+ "Sending `Hello'\n");
+ send_all (usock,
+ "Hello");
+ fprintf (stderr,
+ "Receiving `World'\n");
+ recv_all (usock,
+ "World");
+ fprintf (stderr,
+ "Sending `Finished'\n");
+ send_all (usock,
+ "Finished");
+ fprintf (stderr,
+ "Closing socket\n");
+ while (MHD_NO ==
+ MHD_upgrade_action (urh,
+ MHD_UPGRADE_ACTION_FLUSH))
+ usleep (1000);
+ MHD_upgrade_action (urh,
+ MHD_UPGRADE_ACTION_CLOSE);
+ fprintf (stderr,
+ "Thread terminating\n");
+ return NULL;
+}
+
+
+/**
+ * Function called after a protocol "upgrade" response was sent
+ * successfully and the socket should now be controlled by some
+ * protocol other than HTTP.
+ *
+ * Any data received on the socket will be made available in
+ * 'data_in'. The function should update 'data_in_size' to
+ * reflect the number of bytes consumed from 'data_in' (the remaining
+ * bytes will be made available in the next call to the handler).
+ *
+ * Any data that should be transmitted on the socket should be
+ * stored in 'data_out'. '*data_out_size' is initially set to
+ * the available buffer space in 'data_out'. It should be set to
+ * the number of bytes stored in 'data_out' (which can be zero).
+ *
+ * The return value is a BITMASK that indicates how the function
+ * intends to interact with the event loop. It can request to be
+ * notified for reading, writing, request to UNCORK the send buffer
+ * (which MHD is allowed to ignore, if it is not possible to uncork on
+ * the local platform), to wait for the 'external' select loop to
+ * trigger another round. It is also possible to specify "no events"
+ * to terminate the connection; in this case, the
+ * #MHD_RequestCompletedCallback will be called and all resources of
+ * the connection will be released.
+ *
+ * Except when in 'thread-per-connection' mode, implementations
+ * of this function should never block (as it will still be called
+ * from within the main event loop).
+ *
+ * @param cls closure, whatever was given to #MHD_create_response_for_upgrade().
+ * @param connection original HTTP connection handle,
+ * giving the function a last chance
+ * to inspect the original HTTP request
+ * @param con_cls last value left in `*con_cls` in the `MHD_AccessHandlerCallback`
+ * @param extra_in if we happened to have read bytes after the
+ * HTTP header already (because the client sent
+ * more than the HTTP header of the request before
+ * we sent the upgrade response),
+ * these are the extra bytes already read from @a sock
+ * by MHD. The application should treat these as if
+ * it had read them from @a sock.
+ * @param extra_in_size number of bytes in @a extra_in
+ * @param sock socket to use for bi-directional communication
+ * with the client. For HTTPS, this may not be a socket
+ * that is directly connected to the client and thus certain
+ * operations (TCP-specific setsockopt(), getsockopt(), etc.)
+ * may not work as expected (as the socket could be from a
+ * socketpair() or a TCP-loopback)
+ * @param urh argument for #MHD_upgrade_action()s on this @a connection.
+ * Applications must eventually use this function to perform the
+ * close() action on the @a sock.
+ */
+static void
+upgrade_cb (void *cls,
+ struct MHD_Connection *connection,
+ void *con_cls,
+ const char *extra_in,
+ size_t extra_in_size,
+ MHD_socket sock,
+ struct MHD_UpgradeResponseHandle *urh)
+{
+ usock = sock;
+ if (0 != extra_in_size)
+ abort ();
+ pthread_create (&pt,
+ NULL,
+ &run_usock,
+ urh);
+}
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
+ * must call MHD callbacks to provide content to give back to the
+ * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
+ * #MHD_HTTP_NOT_FOUND, etc.).
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param url the requested url
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param version the HTTP version string (i.e.
+ * #MHD_HTTP_VERSION_1_1)
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of #MHD_get_connection_values; very large POST
+ * data *will* be made available incrementally in
+ * @a upload_data)
+ * @param upload_data_size set initially to the size of the
+ * @a upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param con_cls pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global #MHD_RequestCompletedCallback (which
+ * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, `*con_cls` will be NULL.
+ * @return #MHD_YES if the connection was handled successfully,
+ * #MHD_NO if the socket must be closed due to a serios
+ * error while handling the request
+ */
+static int
+ahc_upgrade (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct MHD_Response *resp;
+ int ret;
+
+ resp = MHD_create_response_for_upgrade (&upgrade_cb,
+ NULL);
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_UPGRADE,
+ "Hello World Protocol");
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_SWITCHING_PROTOCOLS,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+}
+
+
+static int
+test_upgrade_internal_select ()
+{
+ struct MHD_Daemon *d;
+ MHD_socket sock;
+ pid_t pid;
+
+ d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_SUSPEND_RESUME | MHD_USE_TLS,
+ 1080,
+ NULL, NULL,
+ &ahc_upgrade, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem,
+ MHD_OPTION_END);
+ if (NULL == d)
+ return 2;
+ if (-1 == (pid = openssl_connect (&sock, 1080)))
+ {
+ MHD_stop_daemon (d);
+ return 4;
+ }
+
+ send_all (sock,
+ "GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n");
+ recv_hdr (sock);
+ recv_all (sock,
+ "Hello");
+ fprintf (stderr,
+ "Received `Hello'\n");
+ send_all (sock,
+ "World");
+ fprintf (stderr,
+ "Sent `World'\n");
+ recv_all (sock,
+ "Finished");
+ fprintf (stderr,
+ "Received `Finished'\n");
+ MHD_socket_close_ (sock);
+ pthread_join (pt,
+ NULL);
+ fprintf (stderr,
+ "Joined helper thread\n");
+ waitpid (pid, NULL, 0);
+ MHD_stop_daemon (d);
+ return 0;
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ int errorCount = 0;
+
+ if (0 != system ("openssl version 1> /dev/null"))
+ return 77; /* openssl not available, can't run the test */
+ errorCount += test_upgrade_internal_select ();
+ if (errorCount != 0)
+ fprintf (stderr,
+ "Error (code: %u)\n",
+ errorCount);
+ return errorCount != 0; /* 0 == pass */
+}