Evgeny Grin (Karlson2k) il y a 1 an
Parent
commit
e7eea331b1

+ 1 - 1
src/Makefile.am

@@ -15,7 +15,7 @@ SUBDIRS += microhttpd_ws
 endif
 
 if BUILD_EXAMPLES
-#SUBDIRS += examples
+SUBDIRS += examples
 endif
 
 if BUILD_TOOLS

+ 10 - 245
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
-
-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
+  minimal_example2
 
-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

+ 96 - 0
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 <stdio.h>
+#include <microhttpd2.h>
+
+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;
+}

+ 16 - 1
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

+ 1 - 1
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 \

+ 9 - 1
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;
 

+ 5 - 0
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 */

+ 14 - 9
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
   }

+ 37 - 14
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 <string.h>
 #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));
+
+  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);
 
-  mhd_assert (0 && "Not finished yet");
-  // TODO: finish
+  // TODO: update per-IP limits
+  if (NULL != c->addr)
+    free (c->addr);
+  mhd_socket_close (c->socket_fd);
+  free (c);
 }

+ 2 - 0
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

+ 13 - 10
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;
 }

+ 2 - 2
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)

+ 7 - 0
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 */

+ 12 - 0
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
 };
 
 

+ 6 - 1
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
    */

+ 0 - 17
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

+ 23 - 1
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
 };

+ 6 - 1
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

+ 123 - 0
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;
+}

+ 22 - 13
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 */

+ 2 - 0
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);

+ 52 - 0
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;
+}

+ 9 - 0
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 */

+ 2 - 1
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);
 }

+ 88 - 11
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
 }

+ 21 - 7
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 */

+ 15 - 2
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;

+ 439 - 25
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 @@
         "<body>Request HTTP header is too big for the memory constraints " \
         "of this webserver.</body>" \
         "</html>"
+/**
+ * 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 \
+        "<html>" \
+        "<head><title>Request too big</title></head>" \
+        "<body><p>The total size of the request target, the request field lines " \
+        "and the chunk size line exceeds the memory constraints of this web " \
+        "server.</p>" \
+        "<p>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.</p></body>" \
+        "</html>"
+
+/**
+ * Response text used when the request chunk size line without chunk extension
+ * cannot fit the buffer.
+ */
+#define ERR_RSP_REQUEST_CHUNK_LINE_TOO_BIG \
+        "<html>" \
+        "<head><title>Request too big</title></head>" \
+        "<body><p>The total size of the request target, the request field lines " \
+        "and the chunk size line exceeds the memory constraints of this web " \
+        "server.</p>" \
+        "<p>The request could be re-tried with a smaller " \
+        "chunk size, shorter field lines, a shorter request target or a shorter " \
+        "request method token.</p></body>" \
+        "</html>"
 
 /**
  * Response text used when the request (http header) does not
@@ -378,6 +411,17 @@
         "<body>The chunk size used in HTTP chunked encoded " \
         "request is too large.</body></html>"
 
+
+/**
+ * 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;
+}

+ 16 - 0
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 */

+ 124 - 5
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