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:
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 */