libmicrohttpd2

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

commit 9b52a435ef23eef4703e19a8e6c6c3b402f2a1b2
parent 1d164613b04e391f0ef93c66665e91da62b3a192
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Wed, 31 Dec 2025 16:28:59 +0100

Fixed buffer shortage processing

Diffstat:
Msrc/mhd2/stream_process_request.c | 145+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/mhd2/stream_process_request.h | 28+++++++++++++++++++++++-----
Msrc/mhd2/stream_process_states.c | 155++++++++++++++++++++++++++++++++++++-------------------------------------------
3 files changed, 168 insertions(+), 160 deletions(-)

diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c @@ -3770,8 +3770,10 @@ handle_req_chunk_size_line_no_space (struct MHD_Connection *c, * stored in the memory pool (like some header). * @param c the connection to process * @param stage the receive stage where the exhaustion happens. + * @return 'true' if connection should NOT be closed, + * 'false' if connection is closing */ -static MHD_FN_PAR_NONNULL_ALL_ void +static MHD_FN_PAR_NONNULL_ALL_ bool handle_recv_no_space (struct MHD_Connection *c, enum MHD_ProcRecvDataStage stage) { @@ -3809,7 +3811,7 @@ handle_recv_no_space (struct MHD_Connection *c, "No space left in the read buffer when " \ "receiving the initial part of " \ "the request line."); - return; + return false; case MHD_PROC_RECV_URI: case MHD_PROC_RECV_HTTPVER: /* Some data has been received, but the request line is incomplete */ @@ -3823,7 +3825,7 @@ handle_recv_no_space (struct MHD_Connection *c, mhd_RESPOND_WITH_ERROR_STATIC (c, MHD_HTTP_STATUS_URI_TOO_LONG, ERR_RSP_MSG_REQUEST_TOO_BIG); - return; + return true; } mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REQUEST, \ "No space left in the read buffer when " \ @@ -3831,17 +3833,17 @@ handle_recv_no_space (struct MHD_Connection *c, "the request line. " \ "The request uses non-standard HTTP request " \ "method token."); - return; + return false; case MHD_PROC_RECV_HEADERS: handle_req_headers_no_space (c, c->read_buffer, c->read_buffer_offset); - return; + return true; case MHD_PROC_RECV_BODY_NORMAL: /* A header probably has been added to a suspended connection and it took precisely all the space in the buffer. Very low probability. */ mhd_assert (! c->rq.have_chunked_upload); handle_req_headers_no_space (c, NULL, 0); // FIXME: check - return; + return true; case MHD_PROC_RECV_BODY_CHUNKED: mhd_assert (c->rq.have_chunked_upload); if (c->rq.current_chunk_offset != c->rq.current_chunk_size) @@ -3866,16 +3868,17 @@ handle_recv_no_space (struct MHD_Connection *c, c->read_buffer_offset); } } - return; + return true; case MHD_PROC_RECV_FOOTERS: handle_req_footers_no_space (c, c->read_buffer, c->read_buffer_offset); - return; + return true; /* The next cases should not be possible */ case MHD_PROC_RECV_COOKIE: default: break; } mhd_UNREACHABLE (); + return false; } @@ -3968,9 +3971,11 @@ try_grow_read_buffer (struct MHD_Connection *restrict connection, } -MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_ConnectionBufferGrowResult mhd_stream_check_and_grow_read_buffer_space (struct MHD_Connection *restrict c) { + enum MHD_ProcRecvDataStage stage; + bool res; /** * The increase of read buffer size is desirable. */ @@ -4024,78 +4029,78 @@ mhd_stream_check_and_grow_read_buffer_space (struct MHD_Connection *restrict c) } if (! rbuff_grow_desired) - return true; /* No need to increase the buffer */ + return mhd_CONN_BUFF_GROW_OK; /* No need to increase the buffer */ if (try_grow_read_buffer (c, rbuff_grow_required)) - return true; /* Buffer increase succeed */ + return mhd_CONN_BUFF_GROW_OK; /* Buffer increase succeed */ if (! rbuff_grow_required) - return true; /* Can continue without buffer increase */ + return mhd_CONN_BUFF_GROW_OK; /* Can continue without buffer increase */ /* Failed to increase the read buffer size, but need to read the data from the network. No more space left in the buffer, no more space to increase the buffer. */ - if (1) + switch (c->stage) { - enum MHD_ProcRecvDataStage stage; - - switch (c->stage) - { - case mhd_HTTP_STAGE_INIT: - stage = MHD_PROC_RECV_INIT; - break; - case mhd_HTTP_STAGE_REQ_LINE_RECEIVING: - if (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd) - stage = MHD_PROC_RECV_METHOD; - else if (0 == c->rq.req_target_len) - stage = MHD_PROC_RECV_URI; - else - stage = MHD_PROC_RECV_HTTPVER; - break; - case mhd_HTTP_STAGE_REQ_HEADERS_RECEIVING: - stage = MHD_PROC_RECV_HEADERS; - break; - case mhd_HTTP_STAGE_BODY_RECEIVING: - stage = c->rq.have_chunked_upload ? - MHD_PROC_RECV_BODY_CHUNKED : MHD_PROC_RECV_BODY_NORMAL; - break; - case mhd_HTTP_STAGE_FOOTERS_RECEIVING: - stage = MHD_PROC_RECV_FOOTERS; - break; - case mhd_HTTP_STAGE_REQ_LINE_RECEIVED: - case mhd_HTTP_STAGE_HEADERS_RECEIVED: - case mhd_HTTP_STAGE_HEADERS_PROCESSED: - case mhd_HTTP_STAGE_CONTINUE_SENDING: - case mhd_HTTP_STAGE_BODY_RECEIVED: - case mhd_HTTP_STAGE_FOOTERS_RECEIVED: - case mhd_HTTP_STAGE_FULL_REQ_RECEIVED: - case mhd_HTTP_STAGE_REQ_RECV_FINISHED: - case mhd_HTTP_STAGE_START_REPLY: - case mhd_HTTP_STAGE_HEADERS_SENDING: - case mhd_HTTP_STAGE_HEADERS_SENT: - case mhd_HTTP_STAGE_UNCHUNKED_BODY_UNREADY: - case mhd_HTTP_STAGE_UNCHUNKED_BODY_READY: - case mhd_HTTP_STAGE_CHUNKED_BODY_UNREADY: - case mhd_HTTP_STAGE_CHUNKED_BODY_READY: - case mhd_HTTP_STAGE_CHUNKED_BODY_SENT: - case mhd_HTTP_STAGE_FOOTERS_SENDING: - case mhd_HTTP_STAGE_FULL_REPLY_SENT: - case mhd_HTTP_STAGE_PRE_CLOSING: - case mhd_HTTP_STAGE_CLOSED: + case mhd_HTTP_STAGE_INIT: + stage = MHD_PROC_RECV_INIT; + break; + case mhd_HTTP_STAGE_REQ_LINE_RECEIVING: + if (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd) + stage = MHD_PROC_RECV_METHOD; + else if (0 == c->rq.req_target_len) + stage = MHD_PROC_RECV_URI; + else + stage = MHD_PROC_RECV_HTTPVER; + break; + case mhd_HTTP_STAGE_REQ_HEADERS_RECEIVING: + stage = MHD_PROC_RECV_HEADERS; + break; + case mhd_HTTP_STAGE_BODY_RECEIVING: + stage = c->rq.have_chunked_upload ? + MHD_PROC_RECV_BODY_CHUNKED : MHD_PROC_RECV_BODY_NORMAL; + break; + case mhd_HTTP_STAGE_FOOTERS_RECEIVING: + stage = MHD_PROC_RECV_FOOTERS; + break; + case mhd_HTTP_STAGE_REQ_LINE_RECEIVED: + case mhd_HTTP_STAGE_HEADERS_RECEIVED: + case mhd_HTTP_STAGE_HEADERS_PROCESSED: + case mhd_HTTP_STAGE_CONTINUE_SENDING: + case mhd_HTTP_STAGE_BODY_RECEIVED: + case mhd_HTTP_STAGE_FOOTERS_RECEIVED: + case mhd_HTTP_STAGE_FULL_REQ_RECEIVED: + case mhd_HTTP_STAGE_REQ_RECV_FINISHED: + case mhd_HTTP_STAGE_START_REPLY: + case mhd_HTTP_STAGE_HEADERS_SENDING: + case mhd_HTTP_STAGE_HEADERS_SENT: + case mhd_HTTP_STAGE_UNCHUNKED_BODY_UNREADY: + case mhd_HTTP_STAGE_UNCHUNKED_BODY_READY: + case mhd_HTTP_STAGE_CHUNKED_BODY_UNREADY: + case mhd_HTTP_STAGE_CHUNKED_BODY_READY: + case mhd_HTTP_STAGE_CHUNKED_BODY_SENT: + case mhd_HTTP_STAGE_FOOTERS_SENDING: + case mhd_HTTP_STAGE_FULL_REPLY_SENT: + case mhd_HTTP_STAGE_PRE_CLOSING: + case mhd_HTTP_STAGE_CLOSED: #ifdef MHD_SUPPORT_UPGRADE - case mhd_HTTP_STAGE_UPGRADE_HEADERS_SENDING: - case mhd_HTTP_STAGE_UPGRADING: - case mhd_HTTP_STAGE_UPGRADED: - case mhd_HTTP_STAGE_UPGRADED_CLEANING: + case mhd_HTTP_STAGE_UPGRADE_HEADERS_SENDING: + case mhd_HTTP_STAGE_UPGRADING: + case mhd_HTTP_STAGE_UPGRADED: + case mhd_HTTP_STAGE_UPGRADED_CLEANING: #endif /* MHD_SUPPORT_UPGRADE */ - default: - mhd_UNREACHABLE (); - stage = MHD_PROC_RECV_BODY_NORMAL; - break; - } - - handle_recv_no_space (c, stage); + default: + mhd_UNREACHABLE (); + stage = MHD_PROC_RECV_BODY_NORMAL; + break; } - return false; + + res = handle_recv_no_space (c, stage); + + mhd_assert (! res || ! c->dbg.closing_started); + mhd_assert (res || c->dbg.closing_started); + + return + res ? mhd_CONN_BUFF_GROW_ERR_REPLY : mhd_CONN_BUFF_GROW_ERR_CONN_CLOSE; } diff --git a/src/mhd2/stream_process_request.h b/src/mhd2/stream_process_request.h @@ -226,6 +226,25 @@ mhd_stream_process_req_recv_finished (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; /** + * Result of the connection buffer growing + */ +enum mhd_ConnectionBufferGrowResult +{ + /** + * No error + */ + mhd_CONN_BUFF_GROW_OK = 0u, + /** + * Failed to grow buffer, the connection is closing + */ + mhd_CONN_BUFF_GROW_ERR_CONN_CLOSE, + /** + * Failed to grow buffer, the error reply is pending + */ + mhd_CONN_BUFF_GROW_ERR_REPLY +}; + +/** * Check whether enough space is available in the read buffer for the next * operation. * Handles grow of the buffer if required and error conditions (when buffer @@ -233,12 +252,11 @@ MHD_FN_PAR_NONNULL_ALL_; * Must be called only when processing the event loop states and when * reading is required for the next phase. * @param c the connection to check - * @return true if connection handled successfully and enough buffer - * is available, - * false if not enough buffer is available and the loop's states - * must be processed again as connection is in the error state. + * @return #mhd_CONN_BUFF_GROW_OK if connection handled successfully and + * enough buffer is available, + * error code otherwise. */ -MHD_INTERNAL bool +MHD_INTERNAL enum mhd_ConnectionBufferGrowResult mhd_stream_check_and_grow_read_buffer_space (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c @@ -276,56 +276,6 @@ finish_resume (struct MHD_Connection *restrict c) } -/** - * Update current processing state: need to receive, need to send. - * Mark stream as ready or not ready for processing. - * Grow the receive buffer if neccesary, close stream if no buffer space left, - * but connection needs to receive. - * @param c the connection to update - * @return true if connection states updated successfully, - * false if connection has been prepared for closing - */ -static MHD_FN_PAR_NONNULL_ALL_ bool -update_active_state (struct MHD_Connection *restrict c) -{ - /* Do not update states of suspended connection */ - mhd_assert (! c->suspended); - - if (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)) - { - mhd_assert (0 && "Should be handled earlier"); - mhd_conn_start_closing_skt_err (c); - return false; - } - - mhd_conn_event_loop_state_update (c); - - if (! mhd_C_IS_HTTP2 (c)) - { - if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info)) - { - /* Check whether the space is available to receive data */ - if (! mhd_stream_check_and_grow_read_buffer_space (c)) - { - mhd_assert (c->discard_request); - return false; - } - } - } - - /* Current MHD design assumes that data must be always processes when - * available. If it is not possible, connection must be suspended. */ - mhd_assert (MHD_EVENT_LOOP_INFO_PROCESS != c->event_loop_info); - - /* Sockets errors must be already handled */ - mhd_assert (0 == (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)); - - mhd_conn_mark_ready_update (c); - - return true; -} - - MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool mhd_conn_process_data (struct MHD_Connection *restrict c) { @@ -364,7 +314,8 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) { if (! mhd_h2_conn_process_data (c)) return false; - update_active_state (c); + mhd_conn_event_loop_state_update (c); + mhd_conn_mark_ready_update (c); return true; } #endif /* MHD_SUPPORT_HTTP2 */ @@ -408,7 +359,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) mhd_assert (! c->suspended); - while (! c->suspended) + while (! 0) { #ifdef MHD_SUPPORT_HTTPS mhd_assert (! mhd_C_HAS_TLS (c) || \ @@ -626,45 +577,79 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) mhd_UNREACHABLE (); break; } - break; - } - mhd_assert (mhd_HTTP_STAGE_CLOSED != c->stage); + mhd_assert (mhd_HTTP_STAGE_CLOSED != c->stage); - if (mhd_HTTP_STAGE_PRE_CLOSING == c->stage) - { - mhd_assert (0 && "Pre-closing should be already caught in the loop"); - mhd_UNREACHABLE (); - return false; - } + if (mhd_HTTP_STAGE_PRE_CLOSING == c->stage) + { + mhd_assert (0 && "Pre-closing should be already caught in the loop"); + mhd_UNREACHABLE (); + return false; + } - if (c->suspended) - { - /* Do not perform any network activity while suspended */ - c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; + if (c->suspended) + { + /* Do not perform any network activity while suspended */ + c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; + + mhd_conn_mark_unready (c, d); + mhd_conn_deinit_activity_timeout (c); + #ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Connection suspended, FD: %2llu\n", + (unsigned long long) c->sk.fd); + #endif /* mhd_DEBUG_SUSPEND_RESUME */ + return true; + } - mhd_conn_mark_unready (c, d); - mhd_conn_deinit_activity_timeout (c); -#ifdef mhd_DEBUG_SUSPEND_RESUME - fprintf (stderr, - "%%%%%% Connection suspended, FD: %2llu\n", - (unsigned long long) c->sk.fd); -#endif /* mhd_DEBUG_SUSPEND_RESUME */ - return true; - } + if ((c->sk.state.rmt_shut_wr) && (mhd_HTTP_STAGE_START_REPLY > c->stage)) + { + mhd_conn_start_closing (c, + (mhd_HTTP_STAGE_INIT == c->stage) ? + mhd_CONN_CLOSE_HTTP_COMPLETED : + mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY, + NULL); + return false; + } - if ((c->sk.state.rmt_shut_wr) && (mhd_HTTP_STAGE_START_REPLY > c->stage)) - { - mhd_conn_start_closing (c, - (mhd_HTTP_STAGE_INIT == c->stage) ? - mhd_CONN_CLOSE_HTTP_COMPLETED : - mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY, - NULL); - return false; - } + if (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)) + { + mhd_assert (0 && "Should be handled earlier"); + mhd_conn_start_closing_skt_err (c); + return false; + } - if (! update_active_state (c)) - return false; + mhd_conn_event_loop_state_update (c); + + if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info)) + { + /* Check whether the space is available to receive data */ + switch (mhd_stream_check_and_grow_read_buffer_space (c)) + { + case mhd_CONN_BUFF_GROW_ERR_CONN_CLOSE: + mhd_assert (c->discard_request); + return false; + case mhd_CONN_BUFF_GROW_ERR_REPLY: + continue; /* Process error reply */ + case mhd_CONN_BUFF_GROW_OK: + break; + default: + mhd_UNREACHABLE (); + break; + } + } + + /* Current MHD design assumes that data must be always processes when + * available. If it is not possible, connection must be suspended. */ + mhd_assert (MHD_EVENT_LOOP_INFO_PROCESS != c->event_loop_info); + + /* Sockets errors must be already handled */ + mhd_assert (0 == (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)); + + mhd_conn_mark_ready_update (c); + + break; + } return true; }