From e7eea331b13560ab40c09f3805b8a5bd7144740e Mon Sep 17 00:00:00 2001 From: "Evgeny Grin (Karlson2k)" Date: Sun, 21 Jul 2024 07:36:40 +0200 Subject: [PATCH] WIP-5 --- src/Makefile.am | 2 +- src/examples/Makefile.am | 255 +--------------- src/examples/minimal_example2.c | 96 +++++++ src/include/microhttpd2.h | 17 +- src/mhd2/Makefile.am | 2 +- src/mhd2/action.c | 10 +- src/mhd2/conn_data_recv.c | 5 + src/mhd2/conn_data_send.c | 23 +- src/mhd2/daemon_add_conn.c | 51 +++- src/mhd2/daemon_add_conn.h | 2 + src/mhd2/events_process.c | 23 +- src/mhd2/http_status_str.c | 4 +- src/mhd2/mhd_assert.h | 7 + src/mhd2/mhd_connection.h | 12 + src/mhd2/mhd_daemon.h | 7 +- src/mhd2/mhd_request.h | 17 -- src/mhd2/mhd_response.h | 24 +- src/mhd2/mhd_socket_error.h | 7 +- src/mhd2/respond_with_error.c | 123 ++++++++ src/mhd2/respond_with_error.h | 35 ++- src/mhd2/response_destroy.c | 2 + src/mhd2/response_from.c | 52 ++++ src/mhd2/response_from.h | 9 + src/mhd2/response_funcs.c | 3 +- src/mhd2/stream_funcs.c | 99 ++++++- src/mhd2/stream_funcs.h | 28 +- src/mhd2/stream_process_reply.c | 17 +- src/mhd2/stream_process_request.c | 464 ++++++++++++++++++++++++++++-- src/mhd2/stream_process_request.h | 16 ++ src/mhd2/stream_process_states.c | 129 ++++++++- 30 files changed, 1173 insertions(+), 368 deletions(-) create mode 100644 src/examples/minimal_example2.c create mode 100644 src/mhd2/respond_with_error.c diff --git a/src/Makefile.am b/src/Makefile.am index ad20b157..c73a22a3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,7 +15,7 @@ SUBDIRS += microhttpd_ws endif if BUILD_EXAMPLES -#SUBDIRS += examples +SUBDIRS += examples endif if BUILD_TOOLS diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am index ecd998dd..a6cd0d55 100644 --- a/src/examples/Makefile.am +++ b/src/examples/Makefile.am @@ -3,10 +3,9 @@ SUBDIRS = . AM_CPPFLAGS = \ -I$(top_srcdir)/src/include \ - $(CPPFLAGS_ac) \ - -DDATA_DIR=\"$(top_srcdir)/src/datadir/\" + $(CPPFLAGS_ac) -AM_CFLAGS = $(CFLAGS_ac) @LIBGCRYPT_CFLAGS@ +AM_CFLAGS = $(CFLAGS_ac) AM_LDFLAGS = $(LDFLAGS_ac) @@ -14,254 +13,20 @@ MHD_CPU_COUNT_DEF = -DMHD_CPU_COUNT=$(CPU_COUNT) AM_TESTS_ENVIRONMENT = $(TESTS_ENVIRONMENT_ac) +LDADD = $(top_builddir)/src/mhd2/libmicrohttpd2.la + if USE_COVERAGE AM_CFLAGS += --coverage endif -$(top_builddir)/src/microhttpd/libmicrohttpd.la: $(top_builddir)/src/microhttpd/Makefile - @echo ' cd $(top_builddir)/src/microhttpd && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd.la'; \ - $(am__cd) $(top_builddir)/src/microhttpd && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd.la +$(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile + @echo ' cd $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la'; \ + $(am__cd) $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la # example programs noinst_PROGRAMS = \ - benchmark \ - benchmark_https \ - chunked_example \ - minimal_example \ - minimal_example_empty \ - dual_stack_example \ - minimal_example_comet \ - querystring_example \ - timeout \ - fileserver_example \ - fileserver_example_dirs \ - fileserver_example_external_select \ - refuse_post_example + minimal_example2 -if HAVE_EXPERIMENTAL -noinst_PROGRAMS += \ - websocket_chatserver_example -endif - -if MHD_USE_EPOLL -noinst_PROGRAMS += \ - suspend_resume_epoll -endif - -EXTRA_DIST = msgs_i18n.c -noinst_EXTRA_DIST = msgs_i18n.c - -if ENABLE_HTTPS -noinst_PROGRAMS += \ - https_fileserver_example \ - minimal_example_empty_tls -endif -if HAVE_POSTPROCESSOR -noinst_PROGRAMS += \ - post_example -if HAVE_POSIX_THREADS -noinst_PROGRAMS += demo -if ENABLE_HTTPS -noinst_PROGRAMS += demo_https -endif -endif -endif - -if ENABLE_DAUTH -noinst_PROGRAMS += \ - digest_auth_example \ - digest_auth_example_adv -endif - -if ENABLE_BAUTH -noinst_PROGRAMS += \ - authorization_example -endif - -if HAVE_POSIX_THREADS -if ENABLE_UPGRADE -noinst_PROGRAMS += \ - upgrade_example \ - websocket_threaded_example -endif -endif - -if HAVE_ZLIB -noinst_PROGRAMS += \ - http_compression \ - http_chunked_compression -endif - -if HAVE_W32 -AM_CFLAGS += -DWINDOWS -endif - -minimal_example_SOURCES = \ - minimal_example.c -minimal_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -minimal_example_empty_SOURCES = \ - minimal_example_empty.c -minimal_example_empty_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -minimal_example_empty_tls_SOURCES = \ - minimal_example_empty_tls.c -minimal_example_empty_tls_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -upgrade_example_SOURCES = \ - upgrade_example.c -upgrade_example_CFLAGS = \ - $(PTHREAD_CFLAGS) $(AM_CFLAGS) -upgrade_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la \ - $(PTHREAD_LIBS) - -websocket_threaded_example_SOURCES = \ - websocket_threaded_example.c -websocket_threaded_example_CFLAGS = \ - $(PTHREAD_CFLAGS) $(AM_CFLAGS) -websocket_threaded_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la \ - $(PTHREAD_LIBS) - -timeout_SOURCES = \ - timeout.c -timeout_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -chunked_example_SOURCES = \ - chunked_example.c -chunked_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -websocket_chatserver_example_SOURCES = \ - websocket_chatserver_example.c -websocket_chatserver_example_LDADD = \ - $(top_builddir)/src/microhttpd_ws/libmicrohttpd_ws.la \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -demo_SOURCES = \ - demo.c -demo_CFLAGS = \ - $(PTHREAD_CFLAGS) $(AM_CFLAGS) -demo_CPPFLAGS = \ - $(AM_CPPFLAGS) $(MHD_CPU_COUNT_DEF) -demo_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la \ - $(PTHREAD_LIBS) -if MHD_HAVE_LIBMAGIC -demo_LDADD += -lmagic -endif - -demo_https_SOURCES = \ - demo_https.c -demo_https_CFLAGS = \ - $(PTHREAD_CFLAGS) $(AM_CFLAGS) -demo_https_CPPFLAGS = \ - $(AM_CPPFLAGS) $(MHD_CPU_COUNT_DEF) -demo_https_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la \ - $(PTHREAD_LIBS) -if MHD_HAVE_LIBMAGIC -demo_https_LDADD += -lmagic -endif - -benchmark_SOURCES = \ - benchmark.c -benchmark_CPPFLAGS = \ - $(AM_CPPFLAGS) $(MHD_CPU_COUNT_DEF) -benchmark_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -suspend_resume_epoll_SOURCES = \ - suspend_resume_epoll.c -suspend_resume_epoll_CPPFLAGS = \ - $(AM_CPPFLAGS) $(MHD_CPU_COUNT_DEF) -suspend_resume_epoll_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -benchmark_https_SOURCES = \ - benchmark_https.c -benchmark_https_CPPFLAGS = \ - $(AM_CPPFLAGS) $(MHD_CPU_COUNT_DEF) -benchmark_https_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -dual_stack_example_SOURCES = \ - dual_stack_example.c -dual_stack_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -post_example_SOURCES = \ - post_example.c -post_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -minimal_example_comet_SOURCES = \ - minimal_example_comet.c -minimal_example_comet_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -authorization_example_SOURCES = \ - authorization_example.c -authorization_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -digest_auth_example_SOURCES = \ - digest_auth_example.c -digest_auth_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -digest_auth_example_adv_SOURCES = \ - digest_auth_example_adv.c -digest_auth_example_adv_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -refuse_post_example_SOURCES = \ - refuse_post_example.c -refuse_post_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -querystring_example_SOURCES = \ - querystring_example.c -querystring_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -fileserver_example_SOURCES = \ - fileserver_example.c -fileserver_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -fileserver_example_dirs_SOURCES = \ - fileserver_example_dirs.c -fileserver_example_dirs_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -fileserver_example_external_select_SOURCES = \ - fileserver_example_external_select.c -fileserver_example_external_select_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -https_fileserver_example_SOURCES = \ -https_fileserver_example.c -https_fileserver_example_CPPFLAGS = \ - $(AM_CPPFLAGS) $(GNUTLS_CPPFLAGS) -https_fileserver_example_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la - -http_compression_SOURCES = \ - http_compression.c -http_chunked_compression_SOURCES = \ - http_chunked_compression.c -http_compression_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la -http_chunked_compression_LDADD = \ - $(top_builddir)/src/microhttpd/libmicrohttpd.la -if HAVE_ZLIB - http_compression_LDADD += -lz - http_chunked_compression_LDADD += -lz -endif +minimal_example2_SOURCES = \ + minimal_example2.c diff --git a/src/examples/minimal_example2.c b/src/examples/minimal_example2.c new file mode 100644 index 00000000..080bb69d --- /dev/null +++ b/src/examples/minimal_example2.c @@ -0,0 +1,96 @@ +/* + 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 minimal_example2.c + * @brief Minimal example for libmicrohttpd v2 + * @author Karlson2k (Evgeny Grin) + */ + +#include +#include + +static MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) +const struct MHD_Action * +req_cb (void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + static const char res_msg[] = "Hello there!"; + + (void) cls; (void) path; (void) method; (void) upload_size; /* Unused */ + + return MHD_action_from_response ( + request, + MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK, + sizeof(res_msg) / sizeof(char) - 1, + res_msg)); +} + + +int +main (int argc, + char *const *argv) +{ + struct MHD_Daemon *d; + int port; + + if (argc != 2) + { + fprintf (stderr,"Usage:\n%s PORT\n", argv[0]); + return 1; + } + port = atoi (argv[1]); + if ( (1 > port) || (port > 65535) ) + { + fprintf (stderr, + "The port must be a number between 1 and 65535.\n"); + return 2; + } + d = MHD_daemon_create (&req_cb, NULL); + if (NULL != d) + { + if (MHD_SC_OK == + MHD_DAEMON_SET_OPTIONS (d, + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, + (uint_least16_t) port))) + { + if (MHD_SC_OK == MHD_daemon_start (d)) + { + printf ("The MHD daemon is listening on port %d\n" + "Press ENTER to stop.\n", port); + (void) fgetc (stdin); + } + else + fprintf (stderr, "Failed to start MHD daemon.\n"); + } + else + fprintf (stderr, "Failed to set MHD daemon run parameters.\n"); + printf ("Stopping... "); + MHD_daemon_destroy (d); + printf ("OK\n"); + + } + else + fprintf (stderr, "Failed to create MHD daemon.\n"); + + return 0; +} diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h index 61c56e6a..20bdd213 100644 --- a/src/include/microhttpd2.h +++ b/src/include/microhttpd2.h @@ -637,6 +637,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_REQ_COOKIE_INVALID = 40163 , + /** + * The request cannot be processed. Sending error reply. + */ + MHD_SC_REQ_PROCCESSING_ERR_REPLY = 40200 + , /* 50000-level errors are because of an error internal to the MHD logic, possibly including our interaction @@ -921,7 +926,7 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_ITC_STATUS_ERROR = 500104 , /** - * We failed to add a socket to the epoll() set. + * Failed to add a socket to the epoll set. */ MHD_SC_EPOLL_CTL_ADD_FAILED = 500110 , @@ -963,6 +968,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_UNEXPECTED_SELECT_ERROR = 50116 , + /** + * Failed to remove a socket to the epoll set. + */ + MHD_SC_EPOLL_CTL_REMOVE_FAILED = 50117 + , /** * poll() is not supported. */ @@ -1072,6 +1082,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_REPLY_POOL_ALLOCATION_FAILURE = 50231 , + /** + * Failed to allocate memory in connection's pool for the reply. + */ + MHD_SC_ERR_RESPONSE_ALLOCATION_FAILURE = 50250 + , /** * The feature is not supported by this MHD build (either * disabled by configure parameters or build platform diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am index 70c98cef..f43f5f09 100644 --- a/src/mhd2/Makefile.am +++ b/src/mhd2/Makefile.am @@ -71,7 +71,7 @@ libmicrohttpd2_la_SOURCES = \ conn_data_send.c conn_data_send.h \ request_funcs.c request_funcs.h \ request_get_value.c request_get_value.h \ - respond_with_error.h \ + respond_with_error.c respond_with_error.h \ response_from.c response_from.h \ response_add_header.c response_add_header.h \ response_destroy.c response_destroy.h \ diff --git a/src/mhd2/action.c b/src/mhd2/action.c index 0971832a..a9de6e76 100644 --- a/src/mhd2/action.c +++ b/src/mhd2/action.c @@ -28,9 +28,13 @@ #include "mhd_action.h" #include "mhd_request.h" -#include "mhd_public_api.h" + #include "daemon_logger.h" +#include "response_funcs.h" + +#include "mhd_public_api.h" + MHD_EXTERN_ MHD_FN_PAR_NONNULL_ALL_ const struct MHD_Action * @@ -56,6 +60,8 @@ MHD_action_from_response (struct MHD_Request *request, if (NULL == response) return (const struct MHD_Action *) NULL; + mhd_response_check_frozen_freeze (response); + head_act->act = mhd_ACTION_RESPONSE; head_act->data.response = response; @@ -155,6 +161,8 @@ MHD_upload_action_from_response (struct MHD_Request *request, if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) return (const struct MHD_UploadAction *) NULL; + mhd_response_check_frozen_freeze (response); + upl_act->act = mhd_UPLOAD_ACTION_SUSPEND; upl_act->data.response = response; diff --git a/src/mhd2/conn_data_recv.c b/src/mhd2/conn_data_recv.c index 15ae732d..bbbd98ad 100644 --- a/src/mhd2/conn_data_recv.c +++ b/src/mhd2/conn_data_recv.c @@ -65,6 +65,11 @@ mhd_conn_data_recv (struct MHD_Connection *restrict c, if ((mhd_SOCKET_ERR_NO_ERROR != res) || has_err) { /* Handle errors */ + if ((mhd_SOCKET_ERR_NO_ERROR == res) && (0 == received)) + { + c->sk_rmt_shut_wr = true; + res = mhd_SOCKET_ERR_REMT_DISCONN; + } if (has_err && ! mhd_SOCKET_ERR_IS_HARD (res) && c->sk_nonblck) { /* Re-try last time to detect the error */ diff --git a/src/mhd2/conn_data_send.c b/src/mhd2/conn_data_send.c index 3d186885..f6945d92 100644 --- a/src/mhd2/conn_data_send.c +++ b/src/mhd2/conn_data_send.c @@ -148,8 +148,10 @@ mhd_conn_data_send (struct MHD_Connection *restrict c) (! c->rp.props.send_reply_body)), &sent); } - if (mhd_SOCKET_ERR_NO_ERROR != res) + if (mhd_SOCKET_ERR_NO_ERROR == res) { + mhd_assert (MHD_CONNECTION_HEADERS_SENDING == c->state); + if (sent > wb_ready) { /* The complete header and some response data have been sent, @@ -157,17 +159,20 @@ mhd_conn_data_send (struct MHD_Connection *restrict c) mhd_assert (0 == c->rp.rsp_cntn_read_pos); mhd_assert (! c->rp.props.chunked); mhd_assert (c->rp.props.send_reply_body); + c->state = MHD_CONNECTION_UNCHUNKED_BODY_READY; c->write_buffer_send_offset += wb_ready; c->rp.rsp_cntn_read_pos = sent - wb_ready; + if (c->rp.rsp_cntn_read_pos == c->rp.response->cntn_size) + c->state = MHD_CONNECTION_FULL_REPLY_SENT; } else + { c->write_buffer_send_offset += sent; + // TODO: move it to data processing + check_write_done (c, + MHD_CONNECTION_HEADERS_SENT); + } - mhd_assert (MHD_CONNECTION_HEADERS_SENDING == c->state); - - // TODO: move it to data processing - check_write_done (c, - MHD_CONNECTION_HEADERS_SENT); } @@ -226,7 +231,7 @@ mhd_conn_data_send (struct MHD_Connection *restrict c) res = mhd_SOCKET_ERR_INTERNAL; } - if (mhd_SOCKET_ERR_NO_ERROR != res) + if (mhd_SOCKET_ERR_NO_ERROR == res) { if (mhd_REPLY_CNTN_LOC_CONN_BUF != c->rp.cntn_loc) c->rp.rsp_cntn_read_pos += sent; @@ -253,7 +258,7 @@ mhd_conn_data_send (struct MHD_Connection *restrict c) + c->write_buffer_send_offset, true, &sent); - if (mhd_SOCKET_ERR_NO_ERROR != res) + if (mhd_SOCKET_ERR_NO_ERROR == res) { c->write_buffer_send_offset += sent; // TODO: move it to data processing @@ -267,7 +272,7 @@ mhd_conn_data_send (struct MHD_Connection *restrict c) res = mhd_SOCKET_ERR_INTERNAL; } - if (mhd_SOCKET_ERR_NO_ERROR != res) + if (mhd_SOCKET_ERR_NO_ERROR == res) { mhd_stream_update_activity_mark (c); // TODO: centralise activity mark updates } diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c index 04b0b927..0da18132 100644 --- a/src/mhd2/daemon_add_conn.c +++ b/src/mhd2/daemon_add_conn.c @@ -41,6 +41,7 @@ #include "sys_base_types.h" #include "sys_sockets_types.h" #include "sys_sockets_headers.h" +#include "sys_ip_headers.h" #include #ifdef MHD_USE_EPOLL @@ -49,16 +50,15 @@ #include "compat_calloc.h" +#include "mhd_sockets_macros.h" +#include "mhd_sockets_funcs.h" + #include "mhd_panic.h" #include "mhd_daemon.h" #include "mhd_connection.h" -#include "mhd_public_api.h" + #include "daemon_logger.h" -#include "sys_sockets_headers.h" -#include "sys_ip_headers.h" -#include "mhd_sockets_macros.h" -#include "mhd_sockets_funcs.h" #include "mhd_mono_clock.h" #include "mhd_mempool.h" #include "events_process.h" @@ -66,6 +66,8 @@ #include "response_from.h" #include "response_destroy.h" +#include "mhd_public_api.h" + /** * Set initial internal states for the connection to start reading and @@ -146,6 +148,9 @@ connection_clean_destroy (struct MHD_Connection *restrict c, /* Connection must not be in 'process ready' list */ 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 (&(d->events), proc_ready)); + mhd_assert (c != mhd_DLINKEDL_GET_LAST (&(d->events), proc_ready)); #ifdef MHD_USE_EPOLL if (mhd_D_IS_USING_EPOLL (d)) @@ -402,7 +407,7 @@ new_connection_process_ (struct MHD_Daemon *restrict daemon, &event)) { mhd_LOG_MSG (daemon, MHD_SC_EPOLL_CTL_ADD_FAILED, - "Failed to add connection socket ."); + "Failed to add connection socket to epoll."); res = MHD_SC_EPOLL_CTL_ADD_FAILED; } else @@ -897,7 +902,7 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) return mhd_DAEMON_ACCEPT_FAILED; } - if (mhd_FD_FITS_DAEMON (daemon, s)) + if (! mhd_FD_FITS_DAEMON (daemon, s)) { mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_OUTSIDE_OF_SET_RANGE, \ "The accepted socket has value outside of allowed range."); @@ -984,18 +989,36 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) sk_nonbl, sk_spipe_supprs, sk_non_ip)) ? - mhd_DAEMON_ACCEPT_FAILED : mhd_DAEMON_ACCEPT_SUCCESS; + mhd_DAEMON_ACCEPT_SUCCESS : mhd_DAEMON_ACCEPT_FAILED; } MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void mhd_conn_close_final (struct MHD_Connection *restrict c) { - if ((NULL != mhd_DLINKEDL_GET_NEXT (c, proc_ready)) || - (NULL != mhd_DLINKEDL_GET_PREV (c, proc_ready)) || - (c == mhd_DLINKEDL_GET_FIRST (&(c->daemon->events), proc_ready))) - mhd_DLINKEDL_DEL (&(c->daemon->events), c, proc_ready); + mhd_assert (c->dbg.pre_closed); + mhd_assert (c->dbg.pre_cleaned); + mhd_assert (NULL == c->rp.response); + mhd_assert (! c->rq.app_aware); + 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 (0 && "Not finished yet"); - // TODO: finish + if (mhd_D_HAS_THR_PER_CONN (c->daemon)) + { + mhd_assert (0 && "Not implemented yet"); + // TODO: Support "thread per connection" + } + 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_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); + free (c); } diff --git a/src/mhd2/daemon_add_conn.h b/src/mhd2/daemon_add_conn.h index dacac27a..0cba2de6 100644 --- a/src/mhd2/daemon_add_conn.h +++ b/src/mhd2/daemon_add_conn.h @@ -84,6 +84,8 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon); * Finally close and clean-up 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. * @param c the connection to close */ MHD_INTERNAL void diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c index b54cd0dc..509030c5 100644 --- a/src/mhd2/events_process.c +++ b/src/mhd2/events_process.c @@ -67,12 +67,11 @@ update_conn_net_status (struct MHD_Daemon *restrict d, (sk_state | (unsigned int) mhd_SOCKET_NET_STATE_ERROR_READY); c->sk_ready = sk_state; - if (NULL != mhd_DLINKEDL_GET_PREV (c,proc_ready)) + if ((NULL != mhd_DLINKEDL_GET_NEXT (c, proc_ready)) || + (NULL != mhd_DLINKEDL_GET_PREV (c, proc_ready)) || + (c == mhd_DLINKEDL_GET_FIRST (&(c->daemon->events), proc_ready))) return; /* In the 'proc_ready' list already */ - mhd_assert (0 != (((unsigned int) c->event_loop_info) \ - & MHD_EVENT_LOOP_INFO_PROCESS)); - if ((0 != (((unsigned int) c->sk_ready) & ((unsigned int) c->event_loop_info) & (MHD_EVENT_LOOP_INFO_READ | MHD_EVENT_LOOP_INFO_WRITE))) || @@ -209,7 +208,7 @@ daemon_accept_new_conns (struct MHD_Daemon *restrict d) static bool -daemon_process_all_act_coons (struct MHD_Daemon *restrict d) +daemon_process_all_act_conns (struct MHD_Daemon *restrict d) { struct MHD_Connection *c; mhd_assert (! mhd_D_HAS_WORKERS (d)); @@ -264,10 +263,11 @@ poll_update_fds (struct MHD_Daemon *restrict d, { unsigned short events; /* 'unsigned' for correct bits manipulations */ mhd_assert ((i_c - i_s) < d->conns.cfg.count_limit); + mhd_assert (MHD_CONNECTION_CLOSED != c->state); d->events.data.poll.fds[i_c].fd = c->socket_fd; d->events.data.poll.rel[i_c].connection = c; - events = POLLHUP; /* Actually, not needed and should be ignored in 'events' */ + events = 0; if (0 != (c->event_loop_info & MHD_EVENT_LOOP_INFO_READ)) events |= MHD_POLL_IN; if (0 != (c->event_loop_info & MHD_EVENT_LOOP_INFO_WRITE)) @@ -506,8 +506,8 @@ process_all_events_and_data (struct MHD_Daemon *restrict d) else if (! d->net.listen.non_block) d->events.act_req.accept = false; } - daemon_process_all_act_coons (d); - return false; + daemon_process_all_act_conns (d); + return ! d->threading.stop_requested; } @@ -519,6 +519,7 @@ mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC mhd_worker_all_events (void *cls) { struct MHD_Daemon *const restrict d = (struct MHD_Daemon *) cls; + mhd_thread_handle_ID_set_current_thread_ID (&(d->threading.tid)); mhd_assert (d->dbg.net_inited); mhd_assert (! d->dbg.net_deinited); mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); @@ -584,6 +585,8 @@ mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC mhd_worker_listening_only (void *cls) { struct MHD_Daemon *const restrict d = (struct MHD_Daemon *) cls; + mhd_thread_handle_ID_set_current_thread_ID (&(d->threading.tid)); + mhd_assert (d->dbg.net_inited); mhd_assert (! d->dbg.net_deinited); mhd_assert (mhd_DAEMON_TYPE_LISTEN_ONLY == d->threading.d_type); @@ -599,8 +602,8 @@ mhd_worker_listening_only (void *cls) if (! d->threading.stop_requested) { mhd_LOG_MSG (d, MHD_SC_DAEMON_THREAD_STOP_UNEXPECTED, \ - "The daemon thread is stopping, but termination has not been " \ - "requested by the daemon."); + "The daemon thread is stopping, but termination has " \ + "not been requested by the daemon."); } return (mhd_THRD_RTRN_TYPE) 0; } diff --git a/src/mhd2/http_status_str.c b/src/mhd2/http_status_str.c index db49d120..f00c46e7 100644 --- a/src/mhd2/http_status_str.c +++ b/src/mhd2/http_status_str.c @@ -161,7 +161,7 @@ static const struct MHD_String five_hundred[] = { struct mhd_HttpStatusesBlock { - size_t max; + size_t num_elmnts; const struct MHD_String *const data; }; @@ -185,7 +185,7 @@ MHD_HTTP_status_code_to_string (enum MHD_HTTP_StatusCode code) return NULL; if (600 < code) return NULL; - if (statuses[code_i / 100].max > (code_i % 100)) + if (statuses[code_i / 100].num_elmnts <= (code_i % 100)) return NULL; res = statuses[code_i / 100].data + (code_i % 100); if (NULL == res->cstr) diff --git a/src/mhd2/mhd_assert.h b/src/mhd2/mhd_assert.h index 4c3c1d6c..0a6b7248 100644 --- a/src/mhd2/mhd_assert.h +++ b/src/mhd2/mhd_assert.h @@ -78,4 +78,11 @@ # endif /* ! HAVE_ASSERT */ #endif /* NDEBUG */ +#ifdef _DEBUG +# ifdef MHD_UNREACHABLE_ +# undef MHD_UNREACHABLE_ +# endif +# define MHD_UNREACHABLE_ ((void) 0) +#endif + #endif /* ! MHD_ASSERT_H */ diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h index 03d2ba64..0b04e9b8 100644 --- a/src/mhd2/mhd_connection.h +++ b/src/mhd2/mhd_connection.h @@ -335,6 +335,11 @@ enum MHD_FIXED_ENUM_ MHD_CONNECTION_STATE }; +struct mhd_ConnDebugData +{ + bool pre_closed; + bool pre_cleaned; +}; /** * Ability to use same connection for next request @@ -603,6 +608,13 @@ struct MHD_Connection * What is this connection waiting for? */ enum MHD_ConnectionEventLoopInfo event_loop_info; + +#ifndef NDEBUG + /** + * Debugging data + */ + struct mhd_ConnDebugData dbg; +#endif }; diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h index a99e0af7..2f8c75cf 100644 --- a/src/mhd2/mhd_daemon.h +++ b/src/mhd2/mhd_daemon.h @@ -741,10 +741,15 @@ struct mhd_DaemonConnections mhd_DLNKDL_LIST (MHD_Connection,all_conn); /** - * The list of all daemon's connections + * The list of connections sorted by last activity */ mhd_DLNKDL_LIST (MHD_Connection,def_timeout); + /** + * The list of connections with custom timeouts + */ + mhd_DLNKDL_LIST (MHD_Connection,cust_timeout); + /** * The list of all daemon's connections */ diff --git a/src/mhd2/mhd_request.h b/src/mhd2/mhd_request.h index cb608941..c6aa7f34 100644 --- a/src/mhd2/mhd_request.h +++ b/src/mhd2/mhd_request.h @@ -339,23 +339,6 @@ struct MHD_Request */ uint_fast64_t current_chunk_offset; - /** - * Indicate that some of the upload payload data (from the currently - * processed chunk for chunked uploads) have been processed by the - * last call of the connection handler. - * If any data have been processed, but some data left in the buffer - * for further processing, then MHD will use zero timeout before the - * next data processing round. This allow the application handler - * process the data by the fixed portions or other way suitable for - * application developer. - * If no data have been processed, than MHD will wait for more data - * to come (as it makes no sense to call the same connection handler - * under the same conditions). However this is dangerous as if buffer - * is completely used then connection is aborted. Connection - * suspension should be used in such case. - */ - bool some_payload_processed; - /** * We allow the main application to associate some pointer with the * HTTP request, which is passed to each #MHD_AccessHandlerCallback diff --git a/src/mhd2/mhd_response.h b/src/mhd2/mhd_response.h index 78b27aca..5a040faa 100644 --- a/src/mhd2/mhd_response.h +++ b/src/mhd2/mhd_response.h @@ -266,6 +266,23 @@ struct mhd_ResponseConfiguration bool int_err_resp; }; +/** + * Special data for internal error responses + */ +struct mhd_ResponseInternalErrData +{ + /** + * The length of the @a spec_hdr + */ + size_t spec_hdr_len; + /** + * The special header string. + * The final CRLF is not included. + * Must be deallocated if not NULL. + */ + char *spec_hdr; +}; + #ifndef NDEBUG struct mhd_ResponseDebug { @@ -332,7 +349,12 @@ struct MHD_Response */ mhd_DLNKDL_LIST (mhd_ResponseHeader,headers); -#ifndef NDEBUG + /** + * Special data for internal error responses + */ + struct mhd_ResponseInternalErrData special_resp; + + #ifndef NDEBUG struct mhd_ResponseDebug dbg; #endif }; diff --git a/src/mhd2/mhd_socket_error.h b/src/mhd2/mhd_socket_error.h index 8e55de1b..8d36208d 100644 --- a/src/mhd2/mhd_socket_error.h +++ b/src/mhd2/mhd_socket_error.h @@ -57,6 +57,11 @@ enum MHD_FIXED_ENUM_ mhd_SocketError */ mhd_SOCKET_ERR_NOMEM , + /** + * The connection has been gracefully closed by remote peer + */ + mhd_SOCKET_ERR_REMT_DISCONN + , /** * The connection has been hard-closed by remote peer. */ @@ -128,7 +133,7 @@ enum MHD_FIXED_ENUM_ mhd_SocketError /** * Check whether the socket error is unrecoverable */ -#define mhd_SOCKET_ERR_IS_HARD(err) (mhd_SOCKET_ERR_CONNRESET <= (err)) +#define mhd_SOCKET_ERR_IS_HARD(err) (mhd_SOCKET_ERR_REMT_DISCONN <= (err)) /** * Check whether the socket error is unexpected diff --git a/src/mhd2/respond_with_error.c b/src/mhd2/respond_with_error.c new file mode 100644 index 00000000..91b74bae --- /dev/null +++ b/src/mhd2/respond_with_error.c @@ -0,0 +1,123 @@ +/* + 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/respond_with_error.c + * @brief The implementation of error response functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "respond_with_error.h" + +#include "sys_base_types.h" +#include "sys_null_macro.h" +#include "mhd_str_macros.h" + +#include "sys_malloc.h" + +#include "mhd_connection.h" + +#include "mhd_mempool.h" + +#include "response_from.h" +#include "daemon_logger.h" +#include "response_destroy.h" +#include "stream_funcs.h" +#include "daemon_funcs.h" + +#include "mhd_public_api.h" + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) void +respond_with_error_len (struct MHD_Connection *c, + unsigned int http_code, + size_t msg_len, + const char *msg, + size_t add_hdr_line_len, + char *add_hdr_line) +{ + struct MHD_Response *err_res; + + mhd_assert (! c->stop_with_error); /* Do not send error twice */ + mhd_assert (MHD_CONNECTION_REQ_RECV_FINISHED >= c->state); + + /* Discard most of the request data */ + + if (NULL != c->rq.cntn.lbuf.buf) + mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); + c->rq.cntn.lbuf.buf = NULL; + + c->write_buffer = NULL; + c->write_buffer_size = 0; + c->write_buffer_send_offset = 0; + c->write_buffer_append_offset = 0; + + mhd_DLINKEDL_INIT_LIST (&(c->rq), fields); + c->rq.version = NULL; + c->rq.method = NULL; + c->rq.url = NULL; + c->continue_message_write_offset = 0; + if (0 != c->read_buffer_size) + { + mhd_pool_deallocate (c->pool, + c->read_buffer, + c->read_buffer_size); + c->read_buffer = NULL; + c->read_buffer_size = 0; + c->read_buffer_offset = 0; + } + + c->stop_with_error = true; + c->discard_request = true; + if ((MHD_HTTP_STATUS_CONTENT_TOO_LARGE == http_code) || + (MHD_HTTP_STATUS_URI_TOO_LONG == http_code) || + (MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE == http_code)) + c->rq.too_large = true; + + mhd_LOG_PRINT (c->daemon, + MHD_SC_REQ_PROCCESSING_ERR_REPLY, + mhd_LOG_FMT ("Error processing request. Sending %u " \ + "error reply: %s"), + (unsigned int) http_code, msg); + + if (NULL != c->rp.response) + { + mhd_response_dec_use_count (c->rp.response); + c->rp.response = NULL; + } + err_res = mhd_response_special_for_error (http_code, + msg_len, + msg, + add_hdr_line_len, + add_hdr_line); + if (NULL == err_res) + { + if (NULL != add_hdr_line) + free (add_hdr_line); + mhd_STREAM_ABORT (c, \ + mhd_CONN_CLOSE_NO_MEM_FOR_ERR_RESPONSE, \ + "No memory to create error response."); + return; + } + c->rp.response = err_res; + c->state = MHD_CONNECTION_START_REPLY; +} diff --git a/src/mhd2/respond_with_error.h b/src/mhd2/respond_with_error.h index 3dc8930b..8eecc4f1 100644 --- a/src/mhd2/respond_with_error.h +++ b/src/mhd2/respond_with_error.h @@ -34,17 +34,28 @@ struct MHD_Connection; /* forward declaration */ +/** + * Respond with provided error response. + * Current request will be aborted, stream will be closed after sending + * error response. + * @param c the connection to use + * @param http_code the reply HTTP status code + * @param msg_len the length of the @a msg + * @param msg the reply content, could be NULL + * @param add_hdr_line_len the length the @a add_hdr_line + * @param add_hdr_line the additional special header line, could be NULL, + * if not NULL it will be deallocated by free(). + * + */ MHD_INTERNAL void respond_with_error_len (struct MHD_Connection *c, unsigned int http_code, size_t msg_len, const char *msg, - size_t add_hdr_name_len, - const char *add_hdr_name, - size_t add_hdr_value_len, - const char *add_hdr_value) + size_t add_hdr_line_len, + char *add_hdr_line) MHD_FN_PAR_NONNULL_ (1) -MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) MHD_FN_PAR_CSTR_ (8); +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6); #ifdef HAVE_HTTP_AUTO_MESSAGES_BODIES /** @@ -53,16 +64,15 @@ MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) MHD_FN_PAR_CSTR_ (8); # define mhd_RESPOND_WITH_ERROR_STATIC(c, code, msg) \ respond_with_error_len ((c), (code), \ mhd_SSTR_LEN (msg), (msg), \ - 0, NULL, 0, NULL) + 0, NULL) /** * Transmit static string as error response and add specified header */ -# define mhd_RESPOND_WITH_ERROR_HEADER(c,code,m,hd_n_l,hd_n,hd_v_l,hd_v) \ +# define mhd_RESPOND_WITH_ERROR_HEADER(c,code,m,hdrl_l,hdrl) \ respond_with_error_len ((c), (code), \ mhd_SSTR_LEN (m), (m), \ - (hd_n_l), (hd_n), \ - (hd_v_l), (hd_v)) + (hdrl_l), (hdrl)) #else /** @@ -71,16 +81,15 @@ MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) MHD_FN_PAR_CSTR_ (8); # define mhd_RESPOND_WITH_ERROR_STATIC(c, code, msg) \ respond_with_error_len ((c), (code), \ 0, NULL, \ - 0, NULL, 0, NULL) + 0, NULL) /** * Transmit static string as error response and add specified header */ -# define mhd_RESPOND_WITH_ERROR_HEADER(c,code,m,hd_n_l,hd_n,hd_v_l,hd_v) \ +# define mhd_RESPOND_WITH_ERROR_HEADER(c,code,m,hdrl_l,hdrl) \ respond_with_error_len ((c), (code), \ 0, NULL, \ - (hd_n_l), (hd_n), \ - (hd_v_l), (hd_v)) + (hdrl_l), (hdrl)) #endif #endif /* ! MHD_RESPOND_WITH_ERROR_H */ diff --git a/src/mhd2/response_destroy.c b/src/mhd2/response_destroy.c index c5fe4505..94048cc7 100644 --- a/src/mhd2/response_destroy.c +++ b/src/mhd2/response_destroy.c @@ -49,6 +49,8 @@ static MHD_FN_PAR_NONNULL_ (1) void response_full_detinit (struct MHD_Response *restrict r) { mhd_response_remove_all_headers (r); + if (NULL != r->special_resp.spec_hdr) + free (r->special_resp.spec_hdr); if (r->reuse.reusable) mhd_response_deinit_reusable (r); mhd_response_deinit_content_data (r); diff --git a/src/mhd2/response_from.c b/src/mhd2/response_from.c index 24007b2f..9f0ca3c5 100644 --- a/src/mhd2/response_from.c +++ b/src/mhd2/response_from.c @@ -68,6 +68,9 @@ response_create_basic (enum MHD_HTTP_StatusCode sc, { #ifndef HAVE_NULL_PTR_ALL_ZEROS mhd_DLINKEDL_INIT_LIST (r, headers); + r->free.cb = NULL; + r->free.cls = NULL; + r->special_resp.spec_hdr = NULL; s->termination_callback.v_term_cb = NULL; s->termination_callback.v_term_cb_cls = NULL; @@ -353,3 +356,52 @@ MHD_response_from_pipe (enum MHD_HTTP_StatusCode sc, return res; } + + +/** + * Create special internal response for sending error reply + * @param sc the HTTP status code + * @param cntn_len the length of the @a cntn + * @param cntn the content of the response, could be NULL + * @param spec_hdr_len the length of the @a spec_hdr + * @param spec_hdr the special header line, without last CRLF, + * if not NULL it will be deallocated by free(). + * @return + */ +MHD_INTERNAL +MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (5) struct MHD_Response * +mhd_response_special_for_error (unsigned int sc, + size_t cntn_len, + const char *cntn, + size_t spec_hdr_len, + char *spec_hdr) +{ + struct MHD_Response *restrict res; + + mhd_assert (100 <= sc); + mhd_assert (600 > sc); + mhd_assert ((NULL != cntn) || (0 == cntn_len)); + mhd_assert ((NULL != spec_hdr) || (0 == spec_hdr_len)); + + res = mhd_calloc (1, sizeof(struct MHD_Response)); + if (NULL == res) + return NULL; + +#ifndef HAVE_NULL_PTR_ALL_ZEROS + mhd_DLINKEDL_INIT_LIST (res, headers); + res->free.cb = NULL; + res->free.cls = NULL; + res->special_resp.spec_hdr = NULL; +#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ + res->sc = (enum MHD_HTTP_StatusCode) sc; + res->cntn_size = cntn_len; + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_BUFFER; + res->cntn.buf = (const unsigned char *) ((0 != cntn_len) ? cntn : ""); + res->cfg.close_forced = true; + res->cfg.int_err_resp = true; + res->special_resp.spec_hdr_len = spec_hdr_len; + res->special_resp.spec_hdr = spec_hdr; + res->frozen = true; + + return res; +} diff --git a/src/mhd2/response_from.h b/src/mhd2/response_from.h index e4a7fe09..ca964832 100644 --- a/src/mhd2/response_from.h +++ b/src/mhd2/response_from.h @@ -29,6 +29,7 @@ #define MHD_RESPONSE_FROM_H 1 #include "mhd_sys_options.h" +#include "sys_base_types.h" struct MHD_Response; /* forward declaration */ @@ -40,5 +41,13 @@ MHD_INTERNAL void mhd_response_deinit_content_data (struct MHD_Response *restrict r) MHD_FN_PAR_NONNULL_ (1); +MHD_INTERNAL struct MHD_Response * +mhd_response_special_for_error (unsigned int sc, + size_t cntn_len, + const char *cntn, + size_t spec_hdr_len, + char *spec_hdr) +MHD_FN_PAR_CSTR_(3) MHD_FN_PAR_CSTR_(5); + #endif /* ! MHD_RESPONSE_FROM_H */ diff --git a/src/mhd2/response_funcs.c b/src/mhd2/response_funcs.c index 30bf1ea5..c097e3eb 100644 --- a/src/mhd2/response_funcs.c +++ b/src/mhd2/response_funcs.c @@ -76,7 +76,6 @@ response_set_properties (struct MHD_Response *restrict r) { struct ResponseOptions *restrict const s = r->settings; mhd_assert (NULL != s); - r->frozen = true; r->cfg.head_only = s->head_only_response; if (s->http_1_0_compatible_strict) @@ -102,6 +101,8 @@ response_set_properties (struct MHD_Response *restrict r) // TODO: calculate size of the headers and the "Connection:" header + r->frozen = true; + r->settings = NULL; free (s); } diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c index 87a08111..6c72ff90 100644 --- a/src/mhd2/stream_funcs.c +++ b/src/mhd2/stream_funcs.c @@ -32,6 +32,7 @@ #include "mhd_daemon.h" #include "mhd_connection.h" +#include "mhd_response.h" #include "mhd_assert.h" #include "mhd_mempool.h" #include "mhd_str.h" @@ -43,6 +44,7 @@ #include "response_destroy.h" #include "mhd_mono_clock.h" #include "daemon_logger.h" +#include "daemon_funcs.h" #include "mhd_public_api.h" @@ -275,6 +277,8 @@ mhd_stream_get_no_space_err_status_code (struct MHD_Connection *restrict c, mhd_assert (MHD_PROC_RECV_HEADERS <= stage); mhd_assert ((0 == add_element_size) || (NULL != add_element)); + c->rq.too_large = true; + if (MHD_CONNECTION_HEADERS_RECEIVED > c->state) { mhd_assert (NULL != c->rq.field_lines.start); @@ -484,16 +488,15 @@ mhd_stream_finish_req_serving (struct MHD_Connection *restrict c, if (! reuse) { + mhd_assert (! c->stop_with_error || (NULL == c->rp.response) || \ + (c->rp.response->cfg.int_err_resp)); /* Next function will destroy response, notify client, - * destroy memory pool, and set connection state to "CLOSED" */ - mhd_conn_pre_close_req_finished (c); - c->read_buffer = NULL; - c->read_buffer_size = 0; - c->read_buffer_offset = 0; - c->write_buffer = NULL; - c->write_buffer_size = 0; - c->write_buffer_send_offset = 0; - c->write_buffer_append_offset = 0; + * destroy memory pool and set connection state to "CLOSED" */ + mhd_conn_pre_close (c, + c->stop_with_error ? + mhd_CONN_CLOSE_ERR_REPLY_SENT : + mhd_CONN_CLOSE_HTTP_COMPLETED, + NULL); } else { @@ -501,6 +504,7 @@ mhd_stream_finish_req_serving (struct MHD_Connection *restrict c, size_t new_read_buf_size; mhd_assert (! c->stop_with_error); mhd_assert (! c->discard_request); + mhd_assert (NULL == c->rq.cntn.lbuf.buf); #if 0 // TODO: notification callback if ( (NULL != d->notify_completed) && @@ -635,6 +639,7 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, const char *log_msg) { bool close_hard; + bool use_local_lingering; enum MHD_RequestTerminationCode term_code; enum MHD_StatusCode sc; @@ -657,7 +662,17 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, break; case mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY: close_hard = true; - term_code = MHD_REQUEST_TERMINATED_NO_RESOURCES; + term_code = (! c->stop_with_error || c->rq.too_large) ? + MHD_REQUEST_TERMINATED_NO_RESOURCES : + MHD_REQUEST_TERMINATED_HTTP_PROTOCOL_ERROR; + sc = MHD_SC_REPLY_POOL_ALLOCATION_FAILURE; + break; + case mhd_CONN_CLOSE_NO_MEM_FOR_ERR_RESPONSE: + close_hard = true; + term_code = c->rq.too_large ? + MHD_REQUEST_TERMINATED_NO_RESOURCES : + MHD_REQUEST_TERMINATED_HTTP_PROTOCOL_ERROR; + sc = MHD_SC_ERR_RESPONSE_ALLOCATION_FAILURE; break; case mhd_CONN_CLOSE_APP_ERROR: close_hard = true; @@ -680,6 +695,10 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, case mhd_SOCKET_ERR_NOMEM: term_code = MHD_REQUEST_TERMINATED_NO_RESOURCES; break; + case mhd_SOCKET_ERR_REMT_DISCONN: + close_hard = false; + term_code = MHD_REQUEST_TERMINATED_CLIENT_ABORT; + break; case mhd_SOCKET_ERR_CONNRESET: term_code = MHD_REQUEST_TERMINATED_CLIENT_ABORT; break; @@ -716,7 +735,7 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, term_code = MHD_REQUEST_TERMINATED_TIMEOUT_REACHED; break; - case mhd_CONN_CLOSE_CLIENT_HTTP_ERR_SENT_ERR: + case mhd_CONN_CLOSE_ERR_REPLY_SENT: close_hard = false; term_code = c->rq.too_large ? MHD_REQUEST_TERMINATED_NO_RESOURCES : @@ -736,6 +755,7 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, mhd_assert ((NULL == log_msg) || (MHD_SC_INTERNAL_ERROR != sc)); + use_local_lingering = false; /* Make changes on the socket early to let the kernel and the remote * to process the changes in parallel. */ if (close_hard) @@ -752,6 +772,7 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, (void) 0; // TODO: start local lingering phase c->state = MHD_CONNECTION_CLOSED; // TODO: start local lingering phase c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; // TODO: start local lingering phase + // use_local_lingering = true; } else { /* No need / not possible to linger */ @@ -780,6 +801,38 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, (void) term_code; #endif + if (! mhd_D_HAS_THR_PER_CONN (c->daemon)) + { + if (c->connection_timeout_ms == c->daemon->conns.cfg.timeout) + mhd_DLINKEDL_DEL_D (&(c->daemon->conns.def_timeout), \ + c, by_timeout); + else + mhd_DLINKEDL_DEL_D (&(c->daemon->conns.cust_timeout), \ + c, by_timeout); + } + +#ifndef NDEBUG + c->dbg.pre_closed = true; +#endif + + if (! use_local_lingering) + mhd_conn_pre_clean (c); +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) void +mhd_conn_pre_clean (struct MHD_Connection *restrict c) +{ + // TODO: support suspended connections + if ((NULL != mhd_DLINKEDL_GET_NEXT (c, proc_ready)) || + (NULL != mhd_DLINKEDL_GET_PREV (c, proc_ready)) || + (c == mhd_DLINKEDL_GET_FIRST (&(c->daemon->events), proc_ready))) + mhd_DLINKEDL_DEL (&(c->daemon->events), c, proc_ready); + + if (NULL != c->rq.cntn.lbuf.buf) + mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); + c->rq.cntn.lbuf.buf = NULL; if (NULL != c->rp.response) mhd_response_dec_use_count (c->rp.response); c->rp.response = NULL; @@ -792,6 +845,30 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, c->write_buffer_append_offset = 0; c->write_buffer_size = 0; c->write_buffer = NULL; + // TODO: call in the thread where it was allocated for thread-per-connection mhd_pool_destroy (c->pool); c->pool = NULL; + +#ifdef MHD_USE_EPOLL + if (mhd_POLL_TYPE_EPOLL == daemon->events.poll_type) + { + struct epoll_event event; + + event.events = 0; + event.data.ptr = NULL; + if (0 != epoll_ctl (c->daemon->events.data.epoll.e_fd, + EPOLL_CTL_DEL, + c->socket_fd, + &event)) + { + mhd_LOG_MSG (daemon, MHD_SC_EPOLL_CTL_REMOVE_FAILED, + "Failed to remove connection socket from epoll."); + } + } +#endif /* MHD_USE_EPOLL */ + + +#ifndef NDEBUG + c->dbg.pre_cleaned = true; +#endif } diff --git a/src/mhd2/stream_funcs.h b/src/mhd2/stream_funcs.h index cc36670b..53d9bbcf 100644 --- a/src/mhd2/stream_funcs.h +++ b/src/mhd2/stream_funcs.h @@ -174,6 +174,11 @@ enum mhd_ConnCloseReason */ mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY , + /** + * No memory to create error response + */ + mhd_CONN_CLOSE_NO_MEM_FOR_ERR_RESPONSE + , /** * Application behaves incorrectly */ @@ -211,7 +216,7 @@ enum mhd_ConnCloseReason * The connection must be closed after error response as the client * violates HTTP specification */ - mhd_CONN_CLOSE_CLIENT_HTTP_ERR_SENT_ERR + mhd_CONN_CLOSE_ERR_REPLY_SENT , /* Graceful closing */ @@ -224,9 +229,10 @@ enum mhd_ConnCloseReason /** - * Perform initial clean-up and mark for closing. - * Set the reason to "request finished" + * Prepare connection for closing. * @param c the connection for pre-closing + * @param reason the reason for closing + * @param log_msg the message for the log */ MHD_INTERNAL void mhd_conn_pre_close (struct MHD_Connection *restrict c, @@ -249,7 +255,7 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * @param c the connection for pre-closing */ #define mhd_conn_pre_close_app_abort(c) \ - mhd_conn_pre_close (c, mhd_CONN_CLOSE_APP_ABORTED, NULL) + mhd_conn_pre_close ((c), mhd_CONN_CLOSE_APP_ABORTED, NULL) /** * Perform initial clean-up and mark for closing. @@ -257,7 +263,7 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * @param c the connection for pre-closing */ #define mhd_conn_pre_close_skt_err(c) \ - mhd_conn_pre_close (c, mhd_CONN_CLOSE_SOCKET_ERR, NULL) + mhd_conn_pre_close ((c), mhd_CONN_CLOSE_SOCKET_ERR, NULL) /** * Perform initial clean-up and mark for closing. @@ -265,7 +271,7 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * @param c the connection for pre-closing */ #define mhd_conn_pre_close_req_finished(c) \ - mhd_conn_pre_close (c, mhd_CONN_CLOSE_HTTP_COMPLETED, NULL) + mhd_conn_pre_close ((c), mhd_CONN_CLOSE_HTTP_COMPLETED, NULL) /** * Perform initial clean-up and mark for closing. @@ -273,7 +279,15 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); * @param c the connection for pre-closing */ #define mhd_conn_pre_close_timedout(c) \ - mhd_conn_pre_close (c, mhd_CONN_CLOSE_TIMEDOUT, NULL) + mhd_conn_pre_close ((c), mhd_CONN_CLOSE_TIMEDOUT, NULL) +/** + * Perform initial connection cleanup. + * The connection must be prepared for closing. + * @param c the connection for pre-closing + */ +MHD_INTERNAL void +mhd_conn_pre_clean (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ (1); #endif /* ! MHD_STREAM_FUNCS_H */ diff --git a/src/mhd2/stream_process_reply.c b/src/mhd2/stream_process_reply.c index 3a0dacb1..4c00cd57 100644 --- a/src/mhd2/stream_process_reply.c +++ b/src/mhd2/stream_process_reply.c @@ -749,6 +749,19 @@ build_header_response_inn (struct MHD_Connection *restrict c) /* * The headers * */ + /* A special custom header */ + if (0 != r->special_resp.spec_hdr_len) + { + mhd_assert (r->cfg.int_err_resp); + if (buf_size < pos + r->special_resp.spec_hdr_len + 2) + return false; + memcpy (buf + pos, + r->special_resp.spec_hdr, + r->special_resp.spec_hdr_len); + buf[pos++] = '\r'; + buf[pos++] = '\n'; + } + /* Main automatic headers */ /* The "Date:" header */ @@ -809,8 +822,8 @@ build_header_response_inn (struct MHD_Connection *restrict c) (! r->cfg.chunked) && (! r->cfg.head_only)) { /* The size is known and can be indicated by the header */ - if (r->cfg.cnt_len_by_app) - { /* The response does not have "Content-Length" header */ + if (! r->cfg.cnt_len_by_app) + { /* The response does not have app-defined "Content-Length" header */ if (! buffer_append_s (buf, &pos, buf_size, MHD_HTTP_HEADER_CONTENT_LENGTH ": ")) return false; diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c index ee4d8734..b0976445 100644 --- a/src/mhd2/stream_process_request.c +++ b/src/mhd2/stream_process_request.c @@ -45,18 +45,22 @@ #include "mhd_daemon.h" #include "mhd_connection.h" -#include "mhd_public_api.h" #include "daemon_logger.h" #include "mhd_assert.h" #include "mhd_panic.h" +#include "mhd_mempool.h" + #include "request_funcs.h" #include "request_get_value.h" #include "respond_with_error.h" #include "stream_funcs.h" #include "daemon_funcs.h" +#include "mhd_public_api.h" + + /** * Response text used when the request (http header) is * malformed. @@ -294,6 +298,35 @@ "Request HTTP header is too big for the memory constraints " \ "of this webserver." \ "" +/** + * Response text used when the request chunk size line with chunk extension + * cannot fit the buffer. + */ +#define ERR_RSP_REQUEST_CHUNK_LINE_EXT_TOO_BIG \ + "" \ + "Request too big" \ + "

