libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit ee32c4aade3403a60617ee4f2e8d390f44a80737
parent a84144f9dfb1480e46725b76022c109d15255301
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Sat, 28 Sep 2024 17:46:12 +0100

Implemented the basis of HTTP Upgrade + some fixes

Diffstat:
Mconfigure.ac | 6+++---
Msrc/include/microhttpd2.h | 335+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/include/microhttpd2_main.h.in | 276++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/include/microhttpd2_preamble.h.in | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/mhd2/Makefile.am | 32++++++++++++++++++++++++++++----
Msrc/mhd2/action.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/daemon_add_conn.c | 40++++++++++++++++++++++++++++++++++++----
Msrc/mhd2/daemon_add_conn.h | 16++++++++++++++--
Msrc/mhd2/daemon_funcs.c | 4+++-
Msrc/mhd2/daemon_funcs.h | 6+++++-
Msrc/mhd2/daemon_start.c | 39++++++++++++++++++++++++++++++++++++++-
Msrc/mhd2/events_process.c | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/mhd2/mhd_action.h | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Asrc/mhd2/mhd_cntnr_ptr.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_connection.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_daemon.h | 35++++++++++++++++++++++++++++++-----
Msrc/mhd2/mhd_limits.h | 11++++++-----
Msrc/mhd2/mhd_locks.h | 2+-
Msrc/mhd2/mhd_panic.c | 2+-
Msrc/mhd2/mhd_reply.h | 7+++++++
Msrc/mhd2/mhd_send.c | 2+-
Asrc/mhd2/mhd_upgrade.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/stream_funcs.c | 55++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/mhd2/stream_funcs.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/mhd2/stream_process_reply.c | 22+++++++++++-----------
Msrc/mhd2/stream_process_request.c | 27++++++++++++++++++++++-----
Msrc/mhd2/stream_process_states.c | 94+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Asrc/mhd2/sys_offsetof.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/upgrade_net.c | 524+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/upgrade_prep.c | 447+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/upgrade_prep.h | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/upgrade_proc.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/upgrade_proc.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
33 files changed, 2544 insertions(+), 312 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -384,7 +384,7 @@ AS_CASE([${enable_build_type}],[debug|debugger], ) MHD_CHECK_ADD_CC_CFLAGS([-Wformat-overflow -Wformat-truncation -Wformat-security -Wformat-signedness], [CFLAGS_ac]) MHD_CHECK_ADD_CC_CFLAGS([-Wmissing-include-dirs -Wshift-overflow=2 -Wstringop-overflow=4 -Walloc-zero], [CFLAGS_ac]) - MHD_CHECK_ADD_CC_CFLAGS([-Wduplicated-branches -Wduplicated-cond -Wfloat-equal -Wshadow -Wpointer-arith], [CFLAGS_ac]) + MHD_CHECK_ADD_CC_CFLAGS([-Wduplicated-branches -Wduplicated-cond -Wfloat-equal -Wpointer-arith], [CFLAGS_ac]) MHD_FIND_ADD_CC_CFLAG([CFLAGS_ac], [-Wshadow-all], [-Wshadow]) MHD_CHECK_ADD_CC_CFLAGS([-Wbad-function-cast -Wcast-qual -Wwrite-strings -Wconversion], [CFLAGS_ac]) MHD_FIND_ADD_CC_CFLAG([CFLAGS_ac], [-Wcast-align=strict], [-Wcast-align]) @@ -5307,8 +5307,8 @@ AC_ARG_ENABLE([[httpupgrade]], [[enable_httpupgrade='yes']]) AS_VAR_IF([[enable_httpupgrade]],[["yes"]], [ - AC_DEFINE([[UPGRADE_SUPPORT]],[[1]],[Define to 1 if libmicrohttpd is compiled with HTTP Upgrade support.]) ]) -AM_CONDITIONAL([ENABLE_UPGRADE], [[test "x$enable_httpupgrade" = "xyes"]]) + AC_DEFINE([[MHD_UPGRADE_SUPPORT]],[[1]],[Define to 1 if libmicrohttpd is compiled with HTTP Upgrade support.]) ]) +AM_CONDITIONAL([MHD_UPGRADE_SUPPORT], [[test "x$enable_httpupgrade" = "xyes"]]) AC_MSG_RESULT([[$enable_httpupgrade]]) # optional: HTTP cookie parsing support. Enabled by default diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h @@ -509,6 +509,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_SYS_CLOCK_JUMP_BACK_CORRECTED = 30141 , + /** + * Timeout waiting for communication operation for HTTP-Upgraded connection + */ + MHD_SC_UPGRADED_NET_TIMEOUT = 30161 + , /* 40000-level errors are caused by the HTTP client (or the network) */ @@ -1250,6 +1255,33 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_SOCKET_ZERO_SEND_FAILED = 50621 , /** + * The HTTP-Upgraded network connection has been closed / disconnected + */ + MHD_SC_UPGRADED_NET_CONN_CLOSED = 50800 + , + /** + * The HTTP-Upgraded network connection has been broken + */ + MHD_SC_UPGRADED_NET_CONN_BROKEN = 50801 + , + /** + * The TLS communication error on HTTP-Upgraded connection + */ + MHD_SC_UPGRADED_TLS_ERROR = 50801 + , + /** + * Unrecoverable sockets communication error on HTTP-Upgraded connection + */ + MHD_SC_UPGRADED_NET_HARD_ERROR = 50840 + , + /** + * MHD cannot wait for the data on the HTTP-Upgraded connection, because + * current build or the platform does not support required functionality. + * Communication with zero timeout is fully supported. + */ + MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED = 50840 + , + /** * Something wrong in the internal MHD logic. * This error should be never returned if MHD works as expected. * If this code is ever returned, please report to MHD maintainers. @@ -1354,12 +1386,12 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_CONFIGURATION_CONN_LIMIT_TOO_SMALL = 60016 , /** - * The response header name has forbidden characters + * The response header name has forbidden characters or token */ MHD_SC_RESP_HEADER_NAME_INVALID = 60050 , /** - * The response header value has forbidden characters + * The response header value has forbidden characters or token */ MHD_SC_RESP_HEADER_VALUE_INVALID = 60051 , @@ -1389,6 +1421,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_REPLY_CONTENT_LENGTH_NOT_ALLOWED = 60102 , /** + * The provided reply headers do not fit the connection buffer + */ + MHD_SC_REPLY_HEADERS_TOO_LARGE = 60103 + , + /** * The new connection cannot be used because the FD number is higher than * the limit set by FD_SETSIZE (if internal polling with select is used) or * by application. @@ -1396,6 +1433,17 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_NEW_CONN_FD_OUTSIDE_OF_SET_RANGE = 60140 , /** + * The daemon is being destroyed, while not all HTTP-Upgraded connections + * has been closed. + */ + MHD_SC_DAEMON_DESTROYED_WITH_UNCLOSED_UPGRADED = 60160 + , + /** + * The provided pointer to 'struct MHD_UpgradeHandle' is invalid + */ + MHD_SC_UPGRADED_HANDLE_INVALID = 60161 + , + /** * The requested type of information is not recognised. */ MHD_SC_INFO_TYPE_UNKNOWN = 60200 @@ -3626,6 +3674,13 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_RequestEndedCode MHD_REQUEST_ENDED_COMPLETED_OK = 0 , /** + * The response was successfully sent and connection is being switched + * to another protocol. + * @ingroup request + */ + MHD_REQUEST_ENDED_COMPLETED_OK_UPGRADE = 1 + , + /** * No activity on the connection for the number of seconds specified using * #MHD_C_OPTION_TIMEOUT(). * @ingroup request @@ -6569,81 +6624,25 @@ MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_OUT_SIZE_ (3,2); /* ***************** (c) WebSocket support ********** */ /** - * Enumeration for operations MHD should perform on the underlying socket - * of the upgrade. This API is not finalized, and in particular - * the final set of actions is yet to be decided. This is just an - * idea for what we might want. - */ -enum MHD_FIXED_ENUM_APP_SET_ MHD_UpgradeOperation -{ - - /** - * Close the socket, the application is done with it. - */ - MHD_UPGRADE_OPERATION_CLOSE = 0 - , - /** - * Turn on flushing the network buffers after each data piece. - */ - MHD_UPGRADE_OPERATION_DISABLE_NETWORK_BUFFERING = 1 - , - /** - * Turn on normal data buffering (default). - */ - MHD_UPGRADE_OPERATION_ENABLE_NETWORK_BUFFERING = 2 - , - - /* * Sentinel * */ - /** - * The sentinel value. - * This value enforces specific underlying integer type for the enum. - * Do not use. - */ - MHD_UPGRADE_OPERATION_SENTINEL = 65535 -}; - - -/** * Handle given to the application to manage special * actions relating to MHD responses that "upgrade" * the HTTP protocol (i.e. to WebSockets). */ struct MHD_UpgradeHandle; -/** - * This connection-specific callback is provided by MHD to - * applications (unusual) during the #MHD_UpgradeHandler. - * It allows applications to perform 'special' actions on - * the underlying socket from the upgrade. - * - * @param urh the handle identifying the connection to perform - * the upgrade @a action on. - * @param operation which operation should be performed - * @param ... arguments to the action (depends on the action) - * @return #MHD_NO on error, #MHD_YES on success - */ -MHD_EXTERN_ enum MHD_StatusCode -MHD_upgrade_operation (struct MHD_UpgradeHandle *urh, - enum MHD_UpgradeOperation operation) -MHD_FN_PAR_NONNULL_ (1); +#ifndef MHD_UPGRADEHANDLER_DEFINED /** - * 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. + * Function called after a protocol "upgrade" response was sent successfully + * and the connection is being switched to other protocol. * - * Note that the application must not close() @a sock directly, - * but instead use #MHD_action_upgrade() for special operations - * on @a sock. + * The newly provided handle @a urh can be used to send and receive the data + * by #MHD_upgraded_send() and #MHD_upgraded_recv(). The handle must be closed + * by #MHD_upgraded_close() before destroying the daemon. * - * Data forwarding to "upgraded" @a sock will be started as soon - * as this function return. + * "Upgraded" connection will not time out, but still counted for daemon + * global connections limit and for per-IP limit (if set). * * Except when in 'thread-per-connection' mode, implementations * of this function should never block (as it will still be called @@ -6651,25 +6650,8 @@ MHD_FN_PAR_NONNULL_ (1); * * @param cls closure, whatever was given to #MHD_action_upgrade(). * @param request original HTTP request handle, - * giving the function a last chance - * to inspect the original HTTP request - * @param extra_in_size number of bytes in @a extra_in - * @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 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. + * giving the function a last chance + * to inspect the original HTTP request * @param urh argument for #MHD_upgrade_operation() on this @a response. * Applications must eventually use this callback to (indirectly) * perform the close() action on the @a sock. @@ -6677,14 +6659,14 @@ MHD_FN_PAR_NONNULL_ (1); typedef void (*MHD_UpgradeHandler)(void *cls, struct MHD_Request *MHD_RESTRICT request, - size_t extra_in_size, - const char *extra_in, - MHD_Socket sock, struct MHD_UpgradeHandle *MHD_RESTRICT urh); +#define MHD_UPGRADEHANDLER_DEFINED 1 +#endif /* ! MHD_UPGRADEHANDLER_DEFINED */ + /** - * Create a action object that can be used for 101 UPGRADE + * Create a action object that can be used for 101 Upgrade * responses, for example to implement WebSockets. After sending the * response, control over the data stream is given to the callback (which * can then, for example, start some bi-directional communication). @@ -6692,20 +6674,11 @@ typedef void * passed to the OS; if there are communication errors before, the usual MHD * connection error handling code will be performed. * - * MHD will automatically set the correct HTTP status - * code (#MHD_HTTP_STATUS_SWITCHING_PROTOCOLS). - * Setting correct HTTP headers for the upgrade must be done - * manually (this way, it is possible to implement most existing - * WebSocket versions using this API; in fact, this API might be useful - * for any protocol switch, not just WebSockets). - * - * As usual, the response object can be extended with header - * information and then be used any number of times (as long as the - * header information is not connection-specific). - * * At most one action can be created for any request. * * @param request the request to create action for + * @param upgrade_hdr_value the value of the "Upgrade:" header, mandatory + string * @param upgrade_handler function to call with the "upgraded" socket * @param upgrade_handler_cls closure for @a upgrade_handler * @param num_headers number of elements in the @a headers array, @@ -6717,12 +6690,168 @@ typedef void * @return NULL on error (i.e. invalid arguments, out of memory) * @ingroup action */ -MHD_EXTERN_ struct MHD_Action * -MHD_action_upgrade (struct MHD_Request *request, +MHD_EXTERN_ const struct MHD_Action * +MHD_action_upgrade (struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT upgrade_hdr_value, MHD_UpgradeHandler upgrade_handler, void *upgrade_handler_cls, size_t num_headers, - const struct MHD_NameValueCStr *headers) + const struct MHD_NameValueCStr *MHD_RESTRICT headers) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (6,5); + + +/** + * Create a action object that can be used for 101 Upgrade + * responses, for example to implement WebSockets. After sending the + * response, control over the data stream is given to the callback (which + * can then, for example, start some bi-directional communication). + * The callback will ONLY be called after the response header was successfully + * passed to the OS; if there are communication errors before, the usual MHD + * connection error handling code will be performed. + * + * At most one action can be created for any request. + * + * @param request the request to create action for + * @param upgrade_hdr_value the value of the "Upgrade:" header, mandatory + string + * @param upgrade_handler function to call with the "upgraded" socket + * @param upgrade_handler_cls closure for @a upgrade_handler + * @param num_headers number of elements in the @a headers array, + * must be zero if @a headers is NULL + * @param headers the optional pointer to the array of the headers (the strings + * are copied and does not need to be valid after return from + * this function), + * can be NULL if @a num_headers is zero + * @return NULL on error (i.e. invalid arguments, out of memory) + * @ingroup action + */ +MHD_EXTERN_ const struct MHD_UploadAction * +MHD_upload_action_upgrade ( + struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT upgrade_hdr_value, + MHD_UpgradeHandler upgrade_handler, + void *upgrade_handler_cls, + size_t num_headers, + const struct MHD_NameValueCStr *MHD_RESTRICT headers) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (6,5); + + +/** + * Receive data on the HTTP-Upgraded connection. + * + * The function finished if one of the following happens: + * + ANY amount of data has been received, + * + timeout reached, + * + network error occurs + * + * @param urh the HTTP-Upgraded handle + * @param recv_buf_size the size of the @a recv_buf + * @param recv_buf the buffer to receive the data + * @param received_size the pointer to variable to get amount of received data + * @param max_wait_millisec the maximum wait time for the data, + * non-blocking operation if set to zero, + * wait indefinitely if larger or equal to + * #MHD_WAIT_INDEFINITELY, + * the function may return earlier if waiting is + * interrupted or by other reasons + * @return #MHD_SC_OK if ANY data received (check the @a received_size) or + * remote shut down send side (indicated by @a received_size + * set to zero), + * #MHD_SC_UPGRADED_NET_TIMEOUT if NO data received but timeout expired, + * #MHD_SC_UPGRADED_NET_CONN_CLOSED if network connection has been + * closed, + * #MHD_SC_UPGRADED_NET_CONN_BROKEN if broken network connection has + * been detected, + * #MHD_SC_UPGRADED_TLS_ERROR if TLS error occurs (only for TLS), + * #MHD_SC_UPGRADED_NET_HARD_ERROR if any other network or sockets + * unrecoverable error occurs, + * #MHD_SC_UPGRADED_HANDLE_INVALID if @a urh is invalid, + * #MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED if timed wait is not supported + * by this MHD build or platform + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_upgraded_recv (struct MHD_UpgradeHandle *MHD_RESTRICT urh, + size_t recv_buf_size, + void *MHD_RESTRICT recv_buf, + size_t *MHD_RESTRICT received_size, + uint_fast64_t max_wait_millisec) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_(3,2) +MHD_FN_PAR_OUT_ (4); + + +/** + * Send data on the HTTP-Upgraded connection. + * + * The function finished if one of the following happens: + * + ALL provided data has been sent, + * + timeout reached, + * + network error occurs + * + * Parameter @a more_data_to_come controls network buffering. When set to + * #MHD_YES, the OS waits shortly for additional data and tries to use + * the network more effeciently delaying the last network packet, if it is + * incomplete, to combine it with the next data provided. + * + * @param urh the HTTP-Upgraded handle + * @param send_buf_size the amount of data in the @a send_buf + * @param send_buf the buffer with the data to send + * @param sent_size the pointer to get the amout of sent data + * @param max_wait_millisec the maximum wait time for the data, + * non-blocking operation if set to zero, + * wait indefinitely if larger or equal to + * #MHD_WAIT_INDEFINITELY, + * the function may return earlier if waiting is + * interrupted or by other reasons + * @param more_data_to_come set to #MHD_YES if the provided data in + * the @a send_buf is part of a larger data package, + * like an incomplete message or streamed + * (not the final) part of some file, and more data + * expected to be sent soon over the same connection, + * set to #MHD_NO the data in the @a send_buf is + * the complete message or the final part of + * the message (or file) and it should be pushed + * to the network (and to the client) as soon + * as possible + * @return #MHD_SC_OK if ANY data sent (check the @a sent_size), + * #MHD_SC_UPGRADED_NET_TIMEOUT if NO data sent but timeout expired, + * #MHD_SC_UPGRADED_NET_CONN_CLOSED if network connection has been + * closed, + * #MHD_SC_UPGRADED_NET_CONN_BROKEN if broken network connection has + * been detected, + * #MHD_SC_UPGRADED_TLS_ERROR if TLS error occurs (only for TLS), + * #MHD_SC_UPGRADED_NET_HARD_ERROR if any other network or sockets + * unrecoverable error occurs, + * #MHD_SC_UPGRADED_HANDLE_INVALID if @a urh is invalid, + * #MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED if timed wait is not supported + * by this MHD build or platform + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_upgraded_send (struct MHD_UpgradeHandle *MHD_RESTRICT urh, + size_t send_buf_size, + const void *MHD_RESTRICT send_buf, + size_t *MHD_RESTRICT sent_size, + uint_fast64_t max_wait_millisec, + enum MHD_Bool more_data_to_come) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_(3,2) +MHD_FN_PAR_OUT_ (4); + + +/** + * Close HTTP-Upgraded connection handle. + * + * The handle cannot be used after successful return from this function. + * + * The function cannot fail if called correctly (the daemon is not destroyed + * and the upgraded connection has not been closed yet). + * + * @param urh the handle to close + * @return #MHD_SC_OK on success, + * error code otherwise + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_upgraded_close (struct MHD_UpgradeHandle *urh) MHD_FN_PAR_NONNULL_ (1); diff --git a/src/include/microhttpd2_main.h.in b/src/include/microhttpd2_main.h.in @@ -2200,81 +2200,25 @@ MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_OUT_SIZE_ (3,2); /* ***************** (c) WebSocket support ********** */ /** - * Enumeration for operations MHD should perform on the underlying socket - * of the upgrade. This API is not finalized, and in particular - * the final set of actions is yet to be decided. This is just an - * idea for what we might want. - */ -enum MHD_FIXED_ENUM_APP_SET_ MHD_UpgradeOperation -{ - - /** - * Close the socket, the application is done with it. - */ - MHD_UPGRADE_OPERATION_CLOSE = 0 - , - /** - * Turn on flushing the network buffers after each data piece. - */ - MHD_UPGRADE_OPERATION_DISABLE_NETWORK_BUFFERING = 1 - , - /** - * Turn on normal data buffering (default). - */ - MHD_UPGRADE_OPERATION_ENABLE_NETWORK_BUFFERING = 2 - , - - /* * Sentinel * */ - /** - * The sentinel value. - * This value enforces specific underlying integer type for the enum. - * Do not use. - */ - MHD_UPGRADE_OPERATION_SENTINEL = 65535 -}; - - -/** * Handle given to the application to manage special * actions relating to MHD responses that "upgrade" * the HTTP protocol (i.e. to WebSockets). */ struct MHD_UpgradeHandle; -/** - * This connection-specific callback is provided by MHD to - * applications (unusual) during the #MHD_UpgradeHandler. - * It allows applications to perform 'special' actions on - * the underlying socket from the upgrade. - * - * @param urh the handle identifying the connection to perform - * the upgrade @a action on. - * @param operation which operation should be performed - * @param ... arguments to the action (depends on the action) - * @return #MHD_NO on error, #MHD_YES on success - */ -MHD_EXTERN_ enum MHD_StatusCode -MHD_upgrade_operation (struct MHD_UpgradeHandle *urh, - enum MHD_UpgradeOperation operation) -MHD_FN_PAR_NONNULL_ (1); +#ifndef MHD_UPGRADEHANDLER_DEFINED /** - * Function called after a protocol "upgrade" response was sent - * successfully and the socket should now be controlled by some - * protocol other than HTTP. + * Function called after a protocol "upgrade" response was sent successfully + * and the connection is being switched to other protocol. * - * 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. + * The newly provided handle @a urh can be used to send and receive the data + * by #MHD_upgraded_send() and #MHD_upgraded_recv(). The handle must be closed + * by #MHD_upgraded_close() before destroying the daemon. * - * Note that the application must not close() @a sock directly, - * but instead use #MHD_action_upgrade() for special operations - * on @a sock. - * - * Data forwarding to "upgraded" @a sock will be started as soon - * as this function return. + * "Upgraded" connection will not time out, but still counted for daemon + * global connections limit and for per-IP limit (if set). * * Except when in 'thread-per-connection' mode, implementations * of this function should never block (as it will still be called @@ -2282,25 +2226,8 @@ MHD_FN_PAR_NONNULL_ (1); * * @param cls closure, whatever was given to #MHD_action_upgrade(). * @param request original HTTP request handle, - * giving the function a last chance - * to inspect the original HTTP request - * @param extra_in_size number of bytes in @a extra_in - * @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 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. + * giving the function a last chance + * to inspect the original HTTP request * @param urh argument for #MHD_upgrade_operation() on this @a response. * Applications must eventually use this callback to (indirectly) * perform the close() action on the @a sock. @@ -2308,14 +2235,14 @@ MHD_FN_PAR_NONNULL_ (1); typedef void (*MHD_UpgradeHandler)(void *cls, struct MHD_Request *MHD_RESTRICT request, - size_t extra_in_size, - const char *extra_in, - MHD_Socket sock, struct MHD_UpgradeHandle *MHD_RESTRICT urh); +#define MHD_UPGRADEHANDLER_DEFINED 1 +#endif /* ! MHD_UPGRADEHANDLER_DEFINED */ + /** - * Create a action object that can be used for 101 UPGRADE + * Create a action object that can be used for 101 Upgrade * responses, for example to implement WebSockets. After sending the * response, control over the data stream is given to the callback (which * can then, for example, start some bi-directional communication). @@ -2323,20 +2250,11 @@ typedef void * passed to the OS; if there are communication errors before, the usual MHD * connection error handling code will be performed. * - * MHD will automatically set the correct HTTP status - * code (#MHD_HTTP_STATUS_SWITCHING_PROTOCOLS). - * Setting correct HTTP headers for the upgrade must be done - * manually (this way, it is possible to implement most existing - * WebSocket versions using this API; in fact, this API might be useful - * for any protocol switch, not just WebSockets). - * - * As usual, the response object can be extended with header - * information and then be used any number of times (as long as the - * header information is not connection-specific). - * * At most one action can be created for any request. * * @param request the request to create action for + * @param upgrade_hdr_value the value of the "Upgrade:" header, mandatory + string * @param upgrade_handler function to call with the "upgraded" socket * @param upgrade_handler_cls closure for @a upgrade_handler * @param num_headers number of elements in the @a headers array, @@ -2348,12 +2266,168 @@ typedef void * @return NULL on error (i.e. invalid arguments, out of memory) * @ingroup action */ -MHD_EXTERN_ struct MHD_Action * -MHD_action_upgrade (struct MHD_Request *request, +MHD_EXTERN_ const struct MHD_Action * +MHD_action_upgrade (struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT upgrade_hdr_value, MHD_UpgradeHandler upgrade_handler, void *upgrade_handler_cls, size_t num_headers, - const struct MHD_NameValueCStr *headers) + const struct MHD_NameValueCStr *MHD_RESTRICT headers) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (6,5); + + +/** + * Create a action object that can be used for 101 Upgrade + * responses, for example to implement WebSockets. After sending the + * response, control over the data stream is given to the callback (which + * can then, for example, start some bi-directional communication). + * The callback will ONLY be called after the response header was successfully + * passed to the OS; if there are communication errors before, the usual MHD + * connection error handling code will be performed. + * + * At most one action can be created for any request. + * + * @param request the request to create action for + * @param upgrade_hdr_value the value of the "Upgrade:" header, mandatory + string + * @param upgrade_handler function to call with the "upgraded" socket + * @param upgrade_handler_cls closure for @a upgrade_handler + * @param num_headers number of elements in the @a headers array, + * must be zero if @a headers is NULL + * @param headers the optional pointer to the array of the headers (the strings + * are copied and does not need to be valid after return from + * this function), + * can be NULL if @a num_headers is zero + * @return NULL on error (i.e. invalid arguments, out of memory) + * @ingroup action + */ +MHD_EXTERN_ const struct MHD_UploadAction * +MHD_upload_action_upgrade ( + struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT upgrade_hdr_value, + MHD_UpgradeHandler upgrade_handler, + void *upgrade_handler_cls, + size_t num_headers, + const struct MHD_NameValueCStr *MHD_RESTRICT headers) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (6,5); + + +/** + * Receive data on the HTTP-Upgraded connection. + * + * The function finished if one of the following happens: + * + ANY amount of data has been received, + * + timeout reached, + * + network error occurs + * + * @param urh the HTTP-Upgraded handle + * @param recv_buf_size the size of the @a recv_buf + * @param recv_buf the buffer to receive the data + * @param received_size the pointer to variable to get amount of received data + * @param max_wait_millisec the maximum wait time for the data, + * non-blocking operation if set to zero, + * wait indefinitely if larger or equal to + * #MHD_WAIT_INDEFINITELY, + * the function may return earlier if waiting is + * interrupted or by other reasons + * @return #MHD_SC_OK if ANY data received (check the @a received_size) or + * remote shut down send side (indicated by @a received_size + * set to zero), + * #MHD_SC_UPGRADED_NET_TIMEOUT if NO data received but timeout expired, + * #MHD_SC_UPGRADED_NET_CONN_CLOSED if network connection has been + * closed, + * #MHD_SC_UPGRADED_NET_CONN_BROKEN if broken network connection has + * been detected, + * #MHD_SC_UPGRADED_TLS_ERROR if TLS error occurs (only for TLS), + * #MHD_SC_UPGRADED_NET_HARD_ERROR if any other network or sockets + * unrecoverable error occurs, + * #MHD_SC_UPGRADED_HANDLE_INVALID if @a urh is invalid, + * #MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED if timed wait is not supported + * by this MHD build or platform + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_upgraded_recv (struct MHD_UpgradeHandle *MHD_RESTRICT urh, + size_t recv_buf_size, + void *MHD_RESTRICT recv_buf, + size_t *MHD_RESTRICT received_size, + uint_fast64_t max_wait_millisec) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_(3,2) +MHD_FN_PAR_OUT_ (4); + + +/** + * Send data on the HTTP-Upgraded connection. + * + * The function finished if one of the following happens: + * + ALL provided data has been sent, + * + timeout reached, + * + network error occurs + * + * Parameter @a more_data_to_come controls network buffering. When set to + * #MHD_YES, the OS waits shortly for additional data and tries to use + * the network more effeciently delaying the last network packet, if it is + * incomplete, to combine it with the next data provided. + * + * @param urh the HTTP-Upgraded handle + * @param send_buf_size the amount of data in the @a send_buf + * @param send_buf the buffer with the data to send + * @param sent_size the pointer to get the amout of sent data + * @param max_wait_millisec the maximum wait time for the data, + * non-blocking operation if set to zero, + * wait indefinitely if larger or equal to + * #MHD_WAIT_INDEFINITELY, + * the function may return earlier if waiting is + * interrupted or by other reasons + * @param more_data_to_come set to #MHD_YES if the provided data in + * the @a send_buf is part of a larger data package, + * like an incomplete message or streamed + * (not the final) part of some file, and more data + * expected to be sent soon over the same connection, + * set to #MHD_NO the data in the @a send_buf is + * the complete message or the final part of + * the message (or file) and it should be pushed + * to the network (and to the client) as soon + * as possible + * @return #MHD_SC_OK if ANY data sent (check the @a sent_size), + * #MHD_SC_UPGRADED_NET_TIMEOUT if NO data sent but timeout expired, + * #MHD_SC_UPGRADED_NET_CONN_CLOSED if network connection has been + * closed, + * #MHD_SC_UPGRADED_NET_CONN_BROKEN if broken network connection has + * been detected, + * #MHD_SC_UPGRADED_TLS_ERROR if TLS error occurs (only for TLS), + * #MHD_SC_UPGRADED_NET_HARD_ERROR if any other network or sockets + * unrecoverable error occurs, + * #MHD_SC_UPGRADED_HANDLE_INVALID if @a urh is invalid, + * #MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED if timed wait is not supported + * by this MHD build or platform + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_upgraded_send (struct MHD_UpgradeHandle *MHD_RESTRICT urh, + size_t send_buf_size, + const void *MHD_RESTRICT send_buf, + size_t *MHD_RESTRICT sent_size, + uint_fast64_t max_wait_millisec, + enum MHD_Bool more_data_to_come) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_(3,2) +MHD_FN_PAR_OUT_ (4); + + +/** + * Close HTTP-Upgraded connection handle. + * + * The handle cannot be used after successful return from this function. + * + * The function cannot fail if called correctly (the daemon is not destroyed + * and the upgraded connection has not been closed yet). + * + * @param urh the handle to close + * @return #MHD_SC_OK on success, + * error code otherwise + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_upgraded_close (struct MHD_UpgradeHandle *urh) MHD_FN_PAR_NONNULL_ (1); diff --git a/src/include/microhttpd2_preamble.h.in b/src/include/microhttpd2_preamble.h.in @@ -509,6 +509,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_SYS_CLOCK_JUMP_BACK_CORRECTED = 30141 , + /** + * Timeout waiting for communication operation for HTTP-Upgraded connection + */ + MHD_SC_UPGRADED_NET_TIMEOUT = 30161 + , /* 40000-level errors are caused by the HTTP client (or the network) */ @@ -1250,6 +1255,33 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_SOCKET_ZERO_SEND_FAILED = 50621 , /** + * The HTTP-Upgraded network connection has been closed / disconnected + */ + MHD_SC_UPGRADED_NET_CONN_CLOSED = 50800 + , + /** + * The HTTP-Upgraded network connection has been broken + */ + MHD_SC_UPGRADED_NET_CONN_BROKEN = 50801 + , + /** + * The TLS communication error on HTTP-Upgraded connection + */ + MHD_SC_UPGRADED_TLS_ERROR = 50801 + , + /** + * Unrecoverable sockets communication error on HTTP-Upgraded connection + */ + MHD_SC_UPGRADED_NET_HARD_ERROR = 50840 + , + /** + * MHD cannot wait for the data on the HTTP-Upgraded connection, because + * current build or the platform does not support required functionality. + * Communication with zero timeout is fully supported. + */ + MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED = 50840 + , + /** * Something wrong in the internal MHD logic. * This error should be never returned if MHD works as expected. * If this code is ever returned, please report to MHD maintainers. @@ -1354,12 +1386,12 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_CONFIGURATION_CONN_LIMIT_TOO_SMALL = 60016 , /** - * The response header name has forbidden characters + * The response header name has forbidden characters or token */ MHD_SC_RESP_HEADER_NAME_INVALID = 60050 , /** - * The response header value has forbidden characters + * The response header value has forbidden characters or token */ MHD_SC_RESP_HEADER_VALUE_INVALID = 60051 , @@ -1389,6 +1421,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_REPLY_CONTENT_LENGTH_NOT_ALLOWED = 60102 , /** + * The provided reply headers do not fit the connection buffer + */ + MHD_SC_REPLY_HEADERS_TOO_LARGE = 60103 + , + /** * The new connection cannot be used because the FD number is higher than * the limit set by FD_SETSIZE (if internal polling with select is used) or * by application. @@ -1396,6 +1433,17 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_NEW_CONN_FD_OUTSIDE_OF_SET_RANGE = 60140 , /** + * The daemon is being destroyed, while not all HTTP-Upgraded connections + * has been closed. + */ + MHD_SC_DAEMON_DESTROYED_WITH_UNCLOSED_UPGRADED = 60160 + , + /** + * The provided pointer to 'struct MHD_UpgradeHandle' is invalid + */ + MHD_SC_UPGRADED_HANDLE_INVALID = 60161 + , + /** * The requested type of information is not recognised. */ MHD_SC_INFO_TYPE_UNKNOWN = 60200 @@ -3626,6 +3674,13 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_RequestEndedCode MHD_REQUEST_ENDED_COMPLETED_OK = 0 , /** + * The response was successfully sent and connection is being switched + * to another protocol. + * @ingroup request + */ + MHD_REQUEST_ENDED_COMPLETED_OK_UPGRADE = 1 + , + /** * No activity on the connection for the number of seconds specified using * #MHD_C_OPTION_TIMEOUT(). * @ingroup request diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am @@ -24,6 +24,7 @@ MOSTLYCLEANFILES = libmicrohttpd2_la_SOURCES = \ $(CONFIG_HEADER) \ autoinit_funcs.h \ + sys_offsetof.h \ sys_null_macro.h sys_base_types.h sys_bool_type.h \ sys_sockets_types.h sys_sockets_headers.h sys_ip_headers.h \ sys_errno.h sys_file_fd.h sys_malloc.h \ @@ -31,6 +32,7 @@ libmicrohttpd2_la_SOURCES = \ sys_sendfile.h \ compat_calloc.h \ mhd_assert.h \ + mhd_cntnr_ptr.h \ mhd_tristate.h \ mhd_socket_type.h mhd_sockets_macros.h \ mhd_sockets_funcs.c mhd_sockets_funcs.h \ @@ -96,10 +98,19 @@ post_parser_files = \ mhd_post_parser.h mhd_post_result.h mhd_postfield_int.h \ post_parser_funcs.c post_parser_funcs.h +upgrade_files = \ + mhd_upgrade.h \ + upgrade_prep.c upgrade_prep.h \ + upgrade_proc.c upgrade_proc.h \ + upgrade_net.c + if HAVE_POST_PARSER libmicrohttpd2_la_SOURCES += $(post_parser_files) endif +if MHD_UPGRADE_SUPPORT + libmicrohttpd2_la_SOURCES += $(upgrade_files) +endif libmicrohttpd2_la_CPPFLAGS = \ $(AM_CPPFLAGS) $(MHD_LIB_CPPFLAGS) $(MHD_TLS_LIB_CPPFLAGS) \ @@ -163,16 +174,12 @@ $(top_srcdir)/po/POTFILES.in: $(srcdir)/Makefile.am echo "$(subdir)/$$src" >> "$@" ; \ done -.PHONY: update-po-POTFILES.in - EXTRA_DIST = \ libmicrohttpd2.pc.in pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libmicrohttpd2.pc -.NOTPARALLEL: $(srcdir)/daemon_options.h $(srcdir)/daemon_set_options.c $(srcdir)/response_options.h $(srcdir)/response_set_options.c - $(CONFIG_HEADER): $(builddir)/../incl_priv/mhd_config.h.in $(top_srcdir)/configure $(top_builddir)/config.status @echo "cd $(srcdir)/../incl_priv && $(MAKE) $(AM_MAKEFLAGS) mhd_config.h" && \ $(am__cd) $(srcdir)/../incl_priv && $(MAKE) $(AM_MAKEFLAGS) mhd_config.h @@ -180,3 +187,20 @@ $(CONFIG_HEADER): $(builddir)/../incl_priv/mhd_config.h.in $(top_srcdir)/configu $(builddir)/../incl_priv/mhd_config.h.in: $(top_srcdir)/configure.ac @echo "cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh" && \ $(am__cd) $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +check-sources-missing: + @$(am__cd) "$(srcdir)" && \ + echo $(ECHO_N) "Checking for missing sources" ; \ + res="" ; \ + for src in *.[ch] ; do \ + if $(GREP) -q -e "\b$${src//./\.}\b" Makefile.am ; then : ; else \ + res="$$res $$src" ; \ + fi ; \ + echo $(ECHO_N) "." ; \ + done ; \ + echo " done" ; \ + test -z "$$res" || echo "The following sources are missing in Makefile.am:$${res}" + +.PHONY: update-po-POTFILES.in check-sources-missing + +.NOTPARALLEL: $(srcdir)/daemon_options.h $(srcdir)/daemon_set_options.c $(srcdir)/response_options.h $(srcdir)/response_set_options.c diff --git a/src/mhd2/action.c b/src/mhd2/action.c @@ -34,6 +34,10 @@ #include "response_funcs.h" #include "response_destroy.h" +#ifdef MHD_UPGRADE_SUPPORT +# include "upgrade_prep.h" +#endif + #include "mhd_public_api.h" @@ -145,6 +149,82 @@ MHD_action_parse_post (struct MHD_Request *request, } +#ifdef MHD_UPGRADE_SUPPORT + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (6,5) const struct MHD_Action * +MHD_action_upgrade (struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT upgrade_hdr_value, + MHD_UpgradeHandler upgrade_handler, + void *upgrade_handler_cls, + size_t num_headers, + const struct MHD_NameValueCStr *MHD_RESTRICT headers) +{ + struct MHD_Action *const restrict head_act = + &(request->app_act.head_act); + if (mhd_ACTION_NO_ACTION != head_act->act) + return (const struct MHD_Action *) NULL; + if (NULL == upgrade_handler) + return (const struct MHD_Action *) NULL; + if (request->cntn.cntn_size != request->cntn.recv_size) + return (const struct MHD_Action *) NULL; /* Cannot start "Upgrade" if any content upload is pending */ + + if (! mhd_upgrade_prep_for_action (request, + upgrade_hdr_value, + num_headers, + headers, + false)) + return (const struct MHD_Action *) NULL; + + head_act->act = mhd_ACTION_UPGRADE; + head_act->data.upgrd.cb = upgrade_handler; + head_act->data.upgrd.cb_cls = upgrade_handler_cls; + + return head_act; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (6,5) const struct MHD_UploadAction * +MHD_upload_action_upgrade ( + struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT upgrade_hdr_value, + MHD_UpgradeHandler upgrade_handler, + void *upgrade_handler_cls, + size_t num_headers, + const struct MHD_NameValueCStr *MHD_RESTRICT headers) +{ + struct MHD_UploadAction *const restrict upl_act = + &(request->app_act.upl_act); + if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) + return (const struct MHD_UploadAction *) NULL; + if (NULL == upgrade_handler) + return (const struct MHD_UploadAction *) NULL; + if (request->cntn.cntn_size != request->cntn.recv_size) + return (const struct MHD_UploadAction *) NULL; /* Cannot start "Upgrade" if any content upload is pending */ + + if (! mhd_upgrade_prep_for_action (request, + upgrade_hdr_value, + num_headers, + headers, + true)) + return (const struct MHD_UploadAction *) NULL; + + upl_act->act = mhd_UPLOAD_ACTION_UPGRADE; + upl_act->data.upgrd.cb = upgrade_handler; + upl_act->data.upgrd.cb_cls = upgrade_handler_cls; + + return upl_act; +} + + +#endif /* MHD_UPGRADE_SUPPORT */ + + MHD_EXTERN_ MHD_FN_PAR_NONNULL_ALL_ const struct MHD_UploadAction * MHD_upload_action_suspend (struct MHD_Request *request) diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c @@ -916,13 +916,15 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void -mhd_conn_close_final (struct MHD_Connection *restrict c) +mhd_conn_remove_from_daemon (struct MHD_Connection *restrict c) { mhd_assert (c->dbg.closing_started); mhd_assert (c->dbg.pre_cleaned); + mhd_assert (! c->dbg.removed_from_daemon); mhd_assert (NULL == c->rp.response); mhd_assert (! c->rq.app_aware); mhd_assert (! c->in_proc_ready); + mhd_assert (NULL == c->rq.cntn.lbuf.data); mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (c, proc_ready)); mhd_assert (NULL == mhd_DLINKEDL_GET_PREV (c, proc_ready)); mhd_assert (c != mhd_DLINKEDL_GET_FIRST (&(c->daemon->events), proc_ready)); @@ -940,12 +942,42 @@ mhd_conn_close_final (struct MHD_Connection *restrict c) mhd_DLINKEDL_DEL (&(c->daemon->conns), c, all_conn); // TODO: update per-IP limits - if (NULL != c->addr) - free (c->addr); - mhd_socket_close (c->socket_fd); c->daemon->conns.count--; c->daemon->conns.block_new = false; +#ifndef NDEBUG + c->dbg.removed_from_daemon = true; +#endif /* NDEBUG */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_close_final (struct MHD_Connection *restrict c) +{ + mhd_assert (c->dbg.closing_started); + mhd_assert (c->dbg.pre_cleaned); + mhd_assert (c->dbg.removed_from_daemon); + mhd_assert (NULL == c->rp.response); + mhd_assert (! c->rq.app_aware); + mhd_assert (! c->in_proc_ready); + mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (c, proc_ready)); + mhd_assert (NULL == mhd_DLINKEDL_GET_PREV (c, proc_ready)); + mhd_assert (c != mhd_DLINKEDL_GET_FIRST (&(c->daemon->events), proc_ready)); + mhd_assert (c != mhd_DLINKEDL_GET_LAST (&(c->daemon->events), proc_ready)); + + mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (c, by_timeout)); + mhd_assert (NULL == mhd_DLINKEDL_GET_PREV (c, by_timeout)); + mhd_assert (NULL == c->pool); + + mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (c, all_conn)); + mhd_assert (NULL == mhd_DLINKEDL_GET_PREV (c, all_conn)); + mhd_assert (c != mhd_DLINKEDL_GET_FIRST (&(c->daemon->conns), all_conn)); + mhd_assert (c != mhd_DLINKEDL_GET_LAST (&(c->daemon->conns), all_conn)); + + if (NULL != c->addr) + free (c->addr); + mhd_socket_close (c->socket_fd); + free (c); } diff --git a/src/mhd2/daemon_add_conn.h b/src/mhd2/daemon_add_conn.h @@ -80,12 +80,24 @@ enum mhd_DaemonAcceptResult MHD_INTERNAL enum mhd_DaemonAcceptResult mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon); + +/** + * Remove all referenced to the connection from the daemon. + * Must be performed only when connection thread (for thread-per-connection) + * has stopped. + * @param c the connection to remove + */ +MHD_INTERNAL void +mhd_conn_remove_from_daemon (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + /** - * Finally close and clean-up connection. + * Finally close and deallocate connection. * Must be performed only when connection thread (for thread-per-connection) * has stopped. * The connection data deallocated by this function and cannot be used anymore. - * The function must be the last function called for connection object. + * This function must be the last function called for connection object. * @param c the connection to close */ MHD_INTERNAL void diff --git a/src/mhd2/daemon_funcs.c b/src/mhd2/daemon_funcs.c @@ -68,7 +68,9 @@ mhd_daemon_trigger_itc (struct MHD_Daemon *restrict d) #endif /* MHD_USE_THREADS */ -MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +MHD_NORETURN_ // TODO: implement +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ALL_ void mhd_daemon_resume_conns (struct MHD_Daemon *restrict d) { (void) d; diff --git a/src/mhd2/daemon_funcs.h b/src/mhd2/daemon_funcs.h @@ -57,13 +57,17 @@ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_; MHD_INTERNAL bool mhd_daemon_trigger_itc (struct MHD_Daemon *restrict d); -#endif /* MHD_USE_THREADS */ +#else /* ! MHD_USE_THREADS */ + +#define mhd_daemon_trigger_itc (d) ((void) d, ! 0) +#endif /* ! MHD_USE_THREADS */ /** * Check whether any resuming connections are pending and resume them * @param d the daemon to use */ +MHD_NORETURN_ // TODO: implement MHD_INTERNAL void mhd_daemon_resume_conns (struct MHD_Daemon *restrict d) MHD_FN_PAR_NONNULL_ALL_; diff --git a/src/mhd2/daemon_start.c b/src/mhd2/daemon_start.c @@ -1893,7 +1893,7 @@ init_individual_conns (struct MHD_Daemon *restrict d, mhd_DLINKEDL_INIT_LIST (&(d->conns),all_conn); mhd_DLINKEDL_INIT_LIST (&(d->conns),def_timeout); - mhd_DLINKEDL_INIT_LIST (&(d->conns),to_clean); + mhd_DLINKEDL_INIT_LIST (&(d->conns),cust_timeout); d->conns.count = 0; d->conns.block_new = false; @@ -1903,6 +1903,17 @@ init_individual_conns (struct MHD_Daemon *restrict d, else if (256 > d->conns.cfg.mem_pool_size) d->conns.cfg.mem_pool_size = 256; +#ifdef MHD_UPGRADE_SUPPORT + mhd_DLINKEDL_INIT_LIST (&(d->conns.upgr),upgr_cleanup); + if (! mhd_mutex_init (&(d->conns.upgr.ucu_lock))) + { + mhd_LOG_MSG (d, MHD_SC_MUTEX_INIT_FAILURE, \ + "Failed to initialise mutex for the upgraded " \ + "connection list."); + return MHD_SC_MUTEX_INIT_FAILURE; + } +#endif /* MHD_UPGRADE_SUPPORT */ + #ifndef NDEBUG d->dbg.connections_inited = true; #endif @@ -1911,6 +1922,31 @@ init_individual_conns (struct MHD_Daemon *restrict d, /** + * Deinitialise daemon connections' data. + * @param d the daemon object + */ +static MHD_FN_PAR_NONNULL_ (1) void +deinit_individual_conns (struct MHD_Daemon *restrict d) +{ +#ifdef MHD_UPGRADE_SUPPORT + mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->conns.upgr),upgr_cleanup)); + mhd_assert (NULL == mhd_DLINKEDL_GET_LAST (&(d->conns.upgr),upgr_cleanup)); + + mhd_mutex_destroy_chk (&(d->conns.upgr.ucu_lock)); +#endif /* MHD_UPGRADE_SUPPORT */ + + mhd_assert (0 != d->conns.cfg.mem_pool_size); + mhd_assert (0 == d->conns.count); + mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->conns),cust_timeout)); + mhd_assert (NULL == mhd_DLINKEDL_GET_LAST (&(d->conns),cust_timeout)); + mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->conns),def_timeout)); + mhd_assert (NULL == mhd_DLINKEDL_GET_LAST (&(d->conns),def_timeout)); + mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn)); + mhd_assert (NULL == mhd_DLINKEDL_GET_LAST (&(d->conns),all_conn)); +} + + +/** * Prepare daemon-local (worker daemon for thread pool mode) threading data * and finish events initialising. * To be used only with non-master daemons. @@ -1976,6 +2012,7 @@ init_individual_thread_data_events_conns (struct MHD_Daemon *restrict d, static MHD_FN_PAR_NONNULL_ (1) void deinit_individual_thread_data_events_conns (struct MHD_Daemon *restrict d) { + deinit_individual_conns (d); deinit_itc (d); deallocate_events (d); mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn)); diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c @@ -27,6 +27,8 @@ #include "mhd_sys_options.h" #include "events_process.h" +#include "mhd_locks.h" + #include "mhd_socket_type.h" #include "sys_poll.h" #include "sys_select.h" @@ -53,6 +55,10 @@ #include "conn_data_process.h" #include "stream_funcs.h" +#ifdef MHD_UPGRADE_SUPPORT +# include "upgrade_proc.h" +#endif /* MHD_UPGRADE_SUPPORT */ + #include "mhd_public_api.h" static int @@ -234,6 +240,31 @@ daemon_accept_new_conns (struct MHD_Daemon *restrict d) } +/** + * Check whether particular connection should be excluded from standard HTTP + * communication. + * @param c the connection the check + * @return 'true' if connection should not be used for HTTP communication + * 'false' if connection should be processed as HTTP + */ +MHD_static_inline_ MHD_FN_PAR_NONNULL_ALL_ bool +is_conn_excluded_from_http_comm (struct MHD_Connection *restrict c) +{ +#ifdef MHD_UPGRADE_SUPPORT + if (NULL != c->upgr.c) + { + mhd_assert ((MHD_CONNECTION_UPGRADED == c->state) || \ + (MHD_CONNECTION_UPGRADED_CLEANING == c->state)); + return true; + } +#endif /* MHD_UPGRADE_SUPPORT */ + + // TODO: Support suspended connection + + return false; +} + + static bool daemon_process_all_active_conns (struct MHD_Daemon *restrict d) { @@ -248,6 +279,7 @@ daemon_process_all_active_conns (struct MHD_Daemon *restrict d) if (! mhd_conn_process_recv_send_data (c)) { mhd_conn_pre_clean (c); + mhd_conn_remove_from_daemon (c); mhd_conn_close_final (c); } @@ -257,24 +289,81 @@ daemon_process_all_active_conns (struct MHD_Daemon *restrict d) } +#ifdef MHD_UPGRADE_SUPPORT +/** + * Clean-up all HTTP-Upgraded connections scheduled for clean-up + * @param d the daemon to process + */ +static MHD_FN_PAR_NONNULL_ALL_ void +daemon_cleanup_upgraded_conns (struct MHD_Daemon *restrict d) +{ + mhd_assert (! mhd_D_HAS_WORKERS (d)); + + while (true) + { + struct MHD_Connection *c; + + mhd_mutex_lock_chk (&(d->conns.upgr.ucu_lock)); + c = mhd_DLINKEDL_GET_FIRST (&(d->conns.upgr), upgr_cleanup); + if (NULL != c) + mhd_DLINKEDL_DEL (&(d->conns.upgr), c, upgr_cleanup); + mhd_mutex_unlock_chk (&(d->conns.upgr.ucu_lock)); + + if (NULL == c) + break; + + mhd_assert (MHD_CONNECTION_UPGRADED_CLEANING == c->state); + mhd_upgraded_deinit (c); + mhd_conn_pre_clean (c); + mhd_conn_remove_from_daemon (c); + mhd_conn_close_final (c); + } +} + + +#else /* ! MHD_UPGRADE_SUPPORT */ +#define daemon_cleanup_upgraded_conns(d) ((void) d) +#endif /* ! MHD_UPGRADE_SUPPORT */ + static void close_all_daemon_conns (struct MHD_Daemon *d) { struct MHD_Connection *c; + bool has_upgraded_unclosed; + has_upgraded_unclosed = false; if (! mhd_D_HAS_THR_PER_CONN (d)) { for (c = mhd_DLINKEDL_GET_LAST (&(d->conns),all_conn); NULL != c; c = mhd_DLINKEDL_GET_LAST (&(d->conns),all_conn)) { - mhd_conn_pre_close_d_shutdown (c); +#ifdef MHD_UPGRADE_SUPPORT + mhd_assert (MHD_CONNECTION_UPGRADING != c->state); + mhd_assert (MHD_CONNECTION_UPGRADED_CLEANING != c->state); + if (NULL != c->upgr.c) + { + mhd_assert (c == c->upgr.c); + has_upgraded_unclosed = true; + mhd_upgraded_deinit (c); + } + else /* Combined with the next 'if' */ +#endif + if (1) + mhd_conn_start_closing_d_shutdown (c); mhd_conn_pre_clean (c); + mhd_conn_remove_from_daemon (c); mhd_conn_close_final (c); } } else mhd_assert (0 && "Not implemented yet"); + + if (has_upgraded_unclosed) + mhd_LOG_MSG (d, MHD_SC_DAEMON_DESTROYED_WITH_UNCLOSED_UPGRADED, \ + "The daemon is being destroyed, but at least one " \ + "HTTP-Upgraded connection is unclosed. Any use (including " \ + "closing) of such connections is undefined behaviour."); } @@ -377,6 +466,8 @@ select_update_fdsets (struct MHD_Daemon *restrict d, c = mhd_DLINKEDL_GET_NEXT (c,all_conn)) { mhd_assert (MHD_CONNECTION_CLOSED != c->state); + if (is_conn_excluded_from_http_comm (c)) + continue; if (0 != (c->event_loop_info & MHD_EVENT_LOOP_INFO_RECV)) fd_set_wrap (c->socket_fd, @@ -476,10 +567,18 @@ select_update_statuses_from_fdsets (struct MHD_Daemon *d, (NULL != c) && (0 != num_events); c = mhd_DLINKEDL_GET_NEXT (c, all_conn)) { - const MHD_Socket sk = c->socket_fd; - bool recv_ready = FD_ISSET (sk, rfds); - bool send_ready = FD_ISSET (sk, wfds); - bool err_state = FD_ISSET (sk, efds); + MHD_Socket sk; + bool recv_ready; + bool send_ready; + bool err_state; + + if (is_conn_excluded_from_http_comm (c)) + continue; + + sk = c->socket_fd; + recv_ready = FD_ISSET (sk, rfds); + send_ready = FD_ISSET (sk, wfds); + err_state = FD_ISSET (sk, efds); update_conn_net_status (d, c, @@ -621,6 +720,10 @@ poll_update_fds (struct MHD_Daemon *restrict d, c = mhd_DLINKEDL_GET_NEXT (c,all_conn)) { unsigned short events; /* 'unsigned' for correct bits manipulations */ + + if (is_conn_excluded_from_http_comm (c)) + continue; + mhd_assert ((i_c - i_s) < d->conns.cfg.count_limit); mhd_assert (i_c < d->dbg.num_events_elements); mhd_assert (MHD_CONNECTION_CLOSED != c->state); @@ -733,6 +836,7 @@ poll_update_statuses_from_fds (struct MHD_Daemon *restrict d, d->events.data.poll.rel[i_c].fd_id); c = d->events.data.poll.rel[i_c].connection; + mhd_assert (! is_conn_excluded_from_http_comm (c)); mhd_assert (c->socket_fd == d->events.data.poll.fds[i_c].fd); revents = d->events.data.poll.fds[i_c].revents; recv_ready = (0 != (revents & (MHD_POLL_IN | POLLIN))); @@ -886,6 +990,7 @@ poll_update_statuses_from_eevents (struct MHD_Daemon *restrict d, bool err_state; struct MHD_Connection *const restrict c = (struct MHD_Connection *) e->data.ptr; + mhd_assert (! is_conn_excluded_from_http_comm (c)); recv_ready = (0 != (e->events & (EPOLLIN | EPOLLERR | EPOLLHUP))); send_ready = (0 != (e->events & (EPOLLOUT | EPOLLERR | EPOLLHUP))); err_state = (0 != (e->events & (EPOLLERR | EPOLLHUP))); @@ -1000,6 +1105,7 @@ process_all_events_and_data (struct MHD_Daemon *restrict d) d->events.act_req.accept = false; } daemon_process_all_active_conns (d); + daemon_cleanup_upgraded_conns (d); return ! d->threading.stop_requested; } @@ -1107,7 +1213,7 @@ mhd_worker_listening_only (void *cls) mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC mhd_worker_connection (void *cls) { - (void) cls; - mhd_assert (0 && "Not yet implemented"); + if (cls) // TODO: Implement + MHD_PANIC ("Not yet implemented"); return (mhd_THRD_RTRN_TYPE) 0; } diff --git a/src/mhd2/mhd_action.h b/src/mhd2/mhd_action.h @@ -39,6 +39,8 @@ # include "mhd_post_result.h" #endif +struct MHD_Response; /* forward declaration */ +struct MHD_Request; /* forward declaration */ /** * The type of the action requested by application @@ -71,6 +73,13 @@ enum mhd_ActionType * Suspend requests (connection) */ mhd_ACTION_SUSPEND +#ifdef MHD_UPGRADE_SUPPORT + , + /** + * Perform HTTP "upgrade" + */ + mhd_ACTION_UPGRADE +#endif /* MHD_UPGRADE_SUPPORT */ , /** * Hard close request with no response @@ -85,9 +94,6 @@ enum mhd_ActionType ((mhd_ACTION_RESPONSE <= (act)) && (mhd_ACTION_ABORT >= (act))) -struct MHD_Response; /* forward declaration */ -struct MHD_Request; /* forward declaration */ - #ifndef MHD_UPLOADCALLBACK_DEFINED typedef const struct MHD_UploadAction * @@ -204,6 +210,41 @@ struct mhd_PostParseActionData #endif /* HAVE_POST_PARSER */ + +#ifdef MHD_UPGRADE_SUPPORT + +struct MHD_UpgradeHandle; /* forward declaration */ + +#ifndef MHD_UPGRADEHANDLER_DEFINED + +typedef void +(*MHD_UpgradeHandler)(void *cls, + struct MHD_Request *MHD_RESTRICT request, + struct MHD_UpgradeHandle *MHD_RESTRICT urh); + +#define MHD_UPGRADEHANDLER_DEFINED 1 +#endif /* ! MHD_UPGRADEHANDLER_DEFINED */ + + +/** + * The data for "Upgrade" action + */ +struct mhd_UpgradeActionData +{ + /** + * The "upgrade" handler callback + */ + MHD_UpgradeHandler cb; + + /** + * The closure for the @a cb callback + */ + void *cb_cls; +}; + +#endif /* MHD_UPGRADE_SUPPORT */ + + /** * The data for the application action */ @@ -225,6 +266,12 @@ union mhd_ActionData */ struct mhd_PostParseActionData post_parse; #endif /* HAVE_POST_PARSER */ +#ifdef MHD_UPGRADE_SUPPORT + /** + * The data for "Upgrade" action + */ + struct mhd_UpgradeActionData upgrd; +#endif /* MHD_UPGRADE_SUPPORT */ }; @@ -268,6 +315,13 @@ enum mhd_UploadActionType * Suspend requests (connection) */ mhd_UPLOAD_ACTION_SUSPEND +#ifdef MHD_UPGRADE_SUPPORT + , + /** + * Perform HTTP "upgrade" + */ + mhd_UPLOAD_ACTION_UPGRADE +#endif /* MHD_UPGRADE_SUPPORT */ , /** * Hard close request with no response @@ -292,6 +346,12 @@ union mhd_UploadActionData * The data for the action #mhd_ACTION_RESPONSE */ struct MHD_Response *response; +#ifdef MHD_UPGRADE_SUPPORT + /** + * The data for "Upgrade" action + */ + struct mhd_UpgradeActionData upgrd; +#endif /* MHD_UPGRADE_SUPPORT */ }; /** diff --git a/src/mhd2/mhd_cntnr_ptr.h b/src/mhd2/mhd_cntnr_ptr.h @@ -0,0 +1,54 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_cntnr_ptr.h + * @brief The definition of mhd_cntnr_ptr() macro. + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_CNTNR_PTR_H +#define MHD_CNTNR_PTR_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#include "sys_offsetof.h" + +/** + * Get the pointer to the outer @a cntnr_type structure containing @a membr_name + * member by the @a membr_ptr pointer to the member. + * + * This macro checks at compile time whether pointer to the @a membr_name in + * the @a cntnr_type is compatible with the provided @a membr_ptr pointer. + * + * @param membr_ptr the pointer to the member + * @param cntnr_type the type of the container with the @a membr_name member + * @param membr_name the name of the member pointed by @a membr_ptr + * @return the pointer to the outer structure + */ +#define mhd_cntnr_ptr(membr_ptr,cntnr_type,membr_name) \ + ((cntnr_type*) (void*) \ + (((char*) (0 ? \ + (&(((cntnr_type*) NULL)->membr_name)) : \ + (membr_ptr))) - offsetof (cntnr_type,membr_name))) + +#endif /* ! MHD_CNTNR_PTR_H */ diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h @@ -47,6 +47,10 @@ #include "mhd_request.h" #include "mhd_reply.h" +#ifdef MHD_UPGRADE_SUPPORT +# include "mhd_upgrade.h" +#endif /* MHD_UPGRADE_SUPPORT */ + #include "mhd_socket_error.h" #include "mhd_public_api.h" @@ -96,6 +100,13 @@ enum MHD_FIXED_FLAGS_ENUM_ MHD_ConnectionEventLoopInfo * We are finished and are awaiting cleanup. */ MHD_EVENT_LOOP_INFO_CLEANUP = 1 << 5 +#ifdef MHD_UPGRADE_SUPPORT + , + /** + * We are finished and are awaiting cleanup. + */ + MHD_EVENT_LOOP_INFO_UPGRADED = 1 << 6 +#endif /* MHD_UPGRADE_SUPPORT */ }; #define MHD_EVENT_LOOP_INFO_PROCESS_READ \ @@ -290,6 +301,13 @@ enum MHD_FIXED_ENUM_ MHD_CONNECTION_STATE * We have sent the response headers. Get ready to send the body. */ MHD_CONNECTION_HEADERS_SENT +#ifdef MHD_UPGRADE_SUPPORT + , + /** + * Sending special HTTP "Upgrade" headers + */ + MHD_CONNECTION_UPGRADE_HEADERS_SENDING +#endif /* MHD_UPGRADE_SUPPORT */ , /** * We are waiting for the client to provide more @@ -327,6 +345,25 @@ enum MHD_FIXED_ENUM_ MHD_CONNECTION_STATE * Shutdown connection or restart processing to get a new request. */ MHD_CONNECTION_FULL_REPLY_SENT +#ifdef MHD_UPGRADE_SUPPORT + , + /** + * Transition to "Upgraded" state + */ + MHD_CONNECTION_UPGRADING + , + /** + * Sending and receiving data on HTTP-Upgraded connection channel. + * Normal data processing and connection handling is not performed + * by MHD anymore. + */ + MHD_CONNECTION_UPGRADED + , + /** + * Closing HTTP-Upgraded connection + */ + MHD_CONNECTION_UPGRADED_CLEANING +#endif /* MHD_UPGRADE_SUPPORT */ , /** * Finished regular connection processing. @@ -345,6 +382,7 @@ struct mhd_ConnDebugData { bool closing_started; bool pre_cleaned; + bool removed_from_daemon; }; /** @@ -416,6 +454,18 @@ struct MHD_Connection */ mhd_DLNKDL_LINKS (MHD_Connection,by_timeout); +#ifdef MHD_UPGRADE_SUPPORT + /** + * The data for handling HTTP-Upgraded connection + */ + struct MHD_UpgradeHandle upgr; + + /** + * Double-linke list of HTTP-Upgraded connections waiting for clean-up + */ + mhd_DLNKDL_LINKS (MHD_Connection,upgr_cleanup); +#endif /* MHD_UPGRADE_SUPPORT */ + /** * True if connection is suspended */ diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h @@ -713,6 +713,29 @@ struct mhd_DaemonConnectionsSettings size_t mem_pool_size; }; +#ifdef MHD_UPGRADE_SUPPORT + +/** + * The data for HTTP-Upgraded connections + */ +struct mhd_DaemonConnectionsUpgraded +{ + /** + * The list of HTTP-Upgraded connection closed by application and + * queued for cleanup + */ + mhd_DLNKDL_LIST (MHD_Connection,upgr_cleanup); + +#ifdef MHD_USE_THREADS + /** + * The mutex to change or check the @a upgr_cleanup list values + */ + mhd_mutex ucu_lock; +#endif +}; + +#endif /* MHD_UPGRADE_SUPPORT */ + /** * Connections handling data */ @@ -736,11 +759,6 @@ struct mhd_DaemonConnections mhd_DLNKDL_LIST (MHD_Connection,cust_timeout); /** - * The list of all daemon's connections - */ - mhd_DLNKDL_LIST (MHD_Connection,to_clean); - - /** * The current number of connections handled by the daemon */ unsigned int count; @@ -758,6 +776,13 @@ struct mhd_DaemonConnections * Configured settings for the daemon's connections */ struct mhd_DaemonConnectionsSettings cfg; + +#ifdef MHD_UPGRADE_SUPPORT + /** + * The data for HTTP-Upgraded connections + */ + struct mhd_DaemonConnectionsUpgraded upgr; +#endif /* MHD_UPGRADE_SUPPORT */ }; /** diff --git a/src/mhd2/mhd_limits.h b/src/mhd2/mhd_limits.h @@ -39,15 +39,16 @@ # include <limits.h> #endif /* HAVE_LIMITS_H */ -#define mhd_UNSIGNED_TYPE_MAX(type) ((type)(~((type) 0))) +#define mhd_UNSIGNED_TYPE_MAX(type) ((type) (~((type) 0))) /* Assume 8 bits per byte, no padding bits. */ #define mhd_SIGNED_TYPE_MAX(type) \ - ( (type) ((( ((type) 1) << (sizeof(type) * 8 - 2)) - 1) * 2 + 1) ) + ( (type) ((( ((type) 1) << (sizeof(type) * 8 - 2)) - 1) * 2 + 1) ) /* The maximum value for signed type, based on knowledge of unsigned counterpart type */ -#define mhd_SIGNED_TYPE_MAX2(type,utype) ((type)(((utype)(~((utype)0))) >> 1)) +#define mhd_SIGNED_TYPE_MAX2(type,utype) \ + ((type) (((utype) (~((utype) 0))) >> 1)) #define mhd_IS_TYPE_SIGNED(type) (((type) 0) > ((type) - 1)) @@ -168,9 +169,9 @@ #ifndef TIMEVAL_TV_SEC_MAX # ifndef _WIN32 -# define TIMEVAL_TV_SEC_MAX TIME_T_MAX +# define mhd_TIMEVAL_TV_SEC_MAX TIME_T_MAX # else /* _WIN32 */ -# define TIMEVAL_TV_SEC_MAX LONG_MAX +# define mhd_TIMEVAL_TV_SEC_MAX LONG_MAX # endif /* _WIN32 */ #endif /* !TIMEVAL_TV_SEC_MAX */ diff --git a/src/mhd2/mhd_locks.h b/src/mhd2/mhd_locks.h @@ -93,7 +93,7 @@ typedef CRITICAL_SECTION mhd_mutex; * @return nonzero on success, zero otherwise */ # define mhd_mutex_init(pmutex) \ - (InitializeCriticalSection (pmutex), ! 0) + (((void) InitializeCriticalSection (pmutex)), ! 0) # endif #endif diff --git a/src/mhd2/mhd_panic.c b/src/mhd2/mhd_panic.c @@ -19,7 +19,7 @@ */ /** - * @file src/mhd2/mhd_panic.h + * @file src/mhd2/mhd_panic.c * @brief mhd_panic() and MHD_lib_set_panic_func() implementations * @author Karlson2k (Evgeny Grin) */ diff --git a/src/mhd2/mhd_reply.h b/src/mhd2/mhd_reply.h @@ -109,6 +109,13 @@ struct MHD_Reply */ struct MHD_DynamicContentCreatorContext app_act_ctx; +#ifdef MHD_UPGRADE_SUPPORT + /** + * Set to 'true' when "100 Continue" response has been sent + */ + bool sent_100_cntn; +#endif /* MHD_UPGRADE_SUPPORT */ + /** * Response to transmit (initially NULL). */ diff --git a/src/mhd2/mhd_send.c b/src/mhd2/mhd_send.c @@ -812,7 +812,7 @@ mhd_plain_send (struct MHD_Connection *restrict c, * MSG_MORE support) will be used for the next reply so assume * that next sending will be the same, like this call. */ if (push_data && full_buf_sent) - post_send_setopt (c, false, push_data); + post_send_setopt (c, true, push_data); return mhd_SOCKET_ERR_NO_ERROR; } diff --git a/src/mhd2/mhd_upgrade.h b/src/mhd2/mhd_upgrade.h @@ -0,0 +1,53 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_upgrade.h + * @brief The header for declarations of HTTP "upgrade" related data + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_UPGRADE_H +#define MHD_UPGRADE_H 1 + +#include "mhd_sys_options.h" + +#include "mhd_locks.h" + + +struct MHD_Connection; /* forward declaration */ + +/** + * The data for "HTTP-upgraded" connection + */ +struct MHD_UpgradeHandle +{ + /** + * The pointer to the "connection" object + */ + struct MHD_Connection *c; + + /** + * The mutex for some operations + */ + mhd_mutex lock; +}; + +#endif /* ! MHD_UPGRADE_H */ diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c @@ -197,6 +197,24 @@ mhd_stream_maximize_write_buffer (struct MHD_Connection *restrict c) } +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_release_write_buffer (struct MHD_Connection *restrict c) +{ + struct mhd_MemoryPool *const restrict pool = c->pool; + + mhd_assert ((NULL != c->write_buffer) || (0 == c->write_buffer_size)); + mhd_assert (c->write_buffer_append_offset == c->write_buffer_send_offset); + mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset); + + mhd_pool_deallocate (pool, c->write_buffer, c->write_buffer_size); + c->write_buffer_send_offset = 0; + c->write_buffer_append_offset = 0; + c->write_buffer_size = 0; + c->write_buffer = NULL; + +} + + #ifndef MHD_MAX_REASONABLE_HEADERS_SIZE_ /** * A reasonable headers size (excluding request line) that should be sufficient @@ -702,6 +720,10 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, close_hard = true; end_code = MHD_REQUEST_ENDED_NO_RESOURCES; break; + case mhd_CONN_CLOSE_NO_SYS_RESOURCES: + close_hard = true; + end_code = MHD_REQUEST_ENDED_NO_RESOURCES; + break; case mhd_CONN_CLOSE_SOCKET_ERR: close_hard = true; switch (c->sk_discnt_err) @@ -761,6 +783,12 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, MHD_REQUEST_ENDED_NO_RESOURCES : MHD_REQUEST_ENDED_HTTP_PROTOCOL_ERROR; break; +#ifdef MHD_UPGRADE_SUPPORT + case mhd_CONN_CLOSE_UPGRADE: + close_hard = false; + end_code = MHD_REQUEST_ENDED_COMPLETED_OK_UPGRADE; + break; +#endif /* MHD_UPGRADE_SUPPORT */ case mhd_CONN_CLOSE_HTTP_COMPLETED: close_hard = false; end_code = MHD_REQUEST_ENDED_COMPLETED_OK; @@ -775,6 +803,14 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, mhd_assert ((NULL == log_msg) || (MHD_SC_INTERNAL_ERROR != sc)); +#ifdef MHD_UPGRADE_SUPPORT + if (mhd_CONN_CLOSE_UPGRADE == reason) + { + c->state = MHD_CONNECTION_UPGRADING; + c->event_loop_info = MHD_EVENT_LOOP_INFO_UPGRADED; + } + else +#endif /* MHD_UPGRADE_SUPPORT */ /* Make changes on the socket early to let the kernel and the remote * to process the changes in parallel. */ if (close_hard) @@ -839,6 +875,19 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) void +mhd_conn_pre_clean_part1 (struct MHD_Connection *restrict c) +{ + // TODO: support suspended connections + mhd_conn_mark_unready (c, c->daemon); + + mhd_stream_call_dcc_cleanup_if_needed (c); + if (NULL != c->rq.cntn.lbuf.data) + mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) void mhd_conn_pre_clean (struct MHD_Connection *restrict c) { // TODO: support suspended connections @@ -846,17 +895,13 @@ mhd_conn_pre_clean (struct MHD_Connection *restrict c) mhd_assert (c->dbg.closing_started); mhd_assert (! c->dbg.pre_cleaned); - mhd_conn_mark_unready (c, c->daemon); + mhd_conn_pre_clean_part1 (c); - mhd_stream_call_dcc_cleanup_if_needed (c); if (NULL != c->rp.resp_iov.iov) { free (c->rp.resp_iov.iov); c->rp.resp_iov.iov = NULL; } - - if (NULL != c->rq.cntn.lbuf.data) - mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); if (NULL != c->rp.response) mhd_response_dec_use_count (c->rp.response); c->rp.response = NULL; diff --git a/src/mhd2/stream_funcs.h b/src/mhd2/stream_funcs.h @@ -84,6 +84,14 @@ MHD_INTERNAL size_t mhd_stream_maximize_write_buffer (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; +/** + * Fully deallocate write buffer, if it was allocated previously. + * The write buffer must have no unsent data. + * @param c the connection whose write buffer is being manipulated + */ +MHD_INTERNAL void +mhd_stream_release_write_buffer (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; /** * Select the HTTP error status code for "out of receive buffer space" error. @@ -199,6 +207,11 @@ enum mhd_ConnCloseReason mhd_CONN_CLOSE_INT_ERROR , /** + * No system resources available to handle connection + */ + mhd_CONN_CLOSE_NO_SYS_RESOURCES + , + /** * The TCP or TLS connection is broken or aborted due to error on socket * or TLS */ @@ -225,6 +238,18 @@ enum mhd_ConnCloseReason mhd_CONN_CLOSE_ERR_REPLY_SENT , +#ifdef MHD_UPGRADE_SUPPORT + + /* Transition to another protocol */ + /** + * The connection stopped HTTP communication and will be used for another + * protocol. + * The socket is not being closed. + */ + mhd_CONN_CLOSE_UPGRADE + , +#endif /* MHD_UPGRADE_SUPPORT */ + /* Graceful closing */ /** * Close connection after graceful completion of HTTP communication @@ -271,7 +296,7 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * Set the reason to "aborted by application" * @param c the connection for pre-closing */ -#define mhd_conn_pre_close_app_abort(c) \ +#define mhd_conn_start_closing_app_abort(c) \ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_APP_ABORTED, NULL) /** @@ -279,7 +304,7 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * Set the reason to "socket error" * @param c the connection for pre-closing */ -#define mhd_conn_pre_close_skt_err(c) \ +#define mhd_conn_start_closing_skt_err(c) \ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_SOCKET_ERR, NULL) /** @@ -287,7 +312,7 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * Set the reason to "request finished" * @param c the connection for pre-closing */ -#define mhd_conn_pre_close_req_finished(c) \ +#define mhd_conn_start_closing_req_finished(c) \ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_HTTP_COMPLETED, NULL) /** @@ -295,7 +320,7 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * Set the reason to "timed out". * @param c the connection for pre-closing */ -#define mhd_conn_pre_close_timedout(c) \ +#define mhd_conn_start_closing_timedout(c) \ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_TIMEDOUT, NULL) /** @@ -303,15 +328,46 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * Set the reason to "daemon shutdown". * @param c the connection for pre-closing */ -#define mhd_conn_pre_close_d_shutdown(c) \ +#define mhd_conn_start_closing_d_shutdown(c) \ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_DAEMON_SHUTDOWN, NULL) /** + * Perform initial clean-up and mark for closing. + * Set the reason to "no system resources". + * @param c the connection for pre-closing + */ +#define mhd_conn_start_closing_no_sys_res(c) \ + mhd_conn_start_closing ((c), mhd_CONN_CLOSE_NO_SYS_RESOURCES, NULL) + +#ifdef MHD_UPGRADE_SUPPORT +/** + * Perform initial clean-up and prepare for HTTP Upgrade. + * Set the reason to "upgrading". + * @param c the connection for preparing + */ +# define mhd_conn_pre_upgrade(c) \ + mhd_conn_start_closing ((c), mhd_CONN_CLOSE_UPGRADE, NULL) +#endif /* MHD_UPGRADE_SUPPORT */ + + +/** + * Perform first part of the initial connection cleanup. + * This function is used for both standard connection cleanup and for transition + * to HTTP-Upgraded connection. + * This cleanup should be performed in the same thread that processes + * the connection recv/send/data. + * @param c the connection to perform the first part of for pre-cleaning + */ +MHD_INTERNAL void +mhd_conn_pre_clean_part1 (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ (1); + +/** * Perform initial connection cleanup after start of the connection closing * procedure. * This cleanup should be performed in the same thread that processes * the connection recv/send/data. - * @param c the connection for pre-closing + * @param c the connection for pre-cleaning */ MHD_INTERNAL void mhd_conn_pre_clean (struct MHD_Connection *restrict c) diff --git a/src/mhd2/stream_process_reply.c b/src/mhd2/stream_process_reply.c @@ -154,7 +154,7 @@ get_conn_reuse (struct MHD_Connection *c) "keep-alive")) return mhd_CONN_MUST_CLOSE; -#if 0 // def UPGRADE_SUPPORT // TODO: Implement upgrade support +#if 0 // def MHD_UPGRADE_SUPPORT // TODO: Implement upgrade support /* TODO: Move below the next check when MHD stops closing connections * when response is queued in first callback */ if (NULL != r->upgrade_handler) @@ -166,7 +166,7 @@ get_conn_reuse (struct MHD_Connection *c) mhd_assert (! c->stop_with_error); return mhd_CONN_MUST_UPGRADE; } -#endif /* UPGRADE_SUPPORT */ +#endif /* MHD_UPGRADE_SUPPORT */ return mhd_CONN_KEEPALIVE_POSSIBLE; } @@ -198,10 +198,10 @@ is_reply_body_needed (struct MHD_Connection *restrict c, #if 0 /* This check is not needed as upgrade handler is used only with code 101 */ -#ifdef UPGRADE_SUPPORT +#ifdef MHD_UPGRADE_SUPPORT if (NULL != rp.response->upgrade_handler) return RP_BODY_NONE; -#endif /* UPGRADE_SUPPORT */ +#endif /* MHD_UPGRADE_SUPPORT */ #endif #if 0 @@ -254,10 +254,10 @@ setup_reply_properties (struct MHD_Connection *restrict c) c->rp.props.send_reply_body = (use_rp_body > RP_BODY_HEADERS_ONLY); c->rp.props.use_reply_body_headers = (use_rp_body >= RP_BODY_HEADERS_ONLY); -#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support +#if 0 // def MHD_UPGRADE_SUPPORT // TODO: upgrade support mhd_assert ( (NULL == r->upgrade_handler) || (RP_BODY_NONE == use_rp_body) ); -#endif /* UPGRADE_SUPPORT */ +#endif /* MHD_UPGRADE_SUPPORT */ use_chunked = false; end_by_closing = false; @@ -660,21 +660,21 @@ build_header_response_inn (struct MHD_Connection *restrict c) mhd_assert ((mhd_CONN_MUST_CLOSE == c->conn_reuse) || \ (mhd_CONN_KEEPALIVE_POSSIBLE == c->conn_reuse) || \ (mhd_CONN_MUST_UPGRADE == c->conn_reuse)); -#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support +#if 0 // def MHD_UPGRADE_SUPPORT // TODO: upgrade support mhd_assert ((NULL == r->upgrade_handler) || \ (mhd_CONN_MUST_UPGRADE == c->keepalive)); -#else /* ! UPGRADE_SUPPORT */ +#else /* ! MHD_UPGRADE_SUPPORT */ mhd_assert (mhd_CONN_MUST_UPGRADE != c->conn_reuse); -#endif /* ! UPGRADE_SUPPORT */ +#endif /* ! MHD_UPGRADE_SUPPORT */ mhd_assert ((! c->rp.props.chunked) || c->rp.props.use_reply_body_headers); mhd_assert ((! c->rp.props.send_reply_body) || \ c->rp.props.use_reply_body_headers); mhd_assert ((! c->rp.props.end_by_closing) || \ (mhd_CONN_MUST_CLOSE == c->conn_reuse)); -#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support +#if 0 // def MHD_UPGRADE_SUPPORT // TODO: upgrade support mhd_assert (NULL == r->upgrade_handler || \ ! c->rp.props.use_reply_body_headers); -#endif /* UPGRADE_SUPPORT */ +#endif /* MHD_UPGRADE_SUPPORT */ check_connection_reply (c); diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c @@ -2999,8 +2999,14 @@ mhd_stream_call_app_request_cb (struct MHD_Connection *restrict c) case mhd_ACTION_SUSPEND: c->suspended = true; return false; +#ifdef MHD_UPGRADE_SUPPORT + case mhd_ACTION_UPGRADE: + mhd_assert (0 == c->rq.cntn.cntn_size); + c->state = MHD_CONNECTION_UPGRADE_HEADERS_SENDING; + return false; +#endif /* MHD_UPGRADE_SUPPORT */ case mhd_ACTION_ABORT: - mhd_conn_pre_close_app_abort (c); + mhd_conn_start_closing_app_abort (c); return true; case mhd_ACTION_NO_ACTION: default: @@ -3054,8 +3060,16 @@ mhd_stream_process_upload_action (struct MHD_Connection *restrict c, case mhd_UPLOAD_ACTION_SUSPEND: c->suspended = true; return false; +#ifdef MHD_UPGRADE_SUPPORT + case mhd_UPLOAD_ACTION_UPGRADE: + mhd_assert (c->rq.cntn.recv_size == c->rq.cntn.cntn_size); + mhd_assert (! c->rq.have_chunked_upload || \ + MHD_CONNECTION_FULL_REQ_RECEIVED == c->state); + c->state = MHD_CONNECTION_UPGRADE_HEADERS_SENDING; + return false; +#endif /* MHD_UPGRADE_SUPPORT */ case mhd_UPLOAD_ACTION_ABORT: - mhd_conn_pre_close_app_abort (c); + mhd_conn_start_closing_app_abort (c); return true; case mhd_UPLOAD_ACTION_NO_ACTION: default: @@ -3921,9 +3935,12 @@ mhd_stream_check_and_grow_read_buffer_space (struct MHD_Connection *restrict c) case MHD_CONNECTION_FULL_REPLY_SENT: case MHD_CONNECTION_PRE_CLOSING: case MHD_CONNECTION_CLOSED: -#if 0 // def UPGRADE_SUPPORT // TODO: Upgrade support - case MHD_CONNECTION_UPGRADE: -#endif +#ifdef MHD_UPGRADE_SUPPORT + case MHD_CONNECTION_UPGRADE_HEADERS_SENDING: + case MHD_CONNECTION_UPGRADING: + case MHD_CONNECTION_UPGRADED: + case MHD_CONNECTION_UPGRADED_CLEANING: +#endif /* MHD_UPGRADE_SUPPORT */ default: mhd_assert (0); MHD_UNREACHABLE_; diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c @@ -32,6 +32,7 @@ #include "mhd_sys_options.h" #include "sys_bool_type.h" +#include "sys_base_types.h" #include "mhd_str_macros.h" @@ -46,6 +47,10 @@ #include "conn_mark_ready.h" +#ifdef MHD_UPGRADE_SUPPORT +# include "upgrade_proc.h" +#endif /* MHD_UPGRADE_SUPPORT */ + /** * Update current processing state: need to receive, need to send. * Mark stream as ready or not ready for processing. @@ -64,7 +69,7 @@ update_active_state (struct MHD_Connection *restrict c) if (0 != (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY)) { mhd_assert (0 && "Should be handled earlier"); - mhd_conn_pre_close_skt_err (c); + mhd_conn_start_closing_skt_err (c); return false; } @@ -131,6 +136,11 @@ update_active_state (struct MHD_Connection *restrict c) mhd_assert (0 && "Impossible value"); MHD_UNREACHABLE_; break; +#ifdef MHD_UPGRADE_SUPPORT + case MHD_CONNECTION_UPGRADE_HEADERS_SENDING: + c->event_loop_info = MHD_EVENT_LOOP_INFO_SEND; + break; +#endif /* MHD_UPGRADE_SUPPORT */ case MHD_CONNECTION_UNCHUNKED_BODY_UNREADY: mhd_assert (0 && "Should not be possible"); c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; @@ -156,6 +166,20 @@ update_active_state (struct MHD_Connection *restrict c) mhd_assert (0 && "Impossible value"); MHD_UNREACHABLE_; break; +#ifdef MHD_UPGRADE_SUPPORT + case MHD_CONNECTION_UPGRADING: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_UPGRADED: + mhd_assert (0 && "Should not be possible"); + c->event_loop_info = MHD_EVENT_LOOP_INFO_UPGRADED; + break; + case MHD_CONNECTION_UPGRADED_CLEANING: + mhd_assert (0 && "Should be unreachable"); + c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + break; +#endif /* MHD_UPGRADE_SUPPORT */ case MHD_CONNECTION_PRE_CLOSING: mhd_assert (0 && "Should be unreachable"); c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; @@ -164,11 +188,6 @@ update_active_state (struct MHD_Connection *restrict c) mhd_assert (0 && "Should be unreachable"); c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; return false; /* do nothing, not even reading */ -#if 0 // def UPGRADE_SUPPORT // TODO: Upgrade support - case MHD_CONNECTION_UPGRADE: - mhd_assert (0); - break; -#endif /* UPGRADE_SUPPORT */ default: mhd_assert (0 && "Impossible value"); MHD_UNREACHABLE_; @@ -229,6 +248,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) { // TODO: finish resuming, update activity mark // TODO: move to special function + (void) 0; } if ((mhd_SOCKET_ERR_NO_ERROR != c->sk_discnt_err) || @@ -239,7 +259,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) if ((mhd_SOCKET_ERR_NO_ERROR == c->sk_discnt_err) || (mhd_SOCKET_ERR_NOT_CHECKED == c->sk_discnt_err)) c->sk_discnt_err = mhd_socket_error_get_from_socket (c->socket_fd); - mhd_conn_pre_close_skt_err (c); + mhd_conn_start_closing_skt_err (c); return false; } @@ -249,7 +269,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) #endif /* MHD_USE_THREADS */ if (daemon_closing) { - mhd_conn_pre_close_d_shutdown (c); + mhd_conn_start_closing_d_shutdown (c); return false; } @@ -301,6 +321,9 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) if (c->continue_message_write_offset == mhd_SSTR_LEN (mdh_HTTP_1_1_100_CONTINUE_REPLY)) { +#ifdef MHD_UPGRADE_SUPPORT + c->rp.sent_100_cntn = true; +#endif /* MHD_UPGRADE_SUPPORT */ c->state = MHD_CONNECTION_BODY_RECEIVING; continue; } @@ -365,33 +388,6 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) /* no default action, wait for sending all the headers */ break; case MHD_CONNECTION_HEADERS_SENT: -#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support - if (NULL != c->rp.response->upgrade_handler) - { - mhd_assert (0 && "Not implemented yet"); - c->state = MHD_CONNECTION_UPGRADE; - /* This connection is "upgraded". Pass socket to application. */ - if (MHD_NO == - MHD_response_execute_upgrade_ (c->rp.response, - connection)) - { - /* upgrade failed, fail hard */ - CONNECTION_CLOSE_ERROR (connection, - NULL); - continue; - } - /* Response is not required anymore for this connection. */ - if (1) - { - struct MHD_Response *const resp = c->rp.response; - - c->rp.response = NULL; - MHD_destroy_response (resp); - } - continue; - } -#endif /* UPGRADE_SUPPORT */ - if (c->rp.props.send_reply_body) { if (c->rp.props.chunked) @@ -402,6 +398,12 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) else c->state = MHD_CONNECTION_FULL_REPLY_SENT; continue; +#ifdef MHD_UPGRADE_SUPPORT + case MHD_CONNECTION_UPGRADE_HEADERS_SENDING: + if (! mhd_upgrade_try_start_upgrading (c)) + break; + continue; +#endif /* MHD_UPGRADE_SUPPORT */ case MHD_CONNECTION_UNCHUNKED_BODY_READY: mhd_assert (c->rp.props.send_reply_body); mhd_assert (! c->rp.props.chunked); @@ -459,12 +461,23 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) && ! c->discard_request && ! c->sk_rmt_shut_wr); continue; +#ifdef MHD_UPGRADE_SUPPORT + case MHD_CONNECTION_UPGRADING: + if (mhd_upgrade_finish_switch_to_upgraded (c)) + return true; /* Do not close connection */ + mhd_assert (MHD_CONNECTION_PRE_CLOSING == c->state); + continue; + case MHD_CONNECTION_UPGRADED: + mhd_assert (0 && "Should be unreachable"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_UPGRADED_CLEANING: + mhd_assert (0 && "Should be unreachable"); + MHD_UNREACHABLE_; + break; +#endif /* MHD_UPGRADE_SUPPORT */ case MHD_CONNECTION_PRE_CLOSING: return false; -#if 0 // def UPGRADE_SUPPORT - case MHD_CONNECTION_UPGRADE: - return MHD_YES; /* keep open */ -#endif /* UPGRADE_SUPPORT */ case MHD_CONNECTION_CLOSED: mhd_assert (0 && "Should be unreachable"); MHD_UNREACHABLE_; @@ -482,6 +495,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) if (MHD_CONNECTION_PRE_CLOSING == c->state) { mhd_assert (0 && "Pre-closing should be already caught in the loop"); + MHD_UNREACHABLE_; return false; } @@ -504,7 +518,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) if (mhd_stream_is_timeout_expired (c)) // TODO: centralise timeout checks { - mhd_conn_pre_close_timedout (c); + mhd_conn_start_closing_timedout (c); return false; } diff --git a/src/mhd2/sys_offsetof.h b/src/mhd2/sys_offsetof.h @@ -0,0 +1,43 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_offsetof.h + * @brief The definition of system's 'offsetof' macro or suitable replacement. + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SYS_OFFSETOF_H +#define MHD_SYS_OFFSETOF_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#if defined(HAVE_STDDEF_H) +# include <stddef.h> /* it should be already included, actually */ +#endif /* HAVE_STDDEF_H */ + +#ifndef offsetof +# define offsetof(strct, membr) \ + ((size_t) (((char*) &(((strct*) 0)->membr)) - ((char*) ((strct*) 0)))) +#endif /* ! offsetof */ + +#endif /* ! MHD_SYS_OFFSETOF_H */ diff --git a/src/mhd2/upgrade_net.c b/src/mhd2/upgrade_net.c @@ -0,0 +1,524 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/upgrade_net.c + * @brief The implementation of functions for network data exchange + * for HTTP Upgraded connections + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "sys_poll.h" +#ifndef MHD_USE_POLL +# include "sys_select.h" +#endif +#include "mhd_limits.h" + +#include "mhd_sockets_macros.h" + +#include "mhd_upgrade.h" +#include "mhd_connection.h" +#include "mhd_locks.h" + +#include "mhd_recv.h" +#include "mhd_send.h" +#include "mhd_mono_clock.h" + +#include "mhd_public_api.h" + + +#if ! defined (MHD_USE_POLL) && \ + (defined(MHD_POSIX_SOCKETS) || ! defined(MHD_USE_SELECT)) +# if defined(_WIN32) || defined(HAVE_NANOSLEEP) || defined(HAVE_USLEEP) +# define mhd_HAVE_MHD_SLEEP 1 + +/** + * Pause execution for specified number of milliseconds. + * @param ms the number of milliseconds to sleep + */ +static void +mhd_sleep (uint_fast32_t millisec) +{ +#if defined(_WIN32) + Sleep (millisec); +#elif defined(HAVE_NANOSLEEP) + struct timespec slp = {millisec / 1000, (millisec % 1000) * 1000000}; + struct timespec rmn; + int num_retries = 0; + while (0 != nanosleep (&slp, &rmn)) + { + if (EINTR != errno) + externalErrorExit (); + if (num_retries++ > 8) + break; + slp = rmn; + } +#elif defined(HAVE_USLEEP) + uint64_t us = millisec * 1000; + do + { + uint64_t this_sleep; + if (999999 < us) + this_sleep = 999999; + else + this_sleep = us; + /* Ignore return value as it could be void */ + usleep (this_sleep); + us -= this_sleep; + } while (us > 0); +#endif +} + + +#endif /* _WIN32 || HAVE_NANOSLEEP || HAVE_USLEEP */ +#endif /* ! MHD_USE_POLL) && (MHD_POSIX_SOCKETS || ! MHD_USE_SELECT) */ + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (4) enum MHD_StatusCode +MHD_upgraded_recv (struct MHD_UpgradeHandle *MHD_RESTRICT urh, + size_t recv_buf_size, + void *MHD_RESTRICT recv_buf, + size_t *MHD_RESTRICT received_size, + uint_fast64_t max_wait_millisec) +{ + struct MHD_Connection *restrict c = urh->c; +#if defined(MHD_USE_POLL) || defined(MHD_USE_SELECT) + const MHD_Socket socket_fd = c->socket_fd; +#endif /* MHD_USE_POLL || MHD_USE_SELECT */ + char *restrict buf_char = (char *) recv_buf; + size_t last_block_size; + enum mhd_SocketError res; + + *received_size = 0; + + if (&(c->upgr) != urh) + return MHD_SC_UPGRADED_HANDLE_INVALID; + if (MHD_CONNECTION_UPGRADED != c->state) + return MHD_SC_UPGRADED_HANDLE_INVALID; + + if (0 == recv_buf_size) + return MHD_SC_OK; + + if (NULL != c->read_buffer) + { + mhd_mutex_lock_chk (&(urh->lock)); + if (0 != c->read_buffer_offset) /* Re-check under the lock */ + { + if (recv_buf_size < c->read_buffer_offset) + { + memcpy (buf_char, c->read_buffer, recv_buf_size); + last_block_size = recv_buf_size; + c->read_buffer += recv_buf_size; + c->read_buffer_offset -= recv_buf_size; + c->read_buffer_size -= recv_buf_size; + } + else + { + /* recv_buf_size >= c->read_buffer_offset */ + memcpy (buf_char, c->read_buffer, c->read_buffer_offset); + last_block_size = c->read_buffer_offset; + c->read_buffer_offset = 0; + c->read_buffer_size = 0; + /* Do not deallocate the read buffer to save the time under the lock. + The connection memory pool will not be used anyway. */ + c->read_buffer = NULL; + } + } + mhd_mutex_unlock_chk (&(urh->lock)); + *received_size = last_block_size; + if (recv_buf_size == last_block_size) + return MHD_SC_OK; + } + + last_block_size = 0; + res = mhd_recv (c, + recv_buf_size - *received_size, + buf_char + *received_size, + &last_block_size); + if (mhd_SOCKET_ERR_NO_ERROR == res) + { + if (0 == last_block_size) + c->sk_rmt_shut_wr = true; + *received_size += last_block_size; + return MHD_SC_OK; + } + else if (0 != *received_size) + return MHD_SC_OK; + + if (! mhd_SOCKET_ERR_IS_HARD (res)) + { + while (0 != max_wait_millisec) + { +#if defined(MHD_USE_POLL) + if (1) + { + struct pollfd fds[1]; + int poll_wait; + int poll_res; + int wait_err; + + if (MHD_WAIT_INDEFINITELY <= max_wait_millisec) + poll_wait = -1; + else + { + poll_wait = (int) max_wait_millisec; + if ((max_wait_millisec != (uint_fast64_t) poll_wait) || + (0 > poll_wait)) + poll_wait = INT_MAX; + } + fds[0].fd = socket_fd; + fds[0].events = POLLIN; + + poll_res = mhd_poll (fds, + 1, + poll_wait); + if ((0 >= poll_res) && + (0 != *received_size)) + return MHD_SC_OK; + if (0 == poll_res) + return MHD_SC_UPGRADED_NET_TIMEOUT; + + wait_err = mhd_SCKT_GET_LERR (); + if (! mhd_SCKT_ERR_IS_EAGAIN (wait_err) && + ! mhd_SCKT_ERR_IS_EINTR (wait_err) && + ! mhd_SCKT_ERR_IS_LOW_RESOURCES (wait_err)) + return MHD_SC_UPGRADED_NET_HARD_ERROR; + max_wait_millisec = 0; /* Re-try only one time */ + } +#else /* ! MHD_USE_POLL */ + bool use_select; +# if defined(MHD_USE_SELECT) +# ifdef MHD_POSIX_SOCKETS + use_select = socket_fd < FD_SETSIZE; +# else /* MHD_WINSOCK_SOCKETS */ + use_select = true; +# endif /* MHD_WINSOCK_SOCKETS */ + if (use_select) + { + fd_set rfds; + int sel_res; + int wait_err; + struct timeval tmvl; + +# ifdef MHD_POSIX_SOCKETS + tmvl.tv_sec = (long) (max_wait_millisec / 1000); +# else /* MHD_WINSOCK_SOCKETS */ + tmvl.tv_sec = (long) (max_wait_millisec / 1000); +# endif /* MHD_WINSOCK_SOCKETS */ + if ((max_wait_millisec / 1000 != (uint_fast64_t) tmvl.tv_sec) || + (0 > tmvl.tv_sec)) + { + tmvl.tv_sec = mhd_TIMEVAL_TV_SEC_MAX; + tmvl.tv_usec = 0; + } + else + tmvl.tv_usec = (int) (max_wait_millisec % 1000); + FD_ZERO (&rfds); + FD_SET (socket_fd, &rfds); + + sel_res = select (c->socket_fd + 1, + &rfds, + NULL, + NULL, + (MHD_WAIT_INDEFINITELY <= max_wait_millisec) ? + NULL : &tmvl); + + if ((0 >= sel_res) && + (0 != *received_size)) + return MHD_SC_OK; + if (0 == sel_res) + return MHD_SC_UPGRADED_NET_TIMEOUT; + + wait_err = mhd_SCKT_GET_LERR (); + if (! mhd_SCKT_ERR_IS_EAGAIN (wait_err) && + ! mhd_SCKT_ERR_IS_EINTR (wait_err) && + ! mhd_SCKT_ERR_IS_LOW_RESOURCES (wait_err)) + return MHD_SC_UPGRADED_NET_HARD_ERROR; + max_wait_millisec = 0; /* Re-try only one time */ + } +# else /* ! MHD_USE_SELECT */ + use_select = false; +# endif /* ! MHD_USE_SELECT */ +# if ! defined(MHD_WINSOCK_SOCKETS) || ! defined(MHD_USE_SELECT) + if (! use_select) + { +# ifndef mhd_HAVE_MHD_SLEEP + return MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED; +# else /* mhd_HAVE_MHD_SLEEP */ + mhd_sleep (100); + if (MHD_WAIT_INDEFINITELY > max_wait_millisec) + { + if (100 > max_wait_millisec) + max_wait_millisec = 0; + else + max_wait_millisec -= 100; + } +# endif /* mhd_HAVE_MHD_SLEEP */ + } +# endif /* ! MHD_WINSOCK_SOCKETS) || ! MHD_USE_SELECT */ +#endif /* ! MHD_USE_POLL */ + last_block_size = 0; + res = mhd_recv (c, + recv_buf_size - *received_size, + buf_char + *received_size, + &last_block_size); + if (mhd_SOCKET_ERR_NO_ERROR == res) + { + if (0 == last_block_size) + c->sk_rmt_shut_wr = true; + *received_size += last_block_size; + return MHD_SC_OK; + } + } + } + if (! mhd_SOCKET_ERR_IS_HARD (res)) + return MHD_SC_UPGRADED_NET_TIMEOUT; + if (mhd_SOCKET_ERR_REMT_DISCONN == res) + return MHD_SC_UPGRADED_NET_CONN_CLOSED; + if (mhd_SOCKET_ERR_TLS == res) + return MHD_SC_UPGRADED_TLS_ERROR; + if (! mhd_SOCKET_ERR_IS_BAD (res)) + return MHD_SC_UPGRADED_NET_CONN_BROKEN; + + return MHD_SC_UPGRADED_NET_HARD_ERROR; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (4) enum MHD_StatusCode +MHD_upgraded_send (struct MHD_UpgradeHandle *MHD_RESTRICT urh, + size_t send_buf_size, + const void *MHD_RESTRICT send_buf, + size_t *MHD_RESTRICT sent_size, + uint_fast64_t max_wait_millisec, + enum MHD_Bool more_data_to_come) +{ + struct MHD_Connection *restrict c = urh->c; +#if defined(MHD_USE_POLL) || defined(MHD_USE_SELECT) + const MHD_Socket socket_fd = c->socket_fd; +#endif /* MHD_USE_POLL || MHD_USE_SELECT */ + const char *restrict buf_char = (const char *) send_buf; + const bool push_data = (MHD_NO == more_data_to_come); + bool finish_time_set; + bool wait_indefinitely; + uint_fast64_t finish_time = 0; + + *sent_size = 0; + + if (&(c->upgr) != urh) + return MHD_SC_UPGRADED_HANDLE_INVALID; + if (MHD_CONNECTION_UPGRADED != c->state) + return MHD_SC_UPGRADED_HANDLE_INVALID; + + finish_time_set = false; + wait_indefinitely = (MHD_WAIT_INDEFINITELY <= max_wait_millisec); + + while (0 == send_buf_size) + { + enum mhd_SocketError res; + size_t last_block_size; + uint_fast64_t wait_left; +#if ! defined(MHD_USE_POLL) + bool use_select; +#endif /* ! MHD_USE_POLL */ + + last_block_size = 0; + res = mhd_send_data (c, + send_buf_size - *sent_size, + buf_char + *sent_size, + push_data, + &last_block_size); + if (mhd_SOCKET_ERR_NO_ERROR == res) + *sent_size += last_block_size; + else if (mhd_SOCKET_ERR_IS_HARD (res)) + { + if (0 != *sent_size) + return MHD_SC_OK; + + if (mhd_SOCKET_ERR_REMT_DISCONN == res) + return MHD_SC_UPGRADED_NET_CONN_CLOSED; + if (mhd_SOCKET_ERR_TLS == res) + return MHD_SC_UPGRADED_TLS_ERROR; + if (! mhd_SOCKET_ERR_IS_BAD (res)) + return MHD_SC_UPGRADED_NET_CONN_BROKEN; + + return MHD_SC_UPGRADED_NET_HARD_ERROR; + } + + if (0 == max_wait_millisec) + { + if (0 != *sent_size) + return MHD_SC_OK; + + return MHD_SC_UPGRADED_NET_TIMEOUT; + } + + if (! wait_indefinitely) + { + uint_fast64_t cur_time; + cur_time = MHD_monotonic_msec_counter (); + + if (! finish_time_set) + { + finish_time = cur_time + max_wait_millisec; + wait_left = max_wait_millisec; + } + else + { + wait_left = finish_time - cur_time; + if (wait_left > cur_time - finish_time) + return MHD_SC_UPGRADED_NET_TIMEOUT; + } + } + +#if defined(MHD_USE_POLL) + if (1) + { + struct pollfd fds[1]; + int poll_wait; + int poll_res; + int wait_err; + + if (wait_indefinitely) + poll_wait = -1; + else + { + poll_wait = (int) wait_left; + if ((wait_left != (uint_fast64_t) poll_wait) || + (0 > poll_wait)) + poll_wait = INT_MAX; + } + fds[0].fd = socket_fd; + fds[0].events = POLLOUT; + + poll_res = mhd_poll (fds, + 1, + poll_wait); + if (0 < poll_res) + continue; + if (0 == poll_res) + { + if (wait_indefinitely || + (INT_MAX == poll_wait)) + continue; + if (0 != *sent_size) + return MHD_SC_OK; + return MHD_SC_UPGRADED_NET_TIMEOUT; + } + + wait_err = mhd_SCKT_GET_LERR (); + if (! mhd_SCKT_ERR_IS_EAGAIN (wait_err) && + ! mhd_SCKT_ERR_IS_EINTR (wait_err) && + ! mhd_SCKT_ERR_IS_LOW_RESOURCES (wait_err)) + return MHD_SC_UPGRADED_NET_HARD_ERROR; + } +#else /* ! MHD_USE_POLL */ +# if defined(MHD_USE_SELECT) +# ifdef MHD_POSIX_SOCKETS + use_select = socket_fd < FD_SETSIZE; +# else /* MHD_WINSOCK_SOCKETS */ + use_select = true; +# endif /* MHD_WINSOCK_SOCKETS */ + if (use_select) + { + fd_set wfds; + int sel_res; + int wait_err; + struct timeval tmvl; + bool max_wait; + + max_wait = false; + if (wait_indefinitely) + { + tmvl.tv_sec = 0; + tmvl.tv_usec = 0; + } + else + { +# ifdef MHD_POSIX_SOCKETS + tmvl.tv_sec = (long) (max_wait_millisec / 1000); +# else /* MHD_WINSOCK_SOCKETS */ + tmvl.tv_sec = (long) (max_wait_millisec / 1000); +# endif /* MHD_WINSOCK_SOCKETS */ + if ((max_wait_millisec / 1000 != (uint_fast64_t) tmvl.tv_sec) || + (0 > tmvl.tv_sec)) + { + tmvl.tv_sec = mhd_TIMEVAL_TV_SEC_MAX; + tmvl.tv_usec = 0; + max_wait = true; + } + else + tmvl.tv_usec = (int) (max_wait_millisec % 1000); + } + FD_ZERO (&wfds); + FD_SET (socket_fd, &wfds); + + sel_res = select (c->socket_fd + 1, + NULL, + &wfds, + NULL, + wait_indefinitely ? NULL : &tmvl); + + if (0 < sel_res) + continue; + if (0 == sel_res) + { + if (wait_indefinitely || + max_wait) + continue; + if (0 != *sent_size) + return MHD_SC_OK; + return MHD_SC_UPGRADED_NET_TIMEOUT; + } + + wait_err = mhd_SCKT_GET_LERR (); + if (! mhd_SCKT_ERR_IS_EAGAIN (wait_err) && + ! mhd_SCKT_ERR_IS_EINTR (wait_err) && + ! mhd_SCKT_ERR_IS_LOW_RESOURCES (wait_err)) + return MHD_SC_UPGRADED_NET_HARD_ERROR; + } +# else /* ! MHD_USE_SELECT */ + use_select = false; +# endif /* ! MHD_USE_SELECT */ +# if ! defined(MHD_WINSOCK_SOCKETS) || ! defined(MHD_USE_SELECT) + if (! use_select) + { +# ifndef mhd_HAVE_MHD_SLEEP + return MHD_SC_UPGRADED_WAITING_NOT_SUPPORTED; +# else /* mhd_HAVE_MHD_SLEEP */ + mhd_sleep (100); +# endif /* mhd_HAVE_MHD_SLEEP */ + } +# endif /* ! MHD_WINSOCK_SOCKETS) || ! MHD_USE_SELECT */ +#endif /* ! MHD_USE_POLL */ + } + + return MHD_SC_OK; +} diff --git a/src/mhd2/upgrade_prep.c b/src/mhd2/upgrade_prep.c @@ -0,0 +1,447 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/upgrade_prep.c + * @brief The implementation of functions for preparing for MHD Action for + * HTTP-Upgrade + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include <string.h> + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "upgrade_prep.h" + +#include "mhd_cntnr_ptr.h" + +#include "mhd_str_types.h" +#include "mhd_str_macros.h" + +#include "mhd_assert.h" +#include "mhd_str.h" + +#include "daemon_logger.h" + +#include "mhd_request.h" +#include "mhd_connection.h" + +#include "mhd_upgrade.h" +#include "stream_funcs.h" + +#include "mhd_public_api.h" + +/** + * Check whether the provided data fits the buffer and append provided data + * to the buffer + * @param buf_size the size of the @a buf buffer + * @param buf the buffer to use + * @param[in,out] pbuf_used the pointer to the variable with current offset in + * the @a buf buffer, updated if @a copy_data is added + * @param copy_size the size of the @a copy_data + * @param copy_data the data to append to the buffer + * @return 'true' if @a copy_data has been appended to the @a buf buffer, + * 'false' if @a buf buffer has not enough space + */ +MHD_static_inline_ +MHD_FN_PAR_OUT_SIZE_ (2,1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_INOUT_ (3) +MHD_FN_PAR_IN_SIZE_ (5,4) bool +buf_append (size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict pbuf_used, + size_t copy_size, + const char copy_data[MHD_FN_PAR_DYN_ARR_SIZE_ (copy_size)]) +{ + if ((*pbuf_used + copy_size > buf_size) || + (((size_t) (*pbuf_used + copy_size)) < copy_size)) + return false; + + memcpy (buf + *pbuf_used, copy_data, copy_size); + *pbuf_used += copy_size; + + return true; +} + + +/** + * The build_reply_header() results + */ +enum mhd_UpgradeHeaderBuildRes +{ + /** + * Success + */ + MHD_UPGRADE_HDR_BUILD_OK = 0 + , + /** + * Not enough buffer size (not logged) + */ + MHD_UPGRADE_HDR_BUILD_NO_MEM + , + /** + * Some other error (already logged) + */ + MHD_UPGRADE_HDR_BUILD_OTHER_ERR +}; + +/** + * Build full reply header for the upgrade action. + * The reply header serves as a preamble, as soon as it sent the connection + * switched to the "upgraded" mode. + * @param c the connection to use + * @param buf_size the size of the @a buf buffer + * @param[out] buf the buffer to build the reply + * @param[out] pbuf_used the pointer to the variable receiving the size of + * the reply header in the @a buf buffer + * @param upgrade_hdr_value the value of the "Upgrade:" reply header + * @param num_headers the number of elements in the @a headers array, + * must be zero if @a headers is NULL + * @param headers the array of string pairs used as reply headers, + * can be NULL + * @return #MHD_UPGRADE_HDR_BUILD_OK if reply has been built successfully and + * @a pbuf_used has been updated, + * #MHD_UPGRADE_HDR_BUILD_NO_MEM if @a buf has not enough space to build + * the reply header (the error is not yet logged), + * #MHD_UPGRADE_HDR_BUILD_NO_MEM if any other error occurs (the error + * has been logged) + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3) +MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_CSTR_ (5) +MHD_FN_PAR_IN_SIZE_ (7,6) enum mhd_UpgradeHeaderBuildRes +build_reply_header (struct MHD_Connection *restrict c, + const size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *pbuf_used, + const char *restrict upgrade_hdr_value, + size_t num_headers, + const struct MHD_NameValueCStr *restrict headers) +{ + static const struct MHD_String rp_100_cntn_msg = + mhd_MSTR_INIT (mdh_HTTP_1_1_100_CONTINUE_REPLY); + static const struct MHD_String status_line = + mhd_MSTR_INIT (MHD_HTTP_VERSION_1_1_STR " 101 Switching Protocols\r\n"); + static const struct MHD_String upgrade_hdr_start = + mhd_MSTR_INIT (MHD_HTTP_HEADER_UPGRADE ": "); + size_t upgrade_hdr_value_len; + size_t buf_used; + size_t i; + bool has_conn_hdr; + bool hdr_name_invalid; + + mhd_assert (MHD_HTTP_VERSION_1_1 == c->rq.http_ver); + mhd_assert ((0 == c->rq.cntn.cntn_size) || \ + (MHD_CONNECTION_FULL_REQ_RECEIVED == c->state)); + + buf_used = 0; + + if (c->rq.have_expect_100 && ! c->rp.sent_100_cntn) + { + /* Must send "100 Continue" before switching to data pumping */ + if (! buf_append (buf_size, + buf, + &buf_used, + rp_100_cntn_msg.len, + rp_100_cntn_msg.cstr)) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + } + + /* Status line */ + if (! buf_append (buf_size, + buf, + &buf_used, + status_line.len, + status_line.cstr)) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + + /* "Upgrade:" header */ + if (! buf_append (buf_size, + buf, + &buf_used, + upgrade_hdr_start.len, + upgrade_hdr_start.cstr)) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + + upgrade_hdr_value_len = strcspn (upgrade_hdr_value, + "\n\r"); + if ((0 == upgrade_hdr_value_len) || + (0 != upgrade_hdr_value[upgrade_hdr_value_len])) + { + mhd_LOG_MSG (c->daemon, \ + MHD_SC_RESP_HEADER_VALUE_INVALID, \ + "The provided value of the \"Upgrade:\" header " \ + "is invalid."); + return MHD_UPGRADE_HDR_BUILD_OTHER_ERR; + } + if ((buf_used + upgrade_hdr_value_len + 2 > buf_size) || + (((size_t) (buf_used + upgrade_hdr_value_len + 2)) < buf_used)) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + memcpy (buf + buf_used, + upgrade_hdr_value, + upgrade_hdr_value_len); + buf_used += upgrade_hdr_value_len; + buf[buf_used++] = '\r'; + buf[buf_used++] = '\n'; + + /* User headers */ + has_conn_hdr = false; + hdr_name_invalid = false; + for (i = 0; i < num_headers; ++i) + { + static const struct MHD_String conn_hdr_prefix = + mhd_MSTR_INIT ("upgrade, "); + size_t hdr_name_len; + size_t hdr_value_len; + size_t line_len; + bool is_conn_hdr; + + if (NULL == headers[i].name) + { + hdr_name_invalid = true; + break; + } + + hdr_name_len = strcspn (headers[i].name, + "\n\r \t:,;\""); + + if ((0 == hdr_name_len) || + (0 != headers[i].name[hdr_name_len])) + { + hdr_name_invalid = true; + break; + } + + if (NULL == headers[i].value) + break; + + hdr_value_len = strcspn (headers[i].value, + "\n\r"); + + if (0 != headers[i].value[hdr_value_len]) + break; + + if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_UPGRADE, \ + headers[i].name, \ + hdr_name_len)) + break; + + line_len = hdr_name_len + 2 + hdr_value_len + 2; + + is_conn_hdr = + mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_CONNECTION, \ + headers[i].name, \ + hdr_name_len); + if (is_conn_hdr) + { + if (0 == hdr_value_len) + continue; /* Skip the header, proper "Connection:" header will be added below */ + if (has_conn_hdr) + break; /* Two "Connection:" headers */ + has_conn_hdr = true; + + if (mhd_str_has_s_token_caseless (headers[i].value, "close")) + break; + if (mhd_str_has_s_token_caseless (headers[i].value, "keep-alive")) + break; + + line_len += conn_hdr_prefix.len; + if (line_len < conn_hdr_prefix.len) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + } + + if ((buf_used + line_len > buf_size) || + (((size_t) (buf_used + line_len)) < line_len) || + (line_len < hdr_value_len)) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + + memcpy (buf + buf_used, + headers[i].name, + hdr_name_len); + buf_used += hdr_name_len; + buf[buf_used++] = ':'; + buf[buf_used++] = ' '; + + if (is_conn_hdr) + { + memcpy (buf + buf_used, + conn_hdr_prefix.cstr, + conn_hdr_prefix.len); + buf_used += conn_hdr_prefix.len; + } + + memcpy (buf + buf_used, + headers[i].name, + hdr_name_len); + buf[buf_used++] = '\r'; + buf[buf_used++] = '\n'; + } + mhd_assert (buf_size >= buf_used); + mhd_assert (! hdr_name_invalid || (i < num_headers)); + + if (i < num_headers) + { + if (hdr_name_invalid) + mhd_LOG_PRINT (c->daemon, \ + MHD_SC_RESP_HEADER_NAME_INVALID, \ + mhd_LOG_FMT ("The name of the provided header " \ + "number %lu is invalid. " \ + "Header name: '%s'. " \ + "Header Value: '%s'."), + (unsigned long) i, + headers[i].name ? headers[i].name : "(NULL)", + headers[i].value ? headers[i].value : "(NULL)"); + else + mhd_LOG_PRINT (c->daemon, \ + MHD_SC_RESP_HEADER_VALUE_INVALID, \ + mhd_LOG_FMT ("The value of the provided header " \ + "number %lu is invalid. " \ + "Header name: '%s'. " \ + "Header Value: '%s'."), + (unsigned long) i, + headers[i].name ? headers[i].name : "(NULL)", + headers[i].value ? headers[i].value : "(NULL)"); + + return MHD_UPGRADE_HDR_BUILD_OTHER_ERR; + } + + /* "Connection:" header (if has not been added already) */ + if (! has_conn_hdr) + { + static const struct MHD_String conn_hdr_line = + mhd_MSTR_INIT (MHD_HTTP_HEADER_CONNECTION ": upgrade\r\n"); + + if (! buf_append (buf_size, + buf, + &buf_used, + conn_hdr_line.len, + conn_hdr_line.cstr)) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + } + + /* End of reply header */ + if ((buf_used + 2 > buf_size) || + (((size_t) (buf_used + 2)) < 2)) + return MHD_UPGRADE_HDR_BUILD_NO_MEM; + + buf[buf_used++] = '\r'; + buf[buf_used++] = '\n'; + + mhd_assert (buf_size >= buf_used); + *pbuf_used = buf_used; + + return MHD_UPGRADE_HDR_BUILD_OK; +} + + +/** + * Prepare connection to be used with the HTTP "Upgrade" action + * @param c the connection object + * @param upgrade_hdr_value the value of the "Upgrade:" header, mandatory + * string + * @param num_headers number of elements in the @a headers array, + * must be zero if @a headers is NULL + * @param headers the optional pointer to the array of the headers (the strings + * are copied and does not need to be valid after return from + * this function), + * can be NULL if @a num_headers is zero + * @return 'true' if succeed, + * 'false' otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (4,3) bool +connection_prepare_for_upgrade ( + struct MHD_Connection *restrict c, + const char *restrict upgrade_hdr_value, + size_t num_headers, + const struct MHD_NameValueCStr *restrict headers) +{ + enum mhd_UpgradeHeaderBuildRes res; + + mhd_assert (NULL == c->write_buffer); + mhd_assert (0 == c->write_buffer_size); + mhd_assert (0 == c->write_buffer_send_offset); + + mhd_stream_shrink_read_buffer (c); + mhd_stream_maximize_write_buffer (c); + mhd_assert (0 == c->write_buffer_append_offset); + + res = build_reply_header (c, + c->write_buffer_size, + c->write_buffer, + &c->write_buffer_append_offset, + upgrade_hdr_value, + num_headers, + headers); + if (MHD_UPGRADE_HDR_BUILD_OK == res) + return true; /* Success exit point */ + + /* Header build failed */ + if (MHD_UPGRADE_HDR_BUILD_NO_MEM == res) + mhd_LOG_MSG (c->daemon, \ + MHD_SC_REPLY_HEADERS_TOO_LARGE, \ + "No space in the connection memory pool to create complete " \ + "HTTP \"Upgrade\" response header."); + + mhd_stream_release_write_buffer (c); + + return false; +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (4,3) bool +mhd_upgrade_prep_for_action (struct MHD_Request *restrict req, + const char *restrict upgrade_hdr_value, + size_t num_headers, + const struct MHD_NameValueCStr *restrict headers, + bool is_upload_act) +{ + struct MHD_Connection *const c = + mhd_cntnr_ptr (req, struct MHD_Connection, rq); + + mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= c->state); + mhd_assert (MHD_CONNECTION_FULL_REQ_RECEIVED >= c->state); + + if (req->have_chunked_upload && + (MHD_CONNECTION_FOOTERS_RECEIVED >= c->state)) + return false; /* The request has not been fully received */ + + if (! is_upload_act) + { + if (MHD_CONNECTION_HEADERS_PROCESSED != c->state) + return false; + } + else + { + if (MHD_CONNECTION_BODY_RECEIVING > c->state) + return false; + } + + return connection_prepare_for_upgrade (c, + upgrade_hdr_value, + num_headers, + headers); +} diff --git a/src/mhd2/upgrade_prep.h b/src/mhd2/upgrade_prep.h @@ -0,0 +1,67 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/upgrade_prep.h + * @brief The declaration of functions for preparing for MHD Action for + * HTTP-Upgrade + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_UPGRADE_PREP_H +#define MHD_UPGRADE_PREP_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +struct MHD_Request; /* Forward declaration */ +struct MHD_NameValueCStr; /* Forward declaration */ + +/** + * Prepare connection for the HTTP "Upgrade" action. + * + * Unlike other actions, this kind of action manipulates connection's output + * buffers. + * + * @param req the request object + * @param upgrade_hdr_value the value of the "Upgrade:" header, mandatory + string + * @param num_headers number of elements in the @a headers array, + * must be zero if @a headers is NULL + * @param headers the optional pointer to the array of the headers (the strings + * are copied and does not need to be valid after return from + * this function), + * can be NULL if @a num_headers is zero + * @param is_upload_act must be set to 'true' if the action being created is + * "upload" kind of action + * @return + */ +MHD_INTERNAL bool +mhd_upgrade_prep_for_action (struct MHD_Request *restrict req, + const char *restrict upgrade_hdr_value, + size_t num_headers, + const struct MHD_NameValueCStr *restrict headers, + bool is_upload_act) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_IN_SIZE_ (4,3); + +#endif /* ! MHD_UPGRADE_PREP_H */ diff --git a/src/mhd2/upgrade_proc.c b/src/mhd2/upgrade_proc.c @@ -0,0 +1,146 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/upgrade_proc.c + * @brief The implementation of functions for processing data for HTTP Upgrade + * @author Karlson2k (Evgeny Grin) + */ + + +#include "mhd_sys_options.h" + +#include "upgrade_proc.h" + +#include "sys_base_types.h" + +#include "mhd_locks.h" + +#include "mhd_action.h" +#include "mhd_connection.h" +#include "mhd_daemon.h" +#include "mhd_upgrade.h" + +#include "daemon_funcs.h" +#include "stream_funcs.h" +#include "daemon_logger.h" + +#include "mhd_public_api.h" + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) bool +mhd_upgrade_try_start_upgrading (struct MHD_Connection *restrict c) +{ + mhd_assert (MHD_CONNECTION_UPGRADE_HEADERS_SENDING == c->state); + mhd_assert ((mhd_ACTION_UPGRADE == c->rq.app_act.head_act.act) || + (mhd_UPLOAD_ACTION_UPGRADE == c->rq.app_act.upl_act.act)); + mhd_assert (NULL != c->write_buffer); + mhd_assert ((0 != c->read_buffer_offset) || (NULL == c->read_buffer)); + mhd_assert (NULL == c->upgr.c); + + if (c->write_buffer_append_offset != c->write_buffer_send_offset) + return false; + + c->state = MHD_CONNECTION_UPGRADING; + + return true; +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) bool +mhd_upgrade_finish_switch_to_upgraded (struct MHD_Connection *restrict c) +{ + struct mhd_UpgradeActionData *pupgr_data; + mhd_assert (MHD_CONNECTION_UPGRADING == c->state); + mhd_assert (NULL != c->write_buffer); + mhd_assert ((0 != c->read_buffer_offset) || (NULL == c->read_buffer)); + mhd_assert (c == c->upgr.c); + + pupgr_data = (mhd_ACTION_UPGRADE == c->rq.app_act.head_act.act) ? + &(c->rq.app_act.head_act.data.upgrd) : + &(c->rq.app_act.upl_act.data.upgrd); + + // TODO: Support thread-per-connection + + c->upgr.c = c; + if (! mhd_mutex_init (&(c->upgr.lock))) + { + c->upgr.c = NULL; + mhd_LOG_MSG (c->daemon, + MHD_SC_MUTEX_INIT_FAILURE, + "Failed to initialise mutex for HTTP-Upgraded operations"); + mhd_conn_start_closing_no_sys_res (c); + return false; + } + mhd_DLINKEDL_INIT_LINKS (c, upgr_cleanup); + mhd_stream_release_write_buffer (c); + mhd_conn_pre_upgrade (c); + + mhd_conn_pre_clean_part1 (c); + + c->state = MHD_CONNECTION_UPGRADED; + + mhd_assert (! c->in_proc_ready); + mhd_assert (NULL == mhd_DLINKEDL_GET_PREV (c, by_timeout)); + mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (c, by_timeout)); + mhd_assert (c != mhd_DLINKEDL_GET_FIRST (&(c->daemon->conns), def_timeout)); + mhd_assert (c != mhd_DLINKEDL_GET_LAST (&(c->daemon->conns), def_timeout)); + mhd_assert (c != mhd_DLINKEDL_GET_FIRST (&(c->daemon->conns), cust_timeout)); + mhd_assert (c != mhd_DLINKEDL_GET_LAST (&(c->daemon->conns), cust_timeout)); + + pupgr_data->cb (pupgr_data->cb_cls, + &(c->rq), + &(c->upgr)); + return true; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) enum MHD_StatusCode +MHD_upgraded_close (struct MHD_UpgradeHandle *urh) +{ + struct MHD_Connection *const restrict c = urh->c; + struct MHD_Daemon *const restrict d = c->daemon; + + if (MHD_CONNECTION_UPGRADED != c->state) /* Probably, assert would be better here */ + return MHD_SC_TOO_LATE; + + c->state = MHD_CONNECTION_UPGRADED_CLEANING; + mhd_mutex_lock_chk (&(d->conns.upgr.ucu_lock)); + mhd_DLINKEDL_INS_LAST (&(d->conns.upgr), c, upgr_cleanup); + mhd_mutex_unlock_chk (&(d->conns.upgr.ucu_lock)); + (void) mhd_daemon_trigger_itc (d); /* Ignore result, the connection has been placed in cleanup list already */ + + return MHD_SC_OK; +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) void +mhd_upgraded_deinit (struct MHD_Connection *restrict c) +{ + mhd_assert ((MHD_CONNECTION_UPGRADED_CLEANING == c->state) || \ + (MHD_CONNECTION_UPGRADED == c->state)); + mhd_assert (c == c->upgr.c); + + mhd_mutex_destroy_chk (&(c->upgr.lock)); +} diff --git a/src/mhd2/upgrade_proc.h b/src/mhd2/upgrade_proc.h @@ -0,0 +1,68 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/upgrade_proc.h + * @brief The declaration of functions for processing data for HTTP Upgrade + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_UPGRADE_PROC_H +#define MHD_UPGRADE_PROC_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Switch to "upgrading" state if the full upgrade headers have been sent + * completely. + * @param c the connection to use + * @return 'true' if connection is switched to "upgrading" state, + * 'false' if headers has not been sent completely yet. + */ +MHD_INTERNAL bool +mhd_upgrade_try_start_upgrading (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ (1); + + +/** + * Switch connection to "upgraded" state, call application callback for + * switching to "upgraded" state. + * @param c the connection to use + * @return 'true' if connection is switched to "upgraded" state, + * 'false' if connection failed to switch (initialisation error). + */ +MHD_INTERNAL bool +mhd_upgrade_finish_switch_to_upgraded (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ (1); + + +/** + * De-initialise HTTP-Upgraded-specific data + * @param c the connection the de-initialise + */ +MHD_INTERNAL void +mhd_upgraded_deinit (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ (1); + +#endif /* ! MHD_UPGRADE_PROC_H */