/* 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_common.c * @brief Shared logic for testcases for libmicrohttpd upgrading a connection * @author Christian Grothoff */ #include "mhd_sockets.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; /** * Thread we use to run the interaction with the upgraded socket. */ static pthread_t pt_client; /** * Flag set to 1 once the test is finished. */ static int done; static void notify_completed_cb (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { if ( (toe != MHD_REQUEST_TERMINATED_COMPLETED_OK) && (toe != MHD_REQUEST_TERMINATED_CLIENT_ABORT) && (toe != MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN) ) abort (); if (((long) *con_cls) != (long) pthread_self ()) abort (); *con_cls = NULL; } /** * Logging callback. * * @param cls logging closure (NULL) * @param uri access URI * @param connection connection handle * @return #TEST_PTR */ static void * log_cb (void *cls, const char *uri, struct MHD_Connection *connection) { if (0 != strcmp (uri, "/")) abort (); return (void *) (long) pthread_self (); } /** * Function to check that MHD properly notifies about starting * and stopping. * * @param cls client-defined closure * @param connection connection handle * @param socket_context socket-specific pointer where the * client can associate some state specific * to the TCP connection; note that this is * different from the "con_cls" which is per * HTTP request. The client can initialize * during #MHD_CONNECTION_NOTIFY_STARTED and * cleanup during #MHD_CONNECTION_NOTIFY_CLOSED * and access in the meantime using * #MHD_CONNECTION_INFO_SOCKET_CONTEXT. * @param toe reason for connection notification * @see #MHD_OPTION_NOTIFY_CONNECTION * @ingroup request */ static void notify_connection_cb (void *cls, struct MHD_Connection *connection, void **socket_context, enum MHD_ConnectionNotificationCode toe) { static int started; switch (toe) { case MHD_CONNECTION_NOTIFY_STARTED: if (MHD_NO != started) abort (); started = MHD_YES; *socket_context = &started; break; case MHD_CONNECTION_NOTIFY_CLOSED: if (MHD_YES != started) abort (); if (&started != *socket_context) abort (); *socket_context = NULL; started = MHD_NO; break; } } /** * Change socket to blocking. * * @param fd the socket to manipulate * @return non-zero if succeeded, zero otherwise */ static void make_blocking (MHD_socket fd) { #if defined(MHD_POSIX_SOCKETS) int flags; flags = fcntl (fd, F_GETFL); if (-1 == flags) return; if ((flags & ~O_NONBLOCK) != flags) if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) abort (); #elif defined(MHD_WINSOCK_SOCKETS) unsigned long flags = 1; ioctlsocket (fd, FIONBIO, &flags); #endif /* MHD_WINSOCK_SOCKETS */ } 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 = MHD_send_ (sock, &text[off], len - off); if (0 > ret) { if (MHD_SCKT_ERR_IS_EAGAIN_ (MHD_socket_get_error_ ())) { 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 = MHD_recv_ (sock, &c, 1); if (0 > ret) { if (MHD_SCKT_ERR_IS_EAGAIN_ (MHD_socket_get_error_ ())) 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 = MHD_recv_ (sock, &buf[off], len - off); if (0 > ret) { if (MHD_SCKT_ERR_IS_EAGAIN_ (MHD_socket_get_error_ ())) { 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; send_all (usock, "Hello"); recv_all (usock, "World"); send_all (usock, "Finished"); MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE); return NULL; } /** * Main function for the thread that runs the client-side of the * interaction with the upgraded socket. * * @param cls the client socket */ static void * run_usock_client (void *cls) { MHD_socket *sock = cls; send_all (*sock, "GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n"); recv_hdr (*sock); recv_all (*sock, "Hello"); send_all (*sock, "World"); recv_all (*sock, "Finished"); MHD_socket_close_chk_ (*sock); done = 1; 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 already received on the socket will be made available in * @e extra_in. This can happen if the application sent extra data * before MHD send the upgrade response. The application should * treat data from @a extra_in as if it had read it from the socket. * * Note that the application must not close() @a sock directly, * but instead use #MHD_upgrade_action() for special operations * on @a sock. * * 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` of 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). The application is expected * to perform read()/recv() and write()/send() calls on the socket. * The application may also call shutdown(), but must not call * close() directly. * @param urh argument for #MHD_upgrade_action()s on this @a connection. * Applications must eventually use this callback to (indirectly) * 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 (); if (0 != pthread_create (&pt, NULL, &run_usock, urh)) abort (); } /** * 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; if (((long) *con_cls) != (long) pthread_self ()) abort (); 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; } /** * Run the MHD external event loop using select. * * @param daemon daemon to run it for */ static void run_mhd_select_loop (struct MHD_Daemon *daemon) { fd_set rs; fd_set ws; fd_set es; MHD_socket max_fd; MHD_UNSIGNED_LONG_LONG to; struct timeval tv; while (! done) { FD_ZERO (&rs); FD_ZERO (&ws); FD_ZERO (&es); max_fd = -1; to = 1000; if (MHD_YES != MHD_get_fdset (daemon, &rs, &ws, &es, &max_fd)) abort (); (void) MHD_get_timeout (daemon, &to); if (1000 < to) to = 1000; tv.tv_sec = to / 1000; tv.tv_usec = 1000 * (to % 1000); if (0 > MHD_SYS_select_ (max_fd + 1, &rs, &ws, &es, &tv)) abort (); MHD_run_from_select (daemon, &rs, &ws, &es); } } /** * Run the MHD external event loop using select. * * @param daemon daemon to run it for */ static void run_mhd_poll_loop (struct MHD_Daemon *daemon) { abort (); /* currently not implementable with existing MHD API */ } /** * Run the MHD external event loop using select. * * @param daemon daemon to run it for */ static void run_mhd_epoll_loop (struct MHD_Daemon *daemon) { const union MHD_DaemonInfo *di; MHD_socket ep; fd_set rs; MHD_UNSIGNED_LONG_LONG to; struct timeval tv; di = MHD_get_daemon_info (daemon, MHD_DAEMON_INFO_EPOLL_FD); ep = di->listen_fd; while (! done) { FD_ZERO (&rs); to = 1000; FD_SET (ep, &rs); (void) MHD_get_timeout (daemon, &to); if (1000 < to) to = 1000; tv.tv_sec = to / 1000; tv.tv_usec = 1000 * (to % 1000); select (ep + 1, &rs, NULL, NULL, &tv); MHD_run (daemon); } } /** * Run the MHD external event loop using select. * * @param daemon daemon to run it for */ static void run_mhd_loop (struct MHD_Daemon *daemon, int flags) { if (0 != (flags & MHD_USE_POLL)) run_mhd_poll_loop (daemon); #if EPOLL_SUPPORT else if (0 != (flags & MHD_USE_EPOLL)) run_mhd_epoll_loop (daemon); #endif else run_mhd_select_loop (daemon); }