The total size of the request target, the request field lines " \ + "and the chunk size line exceeds the memory constraints of this web " \ + "server.

" \ + "

The request could be re-tried without chunk extensions, with a smaller " \ + "chunk size, shorter field lines, a shorter request target or a shorter " \ + "request method token.

" \ + "" + +/** + * Response text used when the request chunk size line without chunk extension + * cannot fit the buffer. + */ +#define ERR_RSP_REQUEST_CHUNK_LINE_TOO_BIG \ + "" \ + "Request too big" \ + "

The total size of the request target, the request field lines " \ + "and the chunk size line exceeds the memory constraints of this web " \ + "server.

" \ + "

The request could be re-tried with a smaller " \ + "chunk size, shorter field lines, a shorter request target or a shorter " \ + "request method token.

" \ + "" /** * Response text used when the request (http header) does not @@ -378,6 +411,17 @@ "The chunk size used in HTTP chunked encoded " \ "request is too large." + +/** + * The reasonable length of the upload chunk "header" (the size specifier + * with optional chunk extension). + * MHD tries to keep the space in the read buffer large enough to read + * the chunk "header" in one step. + * The real "header" could be much larger, it will be handled correctly + * anyway, however it may require several rounds of buffer grow. + */ +#define MHD_CHUNK_HEADER_REASONABLE_LEN 24 + /** * Get whether bare LF in HTTP header and other protocol elements * should be treated as the line termination depending on the configured @@ -1229,7 +1273,10 @@ process_request_target (struct MHD_Connection *c) mhd_RESPOND_WITH_ERROR_STATIC ( c, - mhd_stream_get_no_space_err_status_code (c,MHD_PROC_RECV_URI, 0, NULL), + mhd_stream_get_no_space_err_status_code (c, + MHD_PROC_RECV_URI, + 0, + NULL), ERR_RSP_MSG_REQUEST_TOO_BIG); mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING != c->state); return false; @@ -1273,12 +1320,14 @@ process_request_target (struct MHD_Connection *c) static void send_redirect_fixed_rq_target (struct MHD_Connection *restrict c) { + static const char hdr_prefix[] = MHD_HTTP_HEADER_LOCATION ": "; + static const size_t hdr_prefix_len = + mhd_SSTR_LEN (MHD_HTTP_HEADER_LOCATION ": "); + char *hdr_line; char *b; size_t fixed_uri_len; size_t i; size_t o; - char *hdr_name; - size_t hdr_name_len; mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); mhd_assert (0 != c->rq.hdrs.rq_line.num_ws_in_uri); @@ -1288,7 +1337,7 @@ send_redirect_fixed_rq_target (struct MHD_Connection *restrict c) + 2 * c->rq.hdrs.rq_line.num_ws_in_uri; if ( (fixed_uri_len + 200 > c->daemon->conns.cfg.mem_pool_size) || (fixed_uri_len > MHD_MAX_FIXED_URI_LEN) || - (NULL == (b = malloc (fixed_uri_len + 1))) ) + (NULL == (hdr_line = malloc (fixed_uri_len + 1 + hdr_prefix_len))) ) { mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, \ "The request has whitespace character is " \ @@ -1296,6 +1345,8 @@ send_redirect_fixed_rq_target (struct MHD_Connection *restrict c) "send automatic redirect to fixed URI."); return; } + memcpy (hdr_line, hdr_prefix, hdr_prefix_len); + b = hdr_line + hdr_prefix_len; i = 0; o = 0; @@ -1336,26 +1387,12 @@ send_redirect_fixed_rq_target (struct MHD_Connection *restrict c) mhd_assert (fixed_uri_len == o); b[o] = 0; /* Zero-terminate the result */ - hdr_name_len = mhd_SSTR_LEN (MHD_HTTP_HEADER_LOCATION); - hdr_name = malloc (hdr_name_len + 1); - if (NULL != hdr_name) - { - memcpy (hdr_name, - MHD_HTTP_HEADER_LOCATION, - hdr_name_len + 1); - /* hdr_name and b are free()d within this call */ - mhd_RESPOND_WITH_ERROR_HEADER (c, - MHD_HTTP_STATUS_MOVED_PERMANENTLY, - ERR_RSP_RQ_TARGET_INVALID_CHAR, - hdr_name_len, - hdr_name, - o, - b); - return; - } - free (b); - mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, - "The request has whitespace character is in the URI."); + mhd_RESPOND_WITH_ERROR_HEADER (c, + MHD_HTTP_STATUS_MOVED_PERMANENTLY, + ERR_RSP_RQ_TARGET_INVALID_CHAR, + o + hdr_prefix_len, + hdr_line); + return; } @@ -3410,9 +3447,386 @@ mhd_stream_process_req_recv_finished (struct MHD_Connection *restrict c) { if (NULL != c->rq.cntn.lbuf.buf) mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); + c->rq.cntn.lbuf.buf = NULL; if (c->rq.cntn.cntn_size != c->rq.cntn.proc_size) c->discard_request = true; mhd_assert (NULL != c->rp.response); c->state = MHD_CONNECTION_START_REPLY; return true; } + + +/** + * Send error reply when receive buffer space exhausted while receiving + * the chunk size line. + * @param c the connection to handle + * @param add_header the optional pointer to the partially received + * the current chunk size line. + * Could be not zero-terminated and can contain binary zeros. + * Can be NULL. + * @param add_header_size the size of the @a add_header + */ +static void +handle_req_chunk_size_line_no_space (struct MHD_Connection *c, + const char *chunk_size_line, + size_t chunk_size_line_size) +{ + unsigned int err_code; + + if (NULL != chunk_size_line) + { + const char *semicol; + /* Check for chunk extension */ + semicol = memchr (chunk_size_line, ';', chunk_size_line_size); + if (NULL != semicol) + { /* Chunk extension present. It could be removed without any loss of the + details of the request. */ + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + ERR_RSP_REQUEST_CHUNK_LINE_EXT_TOO_BIG); + } + } + err_code = mhd_stream_get_no_space_err_status_code (c, + MHD_PROC_RECV_BODY_CHUNKED, + chunk_size_line_size, + chunk_size_line); + mhd_RESPOND_WITH_ERROR_STATIC (c, + err_code, + ERR_RSP_REQUEST_CHUNK_LINE_TOO_BIG); +} + + +/** + * Handle situation with read buffer exhaustion. + * Must be called when no more space left in the read buffer, no more + * space left in the memory pool to grow the read buffer, but more data + * need to be received from the client. + * Could be called when the result of received data processing cannot be + * stored in the memory pool (like some header). + * @param c the connection to process + * @param stage the receive stage where the exhaustion happens. + */ +static MHD_FN_PAR_NONNULL_ALL_ void +handle_recv_no_space (struct MHD_Connection *c, + enum MHD_ProcRecvDataStage stage) +{ + mhd_assert (MHD_PROC_RECV_INIT <= stage); + mhd_assert (MHD_PROC_RECV_FOOTERS >= stage); + mhd_assert (MHD_CONNECTION_FULL_REQ_RECEIVED > c->state); + mhd_assert ((MHD_PROC_RECV_INIT != stage) || \ + (MHD_CONNECTION_INIT == c->state)); + mhd_assert ((MHD_PROC_RECV_METHOD != stage) || \ + (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state)); + mhd_assert ((MHD_PROC_RECV_URI != stage) || \ + (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state)); + mhd_assert ((MHD_PROC_RECV_HTTPVER != stage) || \ + (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state)); + mhd_assert ((MHD_PROC_RECV_HEADERS != stage) || \ + (MHD_CONNECTION_REQ_HEADERS_RECEIVING == c->state)); + mhd_assert (MHD_PROC_RECV_COOKIE != stage); /* handle_req_cookie_no_space() must be called directly */ + mhd_assert ((MHD_PROC_RECV_BODY_NORMAL != stage) || \ + (MHD_CONNECTION_BODY_RECEIVING == c->state)); + mhd_assert ((MHD_PROC_RECV_BODY_CHUNKED != stage) || \ + (MHD_CONNECTION_BODY_RECEIVING == c->state)); + mhd_assert ((MHD_PROC_RECV_FOOTERS != stage) || \ + (MHD_CONNECTION_FOOTERS_RECEIVING == c->state)); + mhd_assert ((MHD_PROC_RECV_BODY_NORMAL != stage) || \ + (! c->rq.have_chunked_upload)); + mhd_assert ((MHD_PROC_RECV_BODY_CHUNKED != stage) || \ + (c->rq.have_chunked_upload)); + switch (stage) + { + case MHD_PROC_RECV_INIT: + case MHD_PROC_RECV_METHOD: + /* Some data has been received, but it is not clear yet whether + * the received data is an valid HTTP request */ + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REQUEST, \ + "No space left in the read buffer when " \ + "receiving the initial part of " \ + "the request line."); + return; + case MHD_PROC_RECV_URI: + case MHD_PROC_RECV_HTTPVER: + /* Some data has been received, but the request line is incomplete */ + mhd_assert (mhd_HTTP_METHOD_NO_METHOD != c->rq.http_mthd); + mhd_assert (MHD_HTTP_VERSION_INVALID == c->rq.http_ver); + /* A quick simple check whether the incomplete line looks + * like an HTTP request */ + if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) && + (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd)) + { + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_URI_TOO_LONG, + ERR_RSP_MSG_REQUEST_TOO_BIG); + return; + } + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REQUEST, \ + "No space left in the read buffer when " \ + "receiving the URI in " \ + "the request line. " \ + "The request uses non-standard HTTP request " \ + "method token."); + return; + case MHD_PROC_RECV_HEADERS: + handle_req_headers_no_space (c, c->read_buffer, c->read_buffer_offset); + return; + 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; + case MHD_PROC_RECV_BODY_CHUNKED: + mhd_assert (c->rq.have_chunked_upload); + if (c->rq.current_chunk_offset != c->rq.current_chunk_size) + { /* Receiving content of the chunk */ + /* A header probably has been added to a suspended connection and + it took precisely all the space in the buffer. + Very low probability. */ + handle_req_headers_no_space (c, NULL, 0); // FIXME: check + } + else + { + if (0 != c->rq.current_chunk_size) + { /* Waiting for chunk-closing CRLF */ + /* Not really possible as some payload should be + processed and the space used by payload should be available. */ + handle_req_headers_no_space (c, NULL, 0); // FIXME: check + } + else + { /* Reading the line with the chunk size */ + handle_req_chunk_size_line_no_space (c, + c->read_buffer, + c->read_buffer_offset); + } + } + return; + case MHD_PROC_RECV_FOOTERS: + handle_req_footers_no_space (c, c->read_buffer, c->read_buffer_offset); + return; + /* The next cases should not be possible */ + case MHD_PROC_RECV_COOKIE: + default: + break; + } + mhd_assert (0 && "Should be unreachable"); +} + + +/** + * Try growing the read buffer. We initially claim half the available + * buffer space for the read buffer (the other half being left for + * management data structures; the write buffer can in the end take + * virtually everything as the read buffer can be reduced to the + * minimum necessary at that point. + * + * @param connection the connection + * @param required set to 'true' if grow is required, i.e. connection + * will fail if no additional space is granted + * @return 'true' on success, 'false' on failure + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +try_grow_read_buffer (struct MHD_Connection *restrict connection, + bool required) +{ + size_t new_size; + size_t avail_size; + const size_t def_grow_size = 1536; // TODO: remove hardcoded increment + void *rb; + + avail_size = mhd_pool_get_free (connection->pool); + if (0 == avail_size) + return false; /* No more space available */ + if (0 == connection->read_buffer_size) + new_size = avail_size / 2; /* Use half of available buffer for reading */ + else + { + size_t grow_size; + + grow_size = avail_size / 8; + if (def_grow_size > grow_size) + { /* Shortage of space */ + const size_t left_free = + connection->read_buffer_size - connection->read_buffer_offset; + mhd_assert (connection->read_buffer_size >= \ + connection->read_buffer_offset); + if ((def_grow_size <= grow_size + left_free) + && (left_free < def_grow_size)) + grow_size = def_grow_size - left_free; /* Use precise 'def_grow_size' for new free space */ + else if (! required) + return false; /* Grow is not mandatory, leave some space in pool */ + else + { + /* Shortage of space, but grow is mandatory */ + const size_t small_inc = + ((mhd_BUF_INC_SIZE > def_grow_size) ? + def_grow_size : mhd_BUF_INC_SIZE) / 8; + if (small_inc < avail_size) + grow_size = small_inc; + else + grow_size = avail_size; + } + } + new_size = connection->read_buffer_size + grow_size; + } + /* Make sure that read buffer will not be moved */ + if ((NULL != connection->read_buffer) && + ! mhd_pool_is_resizable_inplace (connection->pool, + connection->read_buffer, + connection->read_buffer_size)) + { + mhd_assert (0); + return false; + } + /* we can actually grow the buffer, do it! */ + rb = mhd_pool_reallocate (connection->pool, + connection->read_buffer, + connection->read_buffer_size, + new_size); + if (NULL == rb) + { + /* This should NOT be possible: we just computed 'new_size' so that + it should fit. If it happens, somehow our read buffer is not in + the right position in the pool, say because someone called + mhd_pool_allocate() without 'from_end' set to 'true'? Anyway, + should be investigated! (Ideally provide all data from + *pool and connection->read_buffer and new_size for debugging). */ + mhd_assert (0); + return false; + } + mhd_assert (connection->read_buffer == rb); + connection->read_buffer = rb; + mhd_assert (NULL != connection->read_buffer); + connection->read_buffer_size = new_size; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_check_and_grow_read_buffer_space (struct MHD_Connection *restrict c) +{ + /** + * The increase of read buffer size is desirable. + */ + bool rbuff_grow_desired; + /** + * The increase of read buffer size is a hard requirement. + */ + bool rbuff_grow_required; + + mhd_assert (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info)); + mhd_assert (! c->discard_request); + + rbuff_grow_required = (c->read_buffer_offset == c->read_buffer_size); + if (rbuff_grow_required) + rbuff_grow_desired = true; + else + { + rbuff_grow_desired = (c->read_buffer_offset + 1536 > // TODO: remove handcoded buffer grow size + c->read_buffer_size); + + if ((rbuff_grow_desired) && + (MHD_CONNECTION_BODY_RECEIVING == c->state)) + { + if (! c->rq.have_chunked_upload) + { + mhd_assert (MHD_SIZE_UNKNOWN != c->rq.cntn.cntn_size); + /* Do not grow read buffer more than necessary to process the current + request. */ + rbuff_grow_desired = + (c->rq.cntn.cntn_size - c->rq.cntn.recv_size > c->read_buffer_size); // FIXME + } + else + { + mhd_assert (MHD_SIZE_UNKNOWN == c->rq.cntn.cntn_size); + if (0 == c->rq.current_chunk_size) + rbuff_grow_desired = /* Reading value of the next chunk size */ + (MHD_CHUNK_HEADER_REASONABLE_LEN > + c->read_buffer_size); + else + { + const uint_fast64_t cur_chunk_left = + c->rq.current_chunk_size - c->rq.current_chunk_offset; + /* Do not grow read buffer more than necessary to process the current + chunk with terminating CRLF. */ + mhd_assert (c->rq.current_chunk_offset <= c->rq.current_chunk_size); + rbuff_grow_desired = + ((cur_chunk_left + 2) > (uint_fast64_t) (c->read_buffer_size)); + } + } + } + } + + if (! rbuff_grow_desired) + return true; /* No need to increase the buffer */ + + if (try_grow_read_buffer (c, rbuff_grow_required)) + return true; /* Buffer increase succeed */ + + if (! rbuff_grow_required) + return true; /* 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) + { + enum MHD_ProcRecvDataStage stage; + + switch (c->state) + { + case MHD_CONNECTION_INIT: + stage = MHD_PROC_RECV_INIT; + break; + case MHD_CONNECTION_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_CONNECTION_REQ_HEADERS_RECEIVING: + stage = MHD_PROC_RECV_HEADERS; + break; + case MHD_CONNECTION_BODY_RECEIVING: + stage = c->rq.have_chunked_upload ? + MHD_PROC_RECV_BODY_CHUNKED : MHD_PROC_RECV_BODY_NORMAL; + break; + case MHD_CONNECTION_FOOTERS_RECEIVING: + stage = MHD_PROC_RECV_FOOTERS; + break; + case MHD_CONNECTION_REQ_LINE_RECEIVED: + case MHD_CONNECTION_HEADERS_RECEIVED: + case MHD_CONNECTION_HEADERS_PROCESSED: + case MHD_CONNECTION_CONTINUE_SENDING: + case MHD_CONNECTION_BODY_RECEIVED: + case MHD_CONNECTION_FOOTERS_RECEIVED: + case MHD_CONNECTION_FULL_REQ_RECEIVED: + case MHD_CONNECTION_REQ_RECV_FINISHED: + case MHD_CONNECTION_START_REPLY: + case MHD_CONNECTION_HEADERS_SENDING: + case MHD_CONNECTION_HEADERS_SENT: + case MHD_CONNECTION_UNCHUNKED_BODY_UNREADY: + case MHD_CONNECTION_UNCHUNKED_BODY_READY: + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + case MHD_CONNECTION_CHUNKED_BODY_READY: + case MHD_CONNECTION_CHUNKED_BODY_SENT: + case MHD_CONNECTION_FOOTERS_SENDING: + case MHD_CONNECTION_FULL_REPLY_SENT: + case MHD_CONNECTION_CLOSED: +#if 0 // def UPGRADE_SUPPORT // TODO: Upgrade support + case MHD_CONNECTION_UPGRADE: +#endif + default: + mhd_assert (0); + MHD_UNREACHABLE_; + stage = MHD_PROC_RECV_BODY_NORMAL; + } + + handle_recv_no_space (c, stage); + } + return false; +} diff --git a/src/mhd2/stream_process_request.h b/src/mhd2/stream_process_request.h index 810fafd9..689ff73f 100644 --- a/src/mhd2/stream_process_request.h +++ b/src/mhd2/stream_process_request.h @@ -180,5 +180,21 @@ MHD_INTERNAL bool mhd_stream_process_req_recv_finished (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; +/** + * 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 + * grow is required but not possible). + * 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. + */ +MHD_INTERNAL bool +mhd_stream_check_and_grow_read_buffer_space (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; #endif /* ! MHD_STREAM_PROCESS_REQUEST_H */ diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c index 1deb2980..0ad5cad1 100644 --- a/src/mhd2/stream_process_states.c +++ b/src/mhd2/stream_process_states.c @@ -44,7 +44,122 @@ static MHD_FN_PAR_NONNULL_ALL_ bool update_active_state (struct MHD_Connection *restrict c) { - mhd_assert (0); (void) c; + /* Do not update states of suspended connection */ + mhd_assert (! c->suspended); +#if 0 // def HTTPS_SUPPORT // TODO: implement TLS support + if (MHD_TLS_CONN_NO_TLS != connection->tls_state) + { /* HTTPS connection. */ + switch (connection->tls_state) + { + } + } +#endif /* HTTPS_SUPPORT */ + while (1) + { + switch (c->state) + { + case MHD_CONNECTION_INIT: + case MHD_CONNECTION_REQ_LINE_RECEIVING: + c->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + break; + case MHD_CONNECTION_REQ_LINE_RECEIVED: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_REQ_HEADERS_RECEIVING: + c->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + break; + case MHD_CONNECTION_HEADERS_RECEIVED: + case MHD_CONNECTION_HEADERS_PROCESSED: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_CONTINUE_SENDING: + c->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_BODY_RECEIVING: + c->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + break; + case MHD_CONNECTION_BODY_RECEIVED: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_FOOTERS_RECEIVING: + c->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + break; + case MHD_CONNECTION_FOOTERS_RECEIVED: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_FULL_REQ_RECEIVED: + mhd_assert (0 && "Should not be possible"); + c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; + break; + case MHD_CONNECTION_REQ_RECV_FINISHED: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_START_REPLY: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_HEADERS_SENDING: + /* headers in buffer, keep writing */ + c->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_HEADERS_SENT: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_UNCHUNKED_BODY_UNREADY: + mhd_assert (0 && "Should not be possible"); + c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; + break; + case MHD_CONNECTION_UNCHUNKED_BODY_READY: + c->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + mhd_assert (0 && "Should not be possible"); + c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + c->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_CHUNKED_BODY_SENT: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_FOOTERS_SENDING: + c->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE; + break; + case MHD_CONNECTION_FULL_REPLY_SENT: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + case MHD_CONNECTION_CLOSED: + 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_; + } + + if (0 != (MHD_EVENT_LOOP_INFO_READ & 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); + continue; + } + } + break; /* Everything was processed. */ + } return true; } @@ -67,7 +182,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) if ((mhd_SOCKET_ERR_NO_ERROR != c->sk_discnt_err) || (0 != (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY))) { - mhd_assert ((mhd_SOCKET_ERR_NO_ERROR != c->sk_discnt_err) || \ + mhd_assert ((mhd_SOCKET_ERR_NO_ERROR == c->sk_discnt_err) || \ mhd_SOCKET_ERR_IS_HARD (c->sk_discnt_err)); if ((mhd_SOCKET_ERR_NO_ERROR == c->sk_discnt_err) || (mhd_SOCKET_ERR_NOT_CHECKED == c->sk_discnt_err)) @@ -280,23 +395,27 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) && ! c->sk_rmt_shut_wr); continue; case MHD_CONNECTION_CLOSED: - mhd_assert (0 && "Should be unreachable"); - MHD_UNREACHABLE_; - return false; + break; #if 0 // def UPGRADE_SUPPORT case MHD_CONNECTION_UPGRADE: return MHD_YES; /* keep open */ #endif /* UPGRADE_SUPPORT */ default: mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; break; } break; } + if (MHD_CONNECTION_CLOSED == c->state) + return false; + if (c->suspended) { // TODO: process + mhd_assert (0 && "Not implemented yet"); + return true; } if (mhd_stream_check_timedout (c)) // TODO: centralise timeout checks