libmicrohttpd2

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

commit c744d067ebcec0677cf7fb6c769b5bd72a679143
parent 6775213ecb06de5e7064dcca8019b8bccc76ff96
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Sat, 27 Jul 2024 12:06:21 +0200

Implemented MHD2 core functionality + basic tests

Diffstat:
M.gitignore | 3++-
MMakefile.am | 6+-----
Mconfigure.ac | 3124+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mm4/mhd_shutdown_socket_trigger.m4 | 16++++++++--------
Msrc/Makefile.am | 10+++++++++-
Asrc/examples2/.gitignore | 1+
Asrc/examples2/Makefile.am | 32++++++++++++++++++++++++++++++++
Asrc/examples2/minimal_example2.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/incl_priv/Makefile.am | 3+++
Asrc/incl_priv/mhd_sys_options.h | 550+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/include/mhd_future.h | 358+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/include/this_API_is_TERRIBLE.txt | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/.gitignore | 2++
Asrc/mhd2/Makefile.am | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/action.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/autoinit_funcs.h | 303+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/compat_calloc.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/compat_calloc.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/conn_data_process.c | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/conn_data_process.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/conn_data_recv.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/conn_data_recv.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/conn_data_send.c | 308+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/conn_data_send.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/conn_mark_ready.h | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_add_conn.c | 951+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_add_conn.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_create.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_funcs.c | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_funcs.h | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_get_info.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_logger.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_logger.h | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_logger_default.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_logger_default.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_options.h | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_set_options.c | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/daemon_start.c | 2868+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/dcc_action.c | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/events_process.c | 1104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/events_process.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/http_method.h | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/http_prot_ver.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/http_status_str.c | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/http_status_str.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Rlibmicrohttpd.pc.in -> src/mhd2/libmicrohttpd2.pc.in | 0
Asrc/mhd2/mhd_action.h | 308+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_assert.h | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_atomic_counter.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_atomic_counter.h | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_buffer.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_connection.h | 627+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_daemon.h | 969+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_dcc_action.h | 173+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_dlinked_list.h | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_iovec.h | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_itc.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_itc.h | 343+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_itc_types.h | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_lib_init.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_lib_init.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_lib_init_impl.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_limits.h | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_locks.h | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_mempool.c | 809+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_mempool.h | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_mono_clock.c | 450+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_mono_clock.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_panic.c | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_panic.h | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_public_api.h | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_recv.c | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_recv.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_reply.h | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_request.h | 375+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_response.h | 362+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_send.c | 1621+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_send.h | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_socket_error.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_socket_error.h | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_socket_type.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_sockets_funcs.c | 285+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_sockets_funcs.h | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_sockets_macros.h | 339+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_str.c | 2317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_str.h | 769+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_str_macros.h | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_str_types.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_threads.c | 420+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_threads.h | 545+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_tristate.h | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/request_funcs.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/request_funcs.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/request_get_value.c | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/request_get_value.h | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/respond_with_error.c | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/respond_with_error.h | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_add_header.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_add_header.h | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_destroy.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_destroy.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_from.c | 405+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_from.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_funcs.c | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_funcs.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_options.h | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_set_options.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/response_set_options.h | 35+++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_funcs.c | 899+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_funcs.h | 306+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_process_reply.c | 1314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_process_reply.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_process_request.c | 3840+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_process_request.h | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_process_states.c | 503+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/stream_process_states.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_base_types.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_bool_type.h | 38++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_errno.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_file_fd.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_ip_headers.h | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_malloc.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_null_macro.h | 40++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_poll.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_select.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_sendfile.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_sockets_headers.h | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_sockets_types.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/sys_thread_entry_type.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/w32_lib_res.rc.in | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tests/.gitignore | 3+++
Asrc/tests/Makefile.am | 5+++++
Asrc/tests/basic/Makefile.am | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tests/basic/test_basic_checks.c | 362+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
134 files changed, 36577 insertions(+), 951 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -4,10 +4,11 @@ Makefile .libs/ /test-driver /INSTALL -/libmicrohttpd.pc /configure.lineno /config.status.lineno /config.log +/conftest* +/confdef* /debug build-aux/ /exclude diff --git a/Makefile.am b/Makefile.am @@ -3,7 +3,7 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = contrib m4 src . if BUILD_DOC -SUBDIRS += doc +# SUBDIRS += doc endif @@ -40,13 +40,9 @@ W32VSAV = w32/VS-Any-Version/libmicrohttpd.vcxproj w32/VS-Any-Version/libmicroht w32/VS-Any-Version/libmicrohttpd.sln EXTRA_DIST = \ - libmicrohttpd.pc.in \ $(W32COMMON) $(W32VS2013) $(W32VS2015) $(W32VS2017) \ $(W32VS2019) $(W32VS2022) $(W32VSAV) -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libmicrohttpd.pc - EXTRA_DIST += pre-dist-hook-dummy MOSTLYCLEANFILES = pre-dist-hook-dummy DISTCLEANFILES = diff --git a/configure.ac b/configure.ac @@ -30,22 +30,23 @@ AC_CONFIG_HEADERS([src/incl_priv/mhd_config.h]) AC_CONFIG_MACRO_DIR([m4]) m4_pattern_forbid([^_?MHD_[A-Z_]+_CC_])dnl -LIB_VERSION_CURRENT=80 -LIB_VERSION_REVISION=1 -LIB_VERSION_AGE=68 -AC_SUBST([LIB_VERSION_CURRENT]) -AC_SUBST([LIB_VERSION_REVISION]) -AC_SUBST([LIB_VERSION_AGE]) +LIB_VER_CURRENT=0 +LIB_VER_REVISION=0 +LIB_VER_AGE=0 +AC_SUBST([LIB_VER_CURRENT]) +AC_SUBST([LIB_VER_REVISION]) +AC_SUBST([LIB_VER_AGE]) PACKAGE_VERSION_MAJOR='m4_car(m4_unquote(m4_split(AC_PACKAGE_VERSION, [\.])))' PACKAGE_VERSION_MINOR='m4_argn(2, m4_unquote(m4_split(AC_PACKAGE_VERSION, [\.])))' PACKAGE_VERSION_SUBMINOR='m4_argn(3, m4_unquote(m4_split(AC_PACKAGE_VERSION, [\.])))' -AS_VAR_ARITH([MHD_W32_DLL_SUFF],[[$LIB_VERSION_CURRENT - $LIB_VERSION_AGE]]) +AS_VAR_ARITH([MHD_W32_DLL_SUFF],[[$LIB_VER_CURRENT - $LIB_VER_AGE]]) AC_SUBST([PACKAGE_VERSION_MAJOR]) AC_SUBST([PACKAGE_VERSION_MINOR]) AC_SUBST([PACKAGE_VERSION_SUBMINOR]) AC_SUBST([MHD_W32_DLL_SUFF]) +AC_CONFIG_FILES([src/mhd2/w32_lib_res.rc]) MHD_LIB_CPPFLAGS="" MHD_LIB_CFLAGS="" @@ -479,12 +480,12 @@ CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" # Additional flags are checked and added at the end of 'configure' # Check for headers that are ALWAYS required -AC_CHECK_HEADERS_ONCE([stdio.h string.h stdint.h errno.h limits.h fcntl.h], [], - [AC_MSG_ERROR([Compiling libmicrohttpd requires standard POSIX headers files])], [AC_INCLUDES_DEFAULT]) +AC_CHECK_HEADERS_ONCE([stdio.h string.h stdint.h stdarg.h errno.h limits.h fcntl.h], [], + [AC_MSG_ERROR([Compiling libmicrohttpd requires standard C and POSIX headers files])], [AC_INCLUDES_DEFAULT]) # Check for basic optional headers AC_CHECK_HEADERS([stddef.h stdlib.h inttypes.h sys/types.h sys/stat.h unistd.h \ - sys/uio.h], [], [], [AC_INCLUDES_DEFAULT]) + sys/uio.h crtdefs.h malloc.h io.h], [], [], [AC_INCLUDES_DEFAULT]) # Check for clock-specific optional headers AC_CHECK_HEADERS([sys/time.h time.h], [], [], [AC_INCLUDES_DEFAULT]) @@ -496,8 +497,8 @@ AC_CHECK_HEADERS([endian.h machine/endian.h sys/endian.h sys/byteorder.h \ # Check for network and sockets optional headers AC_CHECK_HEADERS([sys/socket.h sys/select.h netinet/in_systm.h netinet/in.h \ - arpa/inet.h netinet/ip.h netinet/tcp.h net/if.h \ - netdb.h sockLib.h inetLib.h], [], [], + sys/un.h arpa/inet.h netinet/ip.h netinet/tcp.h net/if.h \ + netdb.h sockLib.h inetLib.h selectLib.h afunix.h], [], [], [AC_INCLUDES_DEFAULT [ #ifdef HAVE_SYS_TYPES_H @@ -508,7 +509,9 @@ AC_CHECK_HEADERS([sys/socket.h sys/select.h netinet/in_systm.h netinet/in.h \ #endif /* HAVE_INTTYPES_H */ #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> -#endif /* HAVE_SYS_SOCKET_H */ +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif #ifdef HAVE_NETINET_IN_SYSTM_H #include <netinet/in_systm.h> #endif /* HAVE_NETINET_IN_SYSTM_H */ @@ -527,6 +530,72 @@ AC_CHECK_HEADERS([sys/socket.h sys/select.h netinet/in_systm.h netinet/in.h \ # Check for other optional headers AC_CHECK_HEADERS([sys/msg.h sys/mman.h signal.h], [], [], [AC_INCLUDES_DEFAULT]) +AC_CHECK_TYPES([size_t,ssize_t,ptrdiff_t,intptr_t,uintptr_t,uint8_t],[],[], + [[ +/* Keep in sync with src/mhd2/sys_base_types.h */ +#include <stdint.h> /* uint_fast_XXt, int_fast_XXt */ +#if defined(HAVE_STDDEF_H) +# include <stddef.h> /* size_t, NULL */ +#elif defined(HAVE_STDLIB_H) +# include <stdlib.h> /* should provide size_t, NULL */ +#else +# include <stdio.h> /* should provide size_t, NULL */ +#endif +#if defined(HAVE_SYS_TYPES_H) +# include <sys/types.h> /* ssize_t */ +#elif defined(HAVE_UNISTD_H) +# include <unistd.h> /* should provide ssize_t */ +#endif +#ifdef HAVE_CRTDEFS_H +# include <crtdefs.h> /* W32-specific header */ +#endif + ]] +) +AS_IF([test "x$ac_cv_type_size_t" != "xyes"], + [AC_MSG_FAILURE(['size_t' type is not provided by system headers])] +) +AS_IF([test "x$ac_cv_type_uint8_t" != "xyes"], + [AC_MSG_FAILURE(['uint8_t' type is not provided by system headers])] +) +AC_CHECK_SIZEOF([char]) +AS_IF([test "x$ac_cv_sizeof_char" != "x1"], + [AC_MSG_FAILURE(['char' type with size different from '1' is not supported])] +) + + +AC_CHECK_TYPE([socklen_t], + [], + [AC_DEFINE([socklen_t],[int],[Define to suitable 'socklen_t' replacement if 'socklen_t' is not defined by system headers])], + [[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif /* HAVE_SYS_TYPES_H */ +#include <stdint.h> +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif /* HAVE_INTTYPES_H */ +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif /* HAVE_NETINET_IN_SYSTM_H */ +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_SOCKLIB_H +#include <sockLib.h> +#endif /* HAVE_SOCKLIB_H */ +#if defined(_WIN32) && !defined(__CYGWIN__) +#include <winsock2.h> +#include <ws2tcpip.h> +#endif /* _WIN32 && ! __CYGWIN__ */ + ]] +) + AC_CHECK_HEADER([[search.h]], [ MHD_CHECK_LINK_RUN([[for proper tsearch(), tfind() and tdelete()]],[[mhd_cv_sys_tsearch_usable]], @@ -659,7 +728,9 @@ AC_CHECK_HEADERS([sys/sysctl.h netinet/ip_icmp.h netinet/icmp_var.h], [], [], #endif /* HAVE_SYS_SYSCTL_H */ #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> -#endif /* HAVE_SYS_SOCKET_H */ +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif #ifdef HAVE_NETINET_IN_SYSTM_H #include <netinet/in_systm.h> #endif /* HAVE_NETINET_IN_SYSTM_H */ @@ -845,434 +916,818 @@ AS_VAR_IF([[mhd_cv_macro_true_false_valid]], [["yes"]], [[:]], [AC_MSG_ERROR([[Value of "true" or value of "false" is not valid. Check config.log for details.]])]) -AX_CHECK_COMPILE_FLAG([[-Werror=attributes]], +AC_CACHE_CHECK([whether the NULL pointer has all zero bits], + [mhd_cv_ptr_null_all_zeros], [ - AC_MSG_CHECKING([[whether -Werror=attributes actually works]]) - CFLAGS="${CFLAGS_ac} ${user_CFLAGS} -Werror=attributes" - AC_COMPILE_IFELSE([AC_LANG_PROGRAM( - [[__attribute__((non_existing_attrb_dummy)) static int SimpleFunc(void) {return 3;}]], - [[int r = SimpleFunc(); if (r) return r;]])], - [ - AC_MSG_RESULT([[no]]) - errattr_CFLAGS="" - ], [ - AC_MSG_RESULT([[yes]]) - errattr_CFLAGS="-Werror=attributes" - ]) - CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" - ], - [[errattr_CFLAGS=""]], [], []) - -AC_MSG_CHECKING([[for function inline keywords supported by $CC]]) -CFLAGS="${CFLAGS_ac} ${user_CFLAGS} $errattr_CFLAGS" -inln_prfx="none" -# Prefer always inline functions -for inln_prfx_chk in InlineWithAttr __forceinline inline __inline__ __inline _inline _Inline -do - # Try to link to avoid "symbol undefined" problems at build time - AS_IF([[test "x$inln_prfx_chk" = "xInlineWithAttr"]], - [ - AS_IF([[test "x$errattr_CFLAGS" = "x"]], - [[ # Skip test with attribute as negative result can't be detected - inln_prfx_chk="__forceinline" # use next value - ]],[[inln_prfx_chk="inline __attribute__((always_inline))"]]) - ]) - AC_LINK_IFELSE( - [ - AC_LANG_PROGRAM( - [[ -#ifdef __cplusplus -#error This test is only for C. -choke me -#endif -#ifdef HAVE_STDBOOL_H -#include <stdbool.h> -#endif - static $inln_prfx_chk bool cmpfn(int x, int y) - { return x > y; } - static $inln_prfx_chk int sumfn(int x, int y) - { return x + y; } - ]],[[ - int a = 1, b = 100, c; - if (cmpfn(a, b)) - c = sumfn(a, b); - else - c = 0 - sumfn(a, b); - if (c) - return 0; - ]]) - ], - [[ inln_prfx="$inln_prfx_chk" ]]) - test "x$inln_prfx" != "xnone" && break -done -AS_IF([[test "x$inln_prfx" != "xnone"]], + AC_RUN_IFELSE( [ - AC_DEFINE([INLINE_FUNC],[1],[Define to 1 if your C compiler supports inline functions.]) - AC_DEFINE_UNQUOTED([_MHD_static_inline],[static $inln_prfx],[Define to prefix which will be used with MHD static inline functions.]) - ], [ - AC_DEFINE([_MHD_static_inline],[static],[Define to prefix which will be used with MHD static inline functions.]) - ]) -AC_MSG_RESULT([[$inln_prfx]]) -CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" - -AC_CHECK_HEADERS([stdalign.h], [], [], [AC_INCLUDES_DEFAULT]) -AC_CACHE_CHECK([[for C11 'alignof()' support]], [[mhd_cv_c_alignof]], - [AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM( - [[ -#ifdef HAVE_STDALIGN_H -#include <stdalign.h> + AC_LANG_SOURCE([[ +#include <string.h> +#if defined(HAVE_STDDEF_H) +#include <stddef.h> +#elif defined(HAVE_STDLIB_H) +#include <stdlib.h> +#else +#include <stdio.h> #endif - ]], [[ - int var1[(alignof(int) >= 2) ? 1 : -1]; - int var2[alignof(unsigned int) - 1]; - int var3[(alignof(char) > 0) ? 1 : -1]; - int var4[(alignof(long) >= 4) ? 1 : -1]; - /* Mute compiler warnings */ - var1[0] = var2[0] = var3[0] = 0; - var4[0] = 1; - if (var1[0] + var2[0] + var3[0] == var4[0]) - return 1; - ]]) - ], [ - AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM( - [[ -#ifdef HAVE_STDALIGN_H -#include <stdalign.h> -#endif - ]], [[ - /* Should fail if 'alignof()' works */ - int var1[alignof(nonexisting_type) - 1]; +int main(void) +{ + void *ptr1; + void *ptr2; - /* Mute compiler warnings */ - var1[0] = 1; - if (var1[0] + 1 == 1) - return 1; - ]]) - ], [[mhd_cv_c_alignof='no']], [[mhd_cv_c_alignof='yes']]) - ], [[mhd_cv_c_alignof='no']]) - ]) -AS_VAR_IF([mhd_cv_c_alignof], ["yes"], - [AC_DEFINE([[HAVE_C_ALIGNOF]], [1], [Define to 1 if your compiler supports 'alignof()'])]) + ptr1 = &ptr2; + ptr2 = NULL; + memset(&ptr1, 0, sizeof(ptr1)); + if (ptr2 != ptr1) + return 2; + ptr2 = &ptr1; + ptr1 = NULL; + memset(&ptr2, 0, sizeof(ptr2)); + if (0 != memcmp (&ptr1, &ptr2, sizeof(ptr1))) + return 3; -# Check system type -AC_MSG_CHECKING([[for target host OS]]) -os_is_windows="no" -os_is_native_w32="no" -AS_CASE(["$host_os"], - [*darwin* | *rhapsody* | *macosx*], - [AC_DEFINE([OSX],[1],[This is an OS X system]) - mhd_host_os='Darwin' - AC_MSG_RESULT([[$mhd_host_os]])], - [kfreebsd*-gnu], - [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) - AC_DEFINE([FREEBSD],[1],[This is a FreeBSD system]) - mhd_host_os='FreeBSD kernel with GNU userland' - AC_MSG_RESULT([[$mhd_host_os]])], - [freebsd*], - [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) - AC_DEFINE([FREEBSD],[1],[This is a FreeBSD system]) - mhd_host_os='FreeBSD' - AC_MSG_RESULT([[$mhd_host_os]])], - [openbsd*], - [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) - AC_DEFINE([OPENBSD],[1],[This is an OpenBSD system]) - mhd_host_os='OpenBSD' - AC_MSG_RESULT([[$mhd_host_os]])], - [netbsd*], - [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) - AC_DEFINE([NETBSD],[1],[This is a NetBSD system]) - mhd_host_os='NetBSD' - AC_MSG_RESULT([[$mhd_host_os]])], - [*solaris*], - [AC_DEFINE([SOLARIS],[1],[This is a Solaris system]) - AC_DEFINE([_REENTRANT],[1],[Need with solaris or errno does not work]) - mhd_host_os='Solaris' - AC_MSG_RESULT([[$mhd_host_os]])], - [*linux*], - [AC_DEFINE([LINUX],[1],[This is a Linux kernel]) - mhd_host_os='Linux' - AC_MSG_RESULT([[$mhd_host_os]])], - [*cygwin*], - [AC_DEFINE([CYGWIN],[1],[This is a Cygwin system]) - mhd_host_os='Windows/Cygwin' - AC_MSG_RESULT([[$mhd_host_os]]) - os_is_windows="yes"], - [*mingw*], - [ - AC_DEFINE([MINGW],[1],[This is a MinGW system]) - AC_DEFINE([WINDOWS],[1],[This is a Windows system]) - mhd_host_os='Windows/MinGW' - AC_MSG_RESULT([[$mhd_host_os]]) - AC_CHECK_HEADERS([winsock2.h ws2tcpip.h], [], [AC_MSG_ERROR([[Winsock2 headers are required for W32]])], [AC_INCLUDES_DEFAULT]) - AC_CACHE_CHECK([for MS lib utility], [ac_cv_use_ms_lib_tool], - [mslibcheck=`lib 2>&1` - AS_IF([echo "$mslibcheck" | $GREP -e '^Microsoft (R) Library Manager' - >/dev/null], - [ac_cv_use_ms_lib_tool=yes], - [ac_cv_use_ms_lib_tool=no]) - ]) - AS_IF([test "x$ac_cv_use_ms_lib_tool" = "xyes"], - [AC_SUBST([MS_LIB_TOOL], [[lib]])]) - AC_SUBST([lt_cv_objdir]) - os_is_windows="yes" - os_is_native_w32="yes" - ], - [*openedition*], - [AC_DEFINE([OS390],[1],[This is a OS/390 system]) - mhd_host_os='OS/390' - AC_MSG_RESULT([[$mhd_host_os]])], - [gnu*], - [AC_DEFINE([[GNU_HURD]], [[1]], [Define to `1' if host machine runs on GNU Hurd.]) - mhd_host_os='GNU Hurd' - AC_MSG_RESULT([[$mhd_host_os]]) - ], - [ - AC_MSG_RESULT([unrecognised OS]) - mhd_host_os="${host_os}" - AC_MSG_WARN([Unrecognised OS $host_os]) - AC_DEFINE([OTHEROS],1,[Some strange OS]) - ]) + ptr1 = (void*)&ptr1; + ptr2 = (void*)&ptr2; + memset(&ptr1, 0, sizeof(ptr1)); + memset(&ptr2, 0, sizeof(ptr2)); + if (NULL != ptr1) + return 4; + if (NULL != ptr2) + return 5; -AM_CONDITIONAL([CYGWIN_TARGET], [[test "x$os_is_windows" = "xyes" && \ - test "x${os_is_native_w32}" != "xyes"]]) + return 0; +} + ]] + ) + ], + [mhd_cv_ptr_null_all_zeros="yes"], + [mhd_cv_ptr_null_all_zeros="no"], + [ + AS_CASE([$host_cpu],dnl + [[i[234567]86|x86_64|amd64|arm|armeb|armv[0123456789]|armv[0123456789]eb|aarch64|aarch64_be|arm64|mips|mipsel|mips64|mips64el|powerpc|powerpcle|powerpc64|powerpc64le|riscv32|riscv32be|riscv64|riscv64be]], + [ + AS_CASE([$host_os],dnl + [[linux*|freebsd|cygwin|mingw*|msys|gnu|netbsd*|openbsd*|darwin*|solaris2*|haiku]], + [mhd_cv_ptr_null_all_zeros="assuming yes"], + [mhd_cv_ptr_null_all_zeros="assuming no"] + ) + ], + [mhd_cv_ptr_null_all_zeros="assuming no"] + ) + ] + ) + ] +) +AS_IF([test "x${mhd_cv_ptr_null_all_zeros}" = "xyes" || test "x${mhd_cv_ptr_null_all_zeros}" = "xassuming yes"], + [AC_DEFINE([HAVE_NULL_PTR_ALL_ZEROS],[1],[Define to '1' if NULL pointers binary representation is all zero bits])] +) -AS_VAR_IF([os_is_windows], ["yes"], +AC_CACHE_CHECK([whether $CC supports variadic macros],[mhd_cv_cc_macro_variadic], [ - AC_MSG_CHECKING([[whether target W32 version is specified by precompiler defines]]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -/* Note: check logic is reversed for easy log reading */ -#ifdef WINVER -#error WINVER is defined -choke me now; -#endif -#ifdef _WIN32_WINNT -#error _WIN32_WINNT is defined -choke me now; -#endif -#ifdef NTDDI -#error NTDDI is defined -choke me now; -#endif - ]],[[(void)0]]) - ], [[mhd_w32_ver_preselect=no]], [[mhd_w32_ver_preselect=yes]] + AS_CASE([$ac_prog_cc_stdc], + [c89],[:], + [c??],[mhd_cv_cc_macro_variadic="yes"] ) - AC_MSG_RESULT([[${mhd_w32_ver_preselect}]]) - AC_CHECK_HEADERS([windows.h sdkddkver.h], [], [], [AC_INCLUDES_DEFAULT]) - AS_VAR_IF([mhd_w32_ver_preselect],["yes"], - [ - AC_MSG_CHECKING([[for specified target W32 version]]) - AS_UNSET([[mhd_w32_ver]]) - AS_UNSET([[mhd_w32_ver_msg]]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#if _WIN32_WINNT+0 < 0x0501 -/* Check before headers inclusion */ -#error _WIN32_WINNT is less than 0x0501 -choke me now; -#endif + AS_VAR_SET_IF([mhd_cv_cc_macro_variadic],[:], + [ + MHD_SAVED_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag=yes + AC_COMPILE_IFELSE( + [ + AC_LANG_SOURCE([[ +#define GET_THIRD(arg1,arg2,arg3,arg4) (arg3) +#define GET_THIRD_VARIADIC(...) GET_THIRD(__VA_ARGS__) -#ifdef HAVE_SDKDDKVER_H -#include <sdkddkver.h> -#endif -#ifdef HAVE_WINDOWS_H -#include <windows.h> -#endif - -#if _WIN32_WINNT+0 < 0x0501 -#error _WIN32_WINNT is less than 0x0501 -choke me now; -#endif - ]],[[(void)0]]) - ], [], [ - AC_MSG_RESULT([[pre-WinXP]]) - AC_MSG_ERROR([[libmicrohttpd cannot be compiled for Windows version before Windows XP]]) - ] - ) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#ifdef HAVE_SDKDDKVER_H -#include <sdkddkver.h> -#endif -#ifdef HAVE_WINDOWS_H -#include <windows.h> -#endif - -#if _WIN32_WINNT+0 == 0x0501 -#error _WIN32_WINNT is 0x0501 -choke me now; -#endif -#if _WIN32_WINNT+0 == 0x0502 -#error _WIN32_WINNT is 0x0502 -choke me now; -#endif - ]],[[(void)0]]) - ], [], [ - mhd_w32_ver="WinXP" - mhd_w32_ver_msg="WinXP (selected by precompiler flags)" - ] - ) - AS_VAR_SET_IF([mhd_w32_ver], [], - [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#ifdef HAVE_SDKDDKVER_H -#include <sdkddkver.h> -#endif -#ifdef HAVE_WINDOWS_H -#include <windows.h> -#endif +int main(void) +{ + int arr[GET_THIRD_VARIADIC(-1,-2,5,-3) - 4] = {0}; + if (0 != arr[GET_THIRD_VARIADIC(100,1000,0,10000)]) + return 2; + return 0; +} + ]] + ) + ], + [mhd_cv_cc_macro_variadic="yes"], + [mhd_cv_cc_macro_variadic="no"] + ) + ac_c_werror_flag="$MHD_SAVED_ac_c_werror_flag" + AS_UNSET([MHD_SAVED_ac_c_werror_flag]) + ] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_macro_variadic],["yes"], + [AC_DEFINE([HAVE_MACRO_VARIADIC],[1],[Define to '1' if your compiler supports variadic macros])] +) +AC_CACHE_CHECK([whether $CC supports compound literals],[mhd_cv_cc_compound_literals], + [ + MHD_SAVED_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag=yes + AC_COMPILE_IFELSE( + [ + AC_LANG_SOURCE([[ +struct test_strct +{ + char c; + int i; +}; -#if _WIN32_WINNT+0 < 0x0600 -#error _WIN32_WINNT is less than 0x0600 but greater than 0x0502 -choke me now; -#endif - ]],[[(void)0]]) - ], [], [ - AC_MSG_ERROR([[_WIN32_WINNT value is wrong (less than 0x0600 but greater than 0x0502)]]) - ] - ) +int main(void) +{ + struct test_strct strct_var; + int i; - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#ifdef HAVE_SDKDDKVER_H -#include <sdkddkver.h> -#endif -#ifdef HAVE_WINDOWS_H -#include <windows.h> -#endif + strct_var = (struct test_strct) { 'a', 0 }; -#if _WIN32_WINNT+0 == 0x0600 -#error _WIN32_WINNT is 0x0600 -choke me now; -#endif - ]],[[(void)0]]) - ], [], [ - mhd_w32_ver="Vista" - mhd_w32_ver_msg="Vista (selected by precompiler flags)" - ] - ) - ] - ) + i = (int){0}; - AS_VAR_SET_IF([mhd_w32_ver], [], - [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#ifdef HAVE_SDKDDKVER_H -#include <sdkddkver.h> -#endif -#ifdef HAVE_WINDOWS_H -#include <windows.h> -#endif + if (strct_var.i != i) /* Process all variables to avoid warnings */ + return 2; -#if _WIN32_WINNT+0 > 0x0600 -#error _WIN32_WINNT is greater than 0x0600 -choke me now; -#endif - ]],[[(void)0]]) - ], [ - mhd_w32_ver="unknown" - mhd_w32_ver_msg="unknown (cannot be detected)" - ], [ - mhd_w32_ver="newer than Vista" - mhd_w32_ver_msg="newer than Vista (selected by precompiler flags)" - ] - ) - ] - ) - AC_MSG_RESULT([[${mhd_w32_ver}]]) - ], [ - mhd_w32_ver="Vista" - mhd_w32_ver_msg="Vista (default, override by CPPFLAGS=-D_WIN32_WINNT=0xNNNN)" - CPPFLAGS_ac="${CPPFLAGS_ac} -D_WIN32_WINNT=0x0600" - CPPFLAGS="${CPPFLAGS_ac} ${user_CPPFLAGS}" - AC_MSG_CHECKING([[whether headers accept _WIN32_WINNT=0x0600]]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#ifdef HAVE_SDKDDKVER_H -#include <sdkddkver.h> -#endif -#ifdef HAVE_WINDOWS_H -#include <windows.h> -#endif -#include <stdio.h> - ]],[[(void)0]]) - ], [ - AC_MSG_RESULT([[yes]]) - ], [ - AC_MSG_RESULT([[no]]) - AC_MSG_ERROR([Headers do not accept _WIN32_WINNT=0x0600. Consider override target W32 version by CPPFLAGS=-D_WIN32_WINNT=0xNNNN]) - ] - ) - ] + return 0; +} + ]] + ) + ], + [mhd_cv_cc_compound_literals="yes"], + [mhd_cv_cc_compound_literals="no"] ) + ac_c_werror_flag="$MHD_SAVED_ac_c_werror_flag" + AS_UNSET([MHD_SAVED_ac_c_werror_flag]) ] ) - -AS_IF([test "x${os_is_windows}" = "xyes" && test "x${os_is_native_w32}" = "xyes"], +AS_VAR_IF([mhd_cv_cc_compound_literals],["yes"], [ - AC_CACHE_CHECK([W32 run-time library type], [mhd_cv_wctr_type], + AC_DEFINE([HAVE_COMPOUND_LITERALS],[1],[Define to '1' if your compiler supports compound literals]) + + AC_CACHE_CHECK([whether $CC supports compound literals as arrays of the scope],[mhd_cv_cc_compound_literals_arr_scope], [ - AC_EGREP_CPP([MHDMARKER: UCRT run-time library in use!], [ -#include <stdio.h> -#if defined(_UCRT) -#define CRT_STR "MHDMARKER: UCRT run-time library in use!" -#endif -#if defined(__MSVCRT_VERSION__) -#if (__MSVCRT_VERSION__ >= 0xE00) && (__MSVCRT_VERSION__ < 0x1000) -#define CRT_STR "MHDMARKER: UCRT run-time library in use!" -#endif -#if (__MSVCRT_VERSION__ > 0x1400) -#define CRT_STR "MHDMARKER: UCRT run-time library in use!" -#endif + MHD_SAVED_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag=yes + AC_COMPILE_IFELSE( + [ + AC_LANG_SOURCE([[ +#ifdef __cplusplus +choke me now; /* Clang++ actually may handle this check properly, but it is too tricky to test it corretly */ +#error This is a C++ compiler #endif -#ifndef CRT_STR -#define CRT_STR "MHDMARKER: MSVCRT run-time library in use!" -#endif +struct test_strct +{ + char c; + int i; +}; + +static int test_strct_func(struct test_strct *strc) +{ + return 3 == strc->i; +} int main(void) { - printf ("%\n", CRT_STR); - return 0; + struct test_strct *strct_ptr1; + struct test_strct *strct_ptr2; + int *int_ptr; + void *ptr; + + ptr = (struct test_strct[]) { { 'p', 11 }, { 'q', 22 }, { 'r', 33 } }; + strct_ptr1 = (struct test_strct[]) { { 'a', 1 }, { 'b', 2 }, { 'c', 3 } }; + int_ptr = (int[]){0xFF,0xFE,0xFD,0xFC,0xFB,0xFA}; + + if (2 != (strct_ptr1 + 1)->i) + return 3; + + strct_ptr2 = (struct test_strct[]) { { 'a', 8 }, { 'b', 9 }, { 'c', 7 } }; + + if (0xFE != int_ptr[1]) + return 4; + + if (*(int_ptr + 4) != 0xFB) + return 5; + + if (!test_strct_func(strct_ptr1 + 2)) + return 6; + + if (int_ptr[2] != 0xFD) + return 7; + + if (((struct test_strct *)ptr)[0].c == 'p') + ptr = (struct test_strct[]) { { 'x', 55 }, { 'y', 66 }, { 'z', 77 } }; + + if (*((char *)ptr) != 'x') + return 8; + + if (*(int_ptr + 5) != 0xFA) + return 9; + + if (strct_ptr2[1].c != 'b') + return 10; + + return strct_ptr1[1].i == 2 ? 0 : 11; } + ]] + ) ], - [mhd_cv_wctr_type="ucrt"], [mhd_cv_wctr_type="msvcrt"]) + [mhd_cv_cc_compound_literals_arr_scope="yes"], + [mhd_cv_cc_compound_literals_arr_scope="no"] + ) + ac_c_werror_flag="$MHD_SAVED_ac_c_werror_flag" + AS_UNSET([MHD_SAVED_ac_c_werror_flag]) ] ) - mhd_host_os="${mhd_host_os}-${mhd_cv_wctr_type}" - AS_VAR_IF([mhd_cv_wctr_type], ["msvcrt"], - [ - # Use CFLAGS here to override user-supplied wrong CPPFLAGS. Durty trick, but choice is limited. - AX_APPEND_COMPILE_FLAGS([-U__USE_MINGW_ANSI_STDIO -D__USE_MINGW_ANSI_STDIO=0], [CFLAGS_ac]) - AC_SUBST([W32CRT], [MSVCRT]) - ], [AC_SUBST([W32CRT], [UCRT])] + AS_VAR_IF([mhd_cv_cc_compound_literals_arr_scope],["yes"], + [AC_DEFINE([HAVE_COMPOUND_LITERALS_ARRAYS_SCOPE],[1],[Define to '1' if your compiler supports compound literals as arrays of the scope])] ) - CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" - LDFLAGS="${user_LDFLAGS}" - AS_CASE([$mhd_w32_ver], - [WinXP], - [MHD_CHECK_ADD_CC_LDFLAG([-Wl,--major-subsystem-version,5,--minor-subsystem-version,1],[LDFLAGS_ac])], - [Vista], - [MHD_CHECK_ADD_CC_LDFLAG([-Wl,--major-subsystem-version,6,--minor-subsystem-version,0],[LDFLAGS_ac])] + AC_CACHE_CHECK([whether $CC supports compound literals as local arrays],[mhd_cv_cc_compound_literals_arr_local], + [ + MHD_SAVED_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag=yes + AC_COMPILE_IFELSE( + [ + AC_LANG_SOURCE([[ +struct test_strct +{ + char c; + int i; +}; + +static int test_strct_func(struct test_strct *strc) +{ + return 0 == strc->i; +} + +int main(void) +{ + if (test_strct_func((struct test_strct[]) { { 'a', 0 }, { 'b', 1 }, { 'c', 2 } })) + return 4; + + return ((struct test_strct[]) { { 'a', 0 }, { 'b', 1 }, { 'c', 2 } })->i; +} + ]] + ) + ], + [mhd_cv_cc_compound_literals_arr_local="yes"], + [mhd_cv_cc_compound_literals_arr_local="no"] + ) + ac_c_werror_flag="$MHD_SAVED_ac_c_werror_flag" + AS_UNSET([MHD_SAVED_ac_c_werror_flag]) + ] + ) + AS_VAR_IF([mhd_cv_cc_compound_literals_arr_local],["yes"], + [AC_DEFINE([HAVE_COMPOUND_LITERALS_ARRAYS_LOCAL],[1],[Define to '1' if your compiler supports compound literals as local arrays])] ) - LDFLAGS="${LDFLAGS_ac} ${user_LDFLAGS}" - ] -) -CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" + AC_CACHE_CHECK([whether $CC supports compound literals as lvalue],[mhd_cv_cc_compound_literals_lvalues], + [ + MHD_SAVED_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag=yes + AC_COMPILE_IFELSE( + [ + AC_LANG_SOURCE([[ +struct test_strct +{ + char c; + int i; +}; -AC_ARG_WITH([threads], - [AS_HELP_STRING([--with-threads=LIB],[choose threading library (posix, w32, auto, none) [auto]])], - [], [with_threads='auto']) -AS_CASE([[$with_threads]], - [[win32]], [[with_threads='w32']], - [[pthreads]], [[with_threads='posix']], - [[posix]], [[:]], - [[w32]], [[:]], - [[none]], [[with_threads='none']], - [[no]], [[with_threads='none']], - [[auto]], [[:]], - [AC_MSG_ERROR([[incorrect parameter "$with_threads" specified for --with-threads]])] -) +static int test_strct_func(struct test_strct *strc) +{ + return 0 != strc->i; +} + +int main(void) +{ + struct test_strct *strct_ptr; + int int_var; + int *int_ptr; + + int_var = ++(int) {0}; + int_ptr = &((int) {1}); + + if (int_var != *int_ptr) /* Process all variables to avoid warnings */ + return 2; + + if (test_strct_func(&( (struct test_strct) { 'a', 0 } ))) + return 4; + + return 0; +} + ]] + ) + ], + [mhd_cv_cc_compound_literals_lvalues="yes"], + [mhd_cv_cc_compound_literals_lvalues="no"] + ) + ac_c_werror_flag="$MHD_SAVED_ac_c_werror_flag" + AS_UNSET([MHD_SAVED_ac_c_werror_flag]) + ] + ) + AS_VAR_IF([mhd_cv_cc_compound_literals_lvalues],["yes"], + [AC_DEFINE([HAVE_COMPOUND_LITERALS_LVALUES],[1],[Define to '1' if your compiler supports compound literals as lvalues])] + ) + ], + [ + AS_UNSET([mhd_cv_cc_compound_literals_arr_local]) + AS_UNSET([mhd_cv_cc_compound_literals_lvalue]) + AS_UNSET([mhd_cv_cc_compound_literals_arr_scope]) + ] +) + +AS_VAR_SET_IF([ac_cv_c_restrict],[], + [ + # "cache" result in advance if it is known from the compiler test + AS_CASE([$ac_prog_cc_stdc], + [c89],[:], + [c??],[ac_cv_c_restrict="restrict"] + ) + ] +) +AC_C_RESTRICT +AS_VAR_IF([ac_cv_c_restrict],["no"],[], + [AC_DEFINE([HAVE_RESTRICT],[1],[Define to '1' if your compiler supports 'restrict' keyword])] +) +AC_C_INLINE +AS_UNSET([errattr_CFLAGS]) +CFLAGS="${user_CFLAGS}" +MHD_CHECK_CC_CFLAG([-Werror=attributes],[CFLAGS_ac], + [ + AC_CACHE_CHECK([whether -Werror=attributes actually works],[mhd_cv_cflag_werror_attr_works], + [ + CFLAGS="${CFLAGS_ac} ${user_CFLAGS} -Werror=attributes" + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM( + [[__attribute__((non_existing_attrb_dummy)) static int SimpleFunc(void) {return 3;}]], + [[int r = SimpleFunc(); if (r) return r;]] + ) + ], + [mhd_cv_cflag_werror_attr_works="no"], + [mhd_cv_cflag_werror_attr_works="yes"] + ) + ] + ) + AS_VAR_IF([mhd_cv_cflag_werror_attr_works],["yes"], + [errattr_CFLAGS="-Werror=attributes"] + ) + ] +) +CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" +AS_IF([test "x$ac_cv_c_inline" != "xno"], + [ + AC_DEFINE([HAVE_INLINE_FUNCS],[1],[Define to 1 if your C compiler supports inline functions.]) + AC_CACHE_CHECK([for function force inline keywords supported by $CC],[mhd_cv_cc_kwd_forceinline], + [ + MHD_SAVED_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag=yes + mhd_cv_cc_kwd_forceinline="none" + CFLAGS="${CFLAGS_ac} ${user_CFLAGS} $errattr_CFLAGS" + for keyword_chk in "inline __attribute__((always_inline))" __forceinline + do + AS_CASE([${keyword_chk}], + [*attribute*], + [AS_IF([test "x$errattr_CFLAGS" = "x"],[continue])] + ) + AC_LINK_IFELSE([ + AC_LANG_SOURCE([[ +#ifdef HAVE_STDBOOL_H +#include <stdbool.h> +#endif +static ${keyword_chk} bool cmpfn(int x, int y) +{ return x > y; } +static ${keyword_chk} int sumfn(int x, int y) +{ return x + y; } + +int main(void) +{ + int a = 1, b = 100, c; + if (cmpfn(a, b)) + c = sumfn(a, b); + else + c = 0 - sumfn(a, b); + return (cmpfn(0, c) ? 0 : 5); +} + ]] + ) + ], + [mhd_cv_cc_kwd_forceinline="${keyword_chk}"] + ) + test "x${mhd_cv_cc_kwd_forceinline}" != "xnone" && break + done + CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" + ac_c_werror_flag="$MHD_SAVED_ac_c_werror_flag" + AS_UNSET([MHD_SAVED_ac_c_werror_flag]) + ] + ) + ] +) +AS_IF([test "x$ac_cv_c_inline" != "xno" && test "x${mhd_cv_cc_kwd_forceinline}" != "xnone"], + [AC_DEFINE_UNQUOTED([MHD_static_inline_],[static $mhd_cv_cc_kwd_forceinline],[Define to prefix which will be used with MHD static inline functions.])] + , + [AC_DEFINE([MHD_static_inline_],[static inline],[Define to prefix which will be used with MHD static inline functions.])] +) + +AC_CACHE_CHECK([for 'unreachable' keywords supported by $CC],[mhd_cv_cc_kwd_unreachable], + [ + mhd_cv_cc_kwd_unreachable="none" + for keyword_chk in '__builtin_unreachable()' '__assume(0)' 'unreachable()' + do + AC_LINK_IFELSE([ + AC_LANG_SOURCE([[ +#ifdef HAVE_STDDEF_H +#include <stddef.h> +#endif + +int zero_if_positive(int val) +{ + if (val > 0) + return 0; + ${keyword_chk}; + return 1; +} + +int main(void) +{ + return zero_if_positive(1); +} + ]] + ) + ], + [mhd_cv_cc_kwd_unreachable="$keyword_chk"] + ) + test "x${mhd_cv_cc_kwd_unreachable}" != "xnone" && break + done + ] +) +AS_IF([test "x${mhd_cv_cc_kwd_unreachable}" != "xnone"], + [ + AC_DEFINE_UNQUOTED([MHD_UNREACHABLE_],[$mhd_cv_cc_kwd_unreachable],[Define to keyword used to indicate unreachable code paths]) + AS_IF([test "x${mhd_cv_cc_kwd_unreachable}" = 'xunreachable()'], + AC_DEFINE([MHD_UNREACHABLE_NEEDS_STDDEF_H],[$mhd_cv_cc_kwd_unreachable],[Define to '1' if MHD_UNREACHABLE_() requires include of <stddef.h> header]) + ) + ] +) + +AC_CHECK_HEADERS([stdalign.h], [], [], [AC_INCLUDES_DEFAULT]) +AC_CACHE_CHECK([[for C11 'alignof()' support]], [[mhd_cv_c_alignof]], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[ +#ifdef HAVE_STDALIGN_H +#include <stdalign.h> +#endif + ]], [[ + int var1[(alignof(int) >= 2) ? 1 : -1]; + int var2[alignof(unsigned int) - 1]; + int var3[(alignof(char) > 0) ? 1 : -1]; + int var4[(alignof(long) >= 4) ? 1 : -1]; + + /* Mute compiler warnings */ + var1[0] = var2[0] = var3[0] = 0; + var4[0] = 1; + if (var1[0] + var2[0] + var3[0] == var4[0]) + return 1; + ]]) + ], [ + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[ +#ifdef HAVE_STDALIGN_H +#include <stdalign.h> +#endif + ]], [[ + /* Should fail if 'alignof()' works */ + int var1[alignof(nonexisting_type) - 1]; + + /* Mute compiler warnings */ + var1[0] = 1; + if (var1[0] + 1 == 1) + return 1; + ]]) + ], [[mhd_cv_c_alignof='no']], [[mhd_cv_c_alignof='yes']]) + ], [[mhd_cv_c_alignof='no']]) + ]) +AS_VAR_IF([mhd_cv_c_alignof], ["yes"], + [AC_DEFINE([[HAVE_C_ALIGNOF]], [1], [Define to 1 if your compiler supports 'alignof()'])]) + + +# Check system type +AC_MSG_CHECKING([[for target host OS]]) +os_is_windows="no" +os_is_native_w32="no" +AS_CASE(["$host_os"], + [*darwin* | *rhapsody* | *macosx*], + [AC_DEFINE([OSX],[1],[This is an OS X system]) + mhd_host_os='Darwin' + AC_MSG_RESULT([[$mhd_host_os]])], + [kfreebsd*-gnu], + [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) + AC_DEFINE([FREEBSD],[1],[This is a FreeBSD system]) + mhd_host_os='FreeBSD kernel with GNU userland' + AC_MSG_RESULT([[$mhd_host_os]])], + [freebsd*], + [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) + AC_DEFINE([FREEBSD],[1],[This is a FreeBSD system]) + mhd_host_os='FreeBSD' + AC_MSG_RESULT([[$mhd_host_os]])], + [openbsd*], + [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) + AC_DEFINE([OPENBSD],[1],[This is an OpenBSD system]) + mhd_host_os='OpenBSD' + AC_MSG_RESULT([[$mhd_host_os]])], + [netbsd*], + [AC_DEFINE([SOMEBSD],[1],[This is a BSD system]) + AC_DEFINE([NETBSD],[1],[This is a NetBSD system]) + mhd_host_os='NetBSD' + AC_MSG_RESULT([[$mhd_host_os]])], + [*solaris*], + [AC_DEFINE([SOLARIS],[1],[This is a Solaris system]) + AC_DEFINE([_REENTRANT],[1],[Need with solaris or errno does not work]) + mhd_host_os='Solaris' + AC_MSG_RESULT([[$mhd_host_os]])], + [*linux*], + [AC_DEFINE([LINUX],[1],[This is a Linux kernel]) + mhd_host_os='Linux' + AC_MSG_RESULT([[$mhd_host_os]])], + [*cygwin*], + [AC_DEFINE([CYGWIN],[1],[This is a Cygwin system]) + mhd_host_os='Windows/Cygwin' + AC_MSG_RESULT([[$mhd_host_os]]) + os_is_windows="yes"], + [*mingw*], + [ + AC_DEFINE([MINGW],[1],[This is a MinGW system]) + AC_DEFINE([WINDOWS],[1],[This is a Windows system]) + mhd_host_os='Windows/MinGW' + AC_MSG_RESULT([[$mhd_host_os]]) + AC_CHECK_HEADERS([winsock2.h ws2tcpip.h], [], [AC_MSG_ERROR([[Winsock2 headers are required for W32]])], [AC_INCLUDES_DEFAULT]) + AC_CACHE_CHECK([for MS lib utility], [ac_cv_use_ms_lib_tool], + [mslibcheck=`lib 2>&1` + AS_IF([echo "$mslibcheck" | $GREP -e '^Microsoft (R) Library Manager' - >/dev/null], + [ac_cv_use_ms_lib_tool=yes], + [ac_cv_use_ms_lib_tool=no]) + ]) + AS_IF([test "x$ac_cv_use_ms_lib_tool" = "xyes"], + [AC_SUBST([MS_LIB_TOOL], [[lib]])]) + AC_SUBST([lt_cv_objdir]) + os_is_windows="yes" + os_is_native_w32="yes" + ], + [*openedition*], + [AC_DEFINE([OS390],[1],[This is a OS/390 system]) + mhd_host_os='OS/390' + AC_MSG_RESULT([[$mhd_host_os]])], + [gnu*], + [AC_DEFINE([[GNU_HURD]], [[1]], [Define to `1' if host machine runs on GNU Hurd.]) + mhd_host_os='GNU Hurd' + AC_MSG_RESULT([[$mhd_host_os]]) + ], + [ + AC_MSG_RESULT([unrecognised OS]) + mhd_host_os="${host_os}" + AC_MSG_WARN([Unrecognised OS $host_os]) + AC_DEFINE([OTHEROS],1,[Some strange OS]) + ]) + +AM_CONDITIONAL([CYGWIN_TARGET], [[test "x$os_is_windows" = "xyes" && \ + test "x${os_is_native_w32}" != "xyes"]]) + +AS_VAR_IF([os_is_windows], ["yes"], + [ + AC_MSG_CHECKING([[whether target W32 version is specified by precompiler defines]]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +/* Note: check logic is reversed for easy log reading */ +#ifdef WINVER +#error WINVER is defined +choke me now; +#endif +#ifdef _WIN32_WINNT +#error _WIN32_WINNT is defined +choke me now; +#endif +#ifdef NTDDI +#error NTDDI is defined +choke me now; +#endif + ]],[[(void)0]]) + ], [[mhd_w32_ver_preselect=no]], [[mhd_w32_ver_preselect=yes]] + ) + AC_MSG_RESULT([[${mhd_w32_ver_preselect}]]) + AC_CHECK_HEADERS([windows.h sdkddkver.h], [], [], [AC_INCLUDES_DEFAULT]) + AS_VAR_IF([mhd_w32_ver_preselect],["yes"], + [ + AC_MSG_CHECKING([[for specified target W32 version]]) + AS_UNSET([[mhd_w32_ver]]) + AS_UNSET([[mhd_w32_ver_msg]]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#if _WIN32_WINNT+0 < 0x0501 +/* Check before headers inclusion */ +#error _WIN32_WINNT is less than 0x0501 +choke me now; +#endif + +#ifdef HAVE_SDKDDKVER_H +#include <sdkddkver.h> +#endif +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#if _WIN32_WINNT+0 < 0x0501 +#error _WIN32_WINNT is less than 0x0501 +choke me now; +#endif + ]],[[(void)0]]) + ], [], [ + AC_MSG_RESULT([[pre-WinXP]]) + AC_MSG_ERROR([[libmicrohttpd cannot be compiled for Windows version before Windows XP]]) + ] + ) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#ifdef HAVE_SDKDDKVER_H +#include <sdkddkver.h> +#endif +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#if _WIN32_WINNT+0 == 0x0501 +#error _WIN32_WINNT is 0x0501 +choke me now; +#endif +#if _WIN32_WINNT+0 == 0x0502 +#error _WIN32_WINNT is 0x0502 +choke me now; +#endif + ]],[[(void)0]]) + ], [], [ + mhd_w32_ver="WinXP" + mhd_w32_ver_msg="WinXP (selected by precompiler flags)" + ] + ) + AS_VAR_SET_IF([mhd_w32_ver], [], + [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#ifdef HAVE_SDKDDKVER_H +#include <sdkddkver.h> +#endif +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#if _WIN32_WINNT+0 < 0x0600 +#error _WIN32_WINNT is less than 0x0600 but greater than 0x0502 +choke me now; +#endif + ]],[[(void)0]]) + ], [], [ + AC_MSG_ERROR([[_WIN32_WINNT value is wrong (less than 0x0600 but greater than 0x0502)]]) + ] + ) + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#ifdef HAVE_SDKDDKVER_H +#include <sdkddkver.h> +#endif +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#if _WIN32_WINNT+0 == 0x0600 +#error _WIN32_WINNT is 0x0600 +choke me now; +#endif + ]],[[(void)0]]) + ], [], [ + mhd_w32_ver="Vista" + mhd_w32_ver_msg="Vista (selected by precompiler flags)" + ] + ) + ] + ) + + AS_VAR_SET_IF([mhd_w32_ver], [], + [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#ifdef HAVE_SDKDDKVER_H +#include <sdkddkver.h> +#endif +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#if _WIN32_WINNT+0 > 0x0600 +#error _WIN32_WINNT is greater than 0x0600 +choke me now; +#endif + ]],[[(void)0]]) + ], [ + mhd_w32_ver="unknown" + mhd_w32_ver_msg="unknown (cannot be detected)" + ], [ + mhd_w32_ver="newer than Vista" + mhd_w32_ver_msg="newer than Vista (selected by precompiler flags)" + ] + ) + ] + ) + AC_MSG_RESULT([[${mhd_w32_ver}]]) + ], [ + mhd_w32_ver="Vista" + mhd_w32_ver_msg="Vista (default, override by CPPFLAGS=-D_WIN32_WINNT=0xNNNN)" + CPPFLAGS_ac="${CPPFLAGS_ac} -D_WIN32_WINNT=0x0600" + CPPFLAGS="${CPPFLAGS_ac} ${user_CPPFLAGS}" + AC_MSG_CHECKING([[whether headers accept _WIN32_WINNT=0x0600]]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#ifdef HAVE_SDKDDKVER_H +#include <sdkddkver.h> +#endif +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif +#include <stdio.h> + ]],[[(void)0]]) + ], [ + AC_MSG_RESULT([[yes]]) + ], [ + AC_MSG_RESULT([[no]]) + AC_MSG_ERROR([Headers do not accept _WIN32_WINNT=0x0600. Consider override target W32 version by CPPFLAGS=-D_WIN32_WINNT=0xNNNN]) + ] + ) + ] + ) + ] +) + +AS_IF([test "x${os_is_windows}" = "xyes" && test "x${os_is_native_w32}" = "xyes"], + [ + AC_CACHE_CHECK([W32 run-time library type], [mhd_cv_wctr_type], + [ + AC_EGREP_CPP([MHDMARKER: UCRT run-time library in use!], [ +#include <stdio.h> +#if defined(_UCRT) +#define CRT_STR "MHDMARKER: UCRT run-time library in use!" +#endif +#if defined(__MSVCRT_VERSION__) +#if (__MSVCRT_VERSION__ >= 0xE00) && (__MSVCRT_VERSION__ < 0x1000) +#define CRT_STR "MHDMARKER: UCRT run-time library in use!" +#endif +#if (__MSVCRT_VERSION__ > 0x1400) +#define CRT_STR "MHDMARKER: UCRT run-time library in use!" +#endif +#endif + +#ifndef CRT_STR +#define CRT_STR "MHDMARKER: MSVCRT run-time library in use!" +#endif + +int main(void) +{ + printf ("%\n", CRT_STR); + return 0; +} + ], + [mhd_cv_wctr_type="ucrt"], [mhd_cv_wctr_type="msvcrt"]) + ] + ) + mhd_host_os="${mhd_host_os}-${mhd_cv_wctr_type}" + AS_VAR_IF([mhd_cv_wctr_type], ["msvcrt"], + [ + # Use CFLAGS here to override user-supplied wrong CPPFLAGS. Durty trick, but choice is limited. + AX_APPEND_COMPILE_FLAGS([-U__USE_MINGW_ANSI_STDIO -D__USE_MINGW_ANSI_STDIO=0], [CFLAGS_ac]) + AC_SUBST([W32CRT], [MSVCRT]) + ], [AC_SUBST([W32CRT], [UCRT])] + ) + + CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" + LDFLAGS="${user_LDFLAGS}" + AS_CASE([$mhd_w32_ver], + [WinXP], + [MHD_CHECK_ADD_CC_LDFLAG([-Wl,--major-subsystem-version,5,--minor-subsystem-version,1],[LDFLAGS_ac])], + [Vista], + [MHD_CHECK_ADD_CC_LDFLAG([-Wl,--major-subsystem-version,6,--minor-subsystem-version,0],[LDFLAGS_ac])] + ) + LDFLAGS="${LDFLAGS_ac} ${user_LDFLAGS}" + ] +) +CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" + + +AC_ARG_WITH([threads], + [AS_HELP_STRING([--with-threads=LIB],[choose threading library (posix, w32, auto, none) [auto]])], + [], [with_threads='auto']) +AS_CASE([[$with_threads]], + [[win32]], [[with_threads='w32']], + [[pthreads]], [[with_threads='posix']], + [[posix]], [[:]], + [[w32]], [[:]], + [[none]], [[with_threads='none']], + [[no]], [[with_threads='none']], + [[auto]], [[:]], + [AC_MSG_ERROR([[incorrect parameter "$with_threads" specified for --with-threads]])] +) # Check for posix threads support, regardless of configure parameters as # testsuite uses only posix threads. @@ -1354,7 +1809,7 @@ AS_IF([test "x$USE_THREADS" = "xposix"], [AC_DEFINE([MHD_USE_W32_THREADS],[1],[define to use W32 threads])])]) AM_CONDITIONAL([USE_POSIX_THREADS], [test "x$USE_THREADS" = "xposix"]) AM_CONDITIONAL([USE_W32_THREADS], [test "x$USE_THREADS" = "xw32"]) -AM_CONDITIONAL([USE_THREADS], [test "x$USE_THREADS" != "xnone"]) +AM_CONDITIONAL([MHD_USE_THREADS], [test "x$USE_THREADS" != "xnone"]) AM_CONDITIONAL([DISABLE_THREADS], [test "x$USE_THREADS" = "xnone"]) AC_MSG_RESULT([$USE_THREADS]) @@ -1601,6 +2056,8 @@ MHD_FIND_LIB([socket], #endif #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> #endif #ifdef HAVE_SOCKLIB_H #include <sockLib.h> @@ -2146,14 +2603,14 @@ AC_CACHE_CHECK([whether socket value is a signed type],[mhd_cv_socket_signed], /* Keep in sync with microhttpd.h */ #if ! defined(_WIN32) || defined(_SYS_TYPES_FD_SET) -typedef int MHD_socket; +typedef int MHD_Socket; #else /* defined(_WIN32) && ! defined(_SYS_TYPES_FD_SET) */ -typedef SOCKET MHD_socket; +typedef SOCKET MHD_Socket; #endif /* defined(_WIN32) && ! defined(_SYS_TYPES_FD_SET) */ int main(void) { - int test_arr[2 - 5*(!!(0 < ((MHD_socket)-1)))]; + int test_arr[2 - 5*(!!(0 < ((MHD_Socket)-1)))]; test_arr[1] = 0; return test_arr[1]; } @@ -2187,6 +2644,8 @@ MHD_FIND_LIB([sendmsg], #endif #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> #endif #ifdef HAVE_SOCKLIB_H #include <sockLib.h> @@ -2231,7 +2690,7 @@ AC_C_VARARRAYS AC_CACHE_CHECK([[whether __func__ magic-macro is available]], [[mhd_cv_macro___func___avail]], [dnl - AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <stddef.h>]],[[const char *funcname = __func__ ; if (NULL == funcname) return 1;]])], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[const char *funcname = __func__ ; if (!funcname) return 1;]])], [[mhd_cv_macro___func___avail="yes"]],[[mhd_cv_macro___func___avail="no"]]) ]) AS_VAR_IF([mhd_cv_macro___func___avail], ["yes"], @@ -2239,7 +2698,7 @@ AS_VAR_IF([mhd_cv_macro___func___avail], ["yes"], [ AC_CACHE_CHECK([[whether __FUNCTION__ magic-macro is available]], [[mhd_cv_macro___function___avail]], [dnl - AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <stddef.h>]],[[const char *funcname = __FUNCTION__ ; if (NULL == funcname) return 1;]])], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[const char *funcname = __FUNCTION__ ; if (!funcname) return 1;]])], [[mhd_cv_macro___function___avail="yes"]],[[mhd_cv_macro___function___avail="no"]]) ]) AS_VAR_IF([mhd_cv_macro___function___avail], ["yes"], @@ -2247,7 +2706,7 @@ AS_VAR_IF([mhd_cv_macro___func___avail], ["yes"], [ AC_CACHE_CHECK([[whether __PRETTY_FUNCTION__ magic-macro is available]], [[mhd_cv_macro___pretty_function___avail]], [dnl - AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <stddef.h>]],[[const char *funcname = __PRETTY_FUNCTION__ ; if (NULL == funcname) return 1;]])], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[const char *funcname = __PRETTY_FUNCTION__ ; if (!funcname) return 1;]])], [[mhd_cv_macro___pretty_function___avail="yes"]],[[mhd_cv_macro___pretty_function___avail="no"]]) ]) AS_VAR_IF([mhd_cv_macro___pretty_function___avail], ["yes"], @@ -2259,14 +2718,14 @@ AS_VAR_IF([mhd_cv_macro___func___avail], ["yes"], ) AC_CACHE_CHECK([[whether __builtin_bswap32() is available]], [[mhd_cv_func___builtin_bswap32_avail]], [dnl - AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include<stdint.h>]], [[uint32_t a = 1; uint32_t b = __builtin_bswap32(a); a = b; (void) a;]])], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include<stdint.h>]], [[uint_fast32_t a = 1; uint_fast32_t b = __builtin_bswap32(a); a = b; (void) a;]])], [[mhd_cv_func___builtin_bswap32_avail="yes"]],[[mhd_cv_func___builtin_bswap32_avail="no"]]) ]) AS_IF([[test "x$mhd_cv_func___builtin_bswap32_avail" = "xyes"]], [AC_DEFINE([[MHD_HAVE___BUILTIN_BSWAP32]], [[1]], [Define to 1 if you have __builtin_bswap32() builtin function])]) AC_CACHE_CHECK([[whether __builtin_bswap64() is available]], [[mhd_cv_func___builtin_bswap64_avail]], [dnl - AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include<stdint.h>]], [[uint64_t a = 1; uint64_t b = __builtin_bswap64(a); a = b; (void) a;]])], + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include<stdint.h>]], [[uint_fast64_t a = 1; uint_fast64_t b = __builtin_bswap64(a); a = b; (void) a;]])], [[mhd_cv_func___builtin_bswap64_avail="yes"]], [[mhd_cv_func___builtin_bswap64_avail="no"]]) ]) AS_IF([[test "x$mhd_cv_func___builtin_bswap64_avail" = "xyes"]], @@ -2344,70 +2803,166 @@ AM_CONDITIONAL([HEAVY_TESTS],[test "x$use_heavy_tests" = "xyes"]) AM_CONDITIONAL([VHEAVY_TESTS],[test "x$use_vheavy_tests" = "xyes"]) AM_CONDITIONAL([TESTS_STRESS_OS],[false]) +AC_ARG_ENABLE([[select]], + [ + AS_HELP_STRING([[--enable-select[=ARG]]], [enable 'select()' support (yes, no, auto) [auto]]) + ],[],[enable_select='auto'] +) +AC_CACHE_CHECK([for select() function],[mhd_cv_func_select], + [ + AS_IF([test "x$os_is_native_w32" = "xyes"], + [mhd_cv_func_select="yes"], + [ + AC_LINK_IFELSE( + [ + AC_LANG_SOURCE([[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#ifdef HAVE_SELECTLIB_H +#include <selectLib.h> +#endif + +int main(void) +{ + int res; + fd_set rfds; + fd_set wfds; + fd_set efds; + struct timeval tv_to; + + tv_to.tv_sec = 1; + tv_to.tv_usec = 0; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET(1, &wfds); + + res = select(2, &rfds, &wfds, &efds, &tv_to); + if (res < 0) + return 3; + + FD_CLR(1, &efds); + if (FD_ISSET(1, &efds)) + return 4; + + return 0; +} + ]] + ) + ], + [mhd_cv_func_select="yes"], + [mhd_cv_func_select="no"] + ) + ] + ) + ] +) +AS_VAR_IF([mhd_cv_func_select],["yes"], + [ + AC_DEFINE([[HAVE_SELECT]],[[1]],[Define to '1' if select() is supported on your platform]) + AS_IF([test "x$enable_select" != "xno"], + [ + enable_select="yes" + AC_DEFINE([[MHD_USE_SELECT]],[[1]],[Define to '1' to enable use of select() system call]) + ] + ) + ], + [ + AS_VAR_IF([enable_select],["yes"], + [AC_MSG_ERROR([Support for select() was explicitly requested but cannot be enabled on this platform.])] + ) + ] +) +AM_CONDITIONAL([MHD_USE_SELECT],[test "x${enable_select}" = "xyes"]) + AC_ARG_ENABLE([[poll]], - [AS_HELP_STRING([[--enable-poll[=ARG]]], [enable poll support (yes, no, auto) [auto]])], - [enable_poll=${enableval}], - [enable_poll='auto'] - ) - -AS_IF([test "$enable_poll" != "no"], [ - AS_IF([test "$os_is_native_w32" != "yes"], + AS_HELP_STRING([[--enable-poll[=ARG]]], [enable 'poll()' support (yes, no, auto) [auto]]) + ],[],[enable_poll='auto'] +) +AS_IF([test "$os_is_native_w32" != "yes"], + [ + AC_CHECK_HEADERS([poll.h], [ - AC_CHECK_HEADERS([poll.h], - [ - MHD_CHECK_FUNC([poll], - [[ + MHD_CHECK_FUNC([poll], + [[ #include <poll.h> - ]], - [[ + ]], + [[ struct pollfd fds[2]; fds[0].fd = 0; fds[0].events = POLLIN; if (0 > poll(fds, 1, 0)) return 2; - ]], - [have_poll='yes'], [have_poll='no'] - ) - ], [], [AC_INCLUDES_DEFAULT] + ]], + [have_poll='yes'],[have_poll='no'] ) - ], - [ - MHD_CHECK_FUNC([WSAPoll], - [[ + ],[],[AC_INCLUDES_DEFAULT] + ) + ], + [ + MHD_CHECK_FUNC([WSAPoll], + [[ #include <winsock2.h> - ]], - [[ + ]], + [[ WSAPOLLFD fda[2]; WSAPoll(fda, 2, 0); - ]], - [ - have_poll='yes' - AC_DEFINE([HAVE_POLL],[1]) - ], - [have_poll='no'] - ) + ]], + [have_poll='yes'],[have_poll='no'] + ) + ] +) +AS_VAR_IF([have_poll],["yes"], + [ + AC_DEFINE([[HAVE_POLL]],[[1]],[Define to '1' if poll() is supported on your platform]) + AC_CHECK_DECLS([POLLRDNORM,POLLIN,POLLRDBAND,POLLWRNORM,POLLOUT,POLLWRBAND,POLLPRI],[],[],[[ +#ifdef HAVE_POLL_H +#include <poll.h> +#endif +#ifdef HAVE_WINSOCK2_H +#include <winsock2.h> +#endif + ]] + ) + AS_IF([test "x$enable_poll" != "xno"], + [ + enable_poll="yes" + AC_DEFINE([[MHD_USE_POLL]],[[1]],[Define to '1' to enable use of select() system call]) ] ) - AS_IF([test "$enable_poll" = "yes" && test "$have_poll" != "yes"], - [AC_MSG_ERROR([[Support for poll was explicitly requested but cannot be enabled on this platform.]])]) - enable_poll="$have_poll" + ], + [ + AS_VAR_IF([enable_poll],["yes"], + [AC_MSG_ERROR([Support for poll() was explicitly requested but cannot be enabled on this platform.])] + ) ] ) +AM_CONDITIONAL([MHD_USE_POLL],[test "x${enable_poll}" = "xyes"]) AC_ARG_ENABLE([[epoll]], - [AS_HELP_STRING([[--enable-epoll[=ARG]]], [enable epoll support (yes, no, auto) [auto]])], - [enable_epoll=${enableval}], - [enable_epoll='auto'] - ) - + [ + AS_HELP_STRING([[--enable-epoll[=ARG]]], [enable epoll support (yes, no, auto) [auto]]) + ],[],[enable_epoll='auto'] +) AS_IF([test "$enable_epoll" != "no"], [ AX_HAVE_EPOLL AS_IF([test "${ax_cv_have_epoll}" = "yes"], [ - AC_DEFINE([[EPOLL_SUPPORT]],[[1]],[Define to 1 to enable epoll support]) + AC_DEFINE([[HAVE_EPOLL]],[[1]],[Define to '1' if epoll is supported on your platform]) + AC_DEFINE([[MHD_USE_EPOLL]],[[1]],[Define to '1' to enable 'epoll' functionality]) enable_epoll='yes' ], [ @@ -2419,8 +2974,7 @@ AS_IF([test "$enable_epoll" != "no"], ) ] ) - -AM_CONDITIONAL([MHD_HAVE_EPOLL], [[test "x$enable_epoll" = xyes]]) +AM_CONDITIONAL([MHD_USE_EPOLL], [[test "x${enable_epoll}" = "xyes"]]) AS_IF([test "x$enable_epoll" = "xyes"], [ @@ -2440,7 +2994,9 @@ AC_CACHE_CHECK([for supported 'noreturn' keyword], [mhd_cv_decl_noreturn], [ mhd_cv_decl_noreturn="none" CFLAGS="${CFLAGS_ac} ${user_CFLAGS} ${errattr_CFLAGS}" - for decl_noret in '_Noreturn' '__attribute__((__noreturn__))' '__declspec(noreturn)' + MHD_SAVED_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag=yes + for decl_noret in ['[[noreturn]]'] '_Noreturn' '__attribute__((__noreturn__))' '__declspec(noreturn)' do AC_LINK_IFELSE([AC_LANG_SOURCE( [[ @@ -2469,12 +3025,14 @@ int main (int argc, char *const *argv) ) AS_IF([test "x${mhd_cv_decl_noreturn}" != "xnone"], [break]) done - CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" + ac_c_werror_flag="$MHD_SAVED_ac_c_werror_flag" + AS_UNSET([MHD_SAVED_ac_c_werror_flag]) + CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" ] ) AS_VAR_IF([mhd_cv_decl_noreturn], ["none"], - [AC_DEFINE([_MHD_NORETURN], [], [Define to supported 'noreturn' function declaration])], - [AC_DEFINE_UNQUOTED([_MHD_NORETURN], [${mhd_cv_decl_noreturn}], [Define to supported 'noreturn' function declaration])] + [AC_DEFINE([MHD_NORETURN_], [[/* empty */]], [Define to the supported 'noreturn' function declaration])], + [AC_DEFINE_UNQUOTED([MHD_NORETURN_], [${mhd_cv_decl_noreturn}], [Define to the supported 'noreturn' function declaration])] ) # Check for types sizes @@ -2509,8 +3067,8 @@ static struct timeval test_var; ) AC_DEFINE_UNQUOTED([SIZEOF_STRUCT_TIMEVAL_TV_SEC], [$mhd_cv_size_timeval_tv_sec], [The size of `tv_sec' member of `struct timeval', as computed by sizeof]) -AC_CHECK_SIZEOF([int64_t], [], [[#include <stdint.h>]]) -AC_CHECK_SIZEOF([uint64_t], [], [[#include <stdint.h>]]) +AC_CHECK_SIZEOF([int_fast64_t], [], [[#include <stdint.h>]]) +AC_CHECK_SIZEOF([uint_fast64_t], [], [[#include <stdint.h>]]) AC_CHECK_SIZEOF([int], [], [[#include <stdint.h>]]) AC_CHECK_SIZEOF([unsigned int], [], [[#include <stdint.h>]]) AC_CHECK_SIZEOF([unsigned long long], [], [[#include <stdint.h>]]) @@ -2560,6 +3118,8 @@ AC_CHECK_MEMBERS([struct sockaddr.sa_len, struct sockaddr_storage.ss_len, #endif #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> #endif #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> @@ -2663,6 +3223,80 @@ int main(void) [AC_DEFINE([[MHD_USE_GETSOCKNAME]], [[1]], [Define if you have usable `getsockname' function.])] ) +AS_VAR_IF([[os_is_native_w32]], [["yes"]],[], + [ + MHD_CHECK_FUNC([socketpair], + [[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + ]], + [[ + int arr[2]; + i][f (0 != socketpair(0, SOCK_STREAM, 0, arr)) return 2; + close (arr[1]); + close (arr[0]); + ]], + [], + [AS_UNSET([mhd_cv_socketpair_usable])] + ) + ] +) + +AC_CHECK_DECLS([AF_UNIX,AF_LOCAL,SOMAXCONN,IPV6_V6ONLY,SO_REUSEADDR,SO_REUSEPORT,\ + TCP_NODELAY,TCP_FASTOPEN],[],[], + [[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif +#ifdef HAVE_SOCKLIB_H +#include <sockLib.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_WINSOCK2_H +#include <winsock2.h> +#endif +#ifdef HAVE_INETLIB_H +#include <inetLib.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#if !defined(HAVE_NETINET_IN_H) && !defined(HAVE_ARPA_INET_H) && defined(HAVE_NETDB_H) +#include <netdb.h> +#endif +#ifdef HAVE_NETINET_TCP_H +#include <netinet/tcp.h> +#endif +#ifdef HAVE_AFUNIX_H +#include <afunix.h> +#endif +#ifdef HAVE_WS2TCPIP_H +#include <ws2tcpip.h> +#endif + ]] +) + AC_CACHE_CHECK([for usable PAGESIZE macro], [mhd_cv_macro_pagesize_usable], [ AC_LINK_IFELSE( @@ -2763,543 +3397,1085 @@ choke me now ) AS_VAR_IF([[mhd_cv_macro_page_size_usable]], [["yes"]], [ - AC_DEFINE([[MHD_USE_PAGE_SIZE_MACRO]],[[1]],[Define if you have usable PAGE_SIZE macro]) - AC_CACHE_CHECK([whether PAGE_SIZE macro could be used for static init], [mhd_cv_macro_page_size_usable_static], + AC_DEFINE([[MHD_USE_PAGE_SIZE_MACRO]],[[1]],[Define if you have usable PAGE_SIZE macro]) + AC_CACHE_CHECK([whether PAGE_SIZE macro could be used for static init], [mhd_cv_macro_page_size_usable_static], + [ + AC_LINK_IFELSE( + [ + AC_LANG_PROGRAM( + [[ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifndef PAGE_SIZE +#error No PAGE_SIZE macro defined +choke me now +#endif +static long ac_pgsz = PAGE_SIZE + 0; + ]], + [[ + if (1 > ac_pgsz) return 1; + ]] + ) + ], + [[mhd_cv_macro_page_size_usable_static="yes"]], [[mhd_cv_macro_page_size_usable_static="no"]] + ) + ] + ) + AS_VAR_IF([[mhd_cv_macro_page_size_usable_static]], [["yes"]], + [AC_DEFINE([[MHD_USE_PAGE_SIZE_MACRO_STATIC]],[[1]],[Define if you have PAGE_SIZE macro usable for static init])] + ) + ] + ) + ] +) + +# Check for inter-thread signaling type +AC_ARG_ENABLE([[itc]], + [AS_HELP_STRING([[--enable-itc=TYPE]], [use TYPE of inter-thread communication (pipe, socketpair, eventfd) [auto]])], [], + [[enable_itc='auto']] +) + +AS_CASE([[$enable_itc]], + [[pipe]], [[:]], + [[socketpair]], [[:]], + [[eventfd]], [[:]], + [[auto]], [AS_VAR_IF([[os_is_windows]], [["yes"]], [[enable_itc='socketpair']])], + [[eventFD]], [[enable_itc='eventfd']], + [[socket]], [[enable_itc='socketpair']], + [[no]], [AC_MSG_ERROR([[inter-thread communication cannot be disabled]])], + [AC_MSG_ERROR([[unrecognized type "$enable_itc" of inter-thread communication specified by "--enable-itc=$enable_itc"]])] +) +AS_UNSET([[use_itc]]) + +AS_IF([[test "x$enable_itc" = "xeventfd" || test "x$enable_itc" = "xauto"]], + [ + MHD_CHECK_LINK_RUN([[f][or working eventfd(2)]],[[mhd_cv_eventfd_usable]],[[mhd_cv_eventfd_usable='assuming no']], + [ + AC_LANG_SOURCE([[ +#include <sys/eventfd.h> +#include <unistd.h> + +int main(void) +{ + unsigned char buf[8]; + int ret; + int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (0 > efd) + return 2; + ret = 0; + buf[3] = 1; + if (8 != write(efd, buf, 8)) + ret = 3; + else + { + if (8 != read(efd, buf, 8)) + ret = 4; + } + close(efd); + return ret; +} + ]] + ) + ], + [ + use_itc='eventfd' + enable_itc="$use_itc" + AC_DEFINE([[MHD_ITC_EVENTFD_]], [[1]], [Define to use eventFD for inter-thread communication]) + ], + [ + AS_VAR_IF([[enable_itc]], [["eventfd"]], [AC_MSG_ERROR([[eventfd(2) is not usable, consider using other type of inter-thread communication]])]) + ] + ) + AS_VAR_IF([mhd_cv_eventfd_usable],["assuming no"], + [AC_MSG_WARN([if you have 'eventfd' support enabled on your target system consider overriding test result by "mhd_cv_eventfd_usable=yes" configure parameter])] + ) + ] +) + +AS_IF([[test "x$enable_itc" = "xpipe" || test "x$enable_itc" = "xauto"]], [ + AS_VAR_IF([[os_is_native_w32]], [["yes"]], [], [ + AC_CACHE_CHECK([[whether pipe(3) is usable]], [[mhd_cv_pipe_usable]], [ + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ +AC_INCLUDES_DEFAULT +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + ], [[ + int arr[2]; + int res; + res = pipe(arr); + if (res != 0) return 33; + close (arr[0]); + close (arr[1]); + ]]) + ], [[mhd_cv_pipe_usable='yes']], [[mhd_cv_pipe_usable='no']]) + ]) + AS_VAR_IF([[mhd_cv_pipe_usable]], [["yes"]], [ + use_itc='pipe' + enable_itc="$use_itc" + AC_DEFINE([[MHD_ITC_PIPE_]], [[1]], [Define to use pipe for inter-thread communication]) + MHD_CHECK_LINK_RUN([[whether pipe2(2) is usable]],[[mhd_cv_pipe2_usable]], + [ + # Cross-compiling + AS_CASE([${host_os}], [kfreebsd*-gnu], [[mhd_cv_pipe2_usable='assuming no']], + [[mhd_cv_pipe2_usable='assuming yes']]) + ], + [ + AC_LANG_PROGRAM([ +AC_INCLUDES_DEFAULT +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + ], [[ + int arr[2]; + int res; + res = pipe2(arr, O_CLOEXEC | O_NONBLOCK); + if (res != 0) return 33; + close (arr[0]); + close (arr[1]); + ]] + ) + ], + [AC_DEFINE([[HAVE_PIPE2_FUNC]], [[1]], [Define if you have usable pipe2(2) function])] + ) + ], [ + AS_VAR_IF([[enable_itc]], [["pipe"]], [AC_MSG_ERROR([[pipe(3) is not usable, consider using other type of inter-thread communication]])]) + ]) + ]) +]) + +AS_IF([[test "x$enable_itc" = "xsocketpair" || test "x$enable_itc" = "xauto"]], + [ + AS_IF([test "x${os_is_native_w32}" = "xyes"], + [mhd_cv_socketpair_usable='yes'], + [test "x${mhd_cv_func_socketpair}" = "xyes"], + [ + AC_CACHE_CHECK([[whether socketpair(3) is usable]], [[mhd_cv_socketpair_usable]], [ - AC_LINK_IFELSE( - [ + AC_LINK_IFELSE([ AC_LANG_PROGRAM( [[ -#ifdef HAVE_UNISTD_H -#include <unistd.h> +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> #endif -#ifdef HAVE_LIMITS_H -#include <limits.h> +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> #endif -#ifdef HAVE_SYS_PARAM_H -#include <sys/param.h> +#if defined(HAVE_UNISTD_H) +#include <unistd.h> #endif -#ifndef PAGE_SIZE -#error No PAGE_SIZE macro defined -choke me now +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> #endif -static long ac_pgsz = PAGE_SIZE + 0; - ]], - [[ - if (1 > ac_pgsz) return 1; + ]], [[ + int arr[2]; + int res; +#if defined(AF_UNIX) + res = socketpair(AF_UNIX, SOCK_STREAM, 0, arr); +#elif defined(AF_LOCAL) + res = socketpair(AF_LOCAL, SOCK_STREAM, 0, arr); +#else +#error AF_LOCAL and AF_UNIX are both undefined + choke me now; +#endif + if (res != 0) return 1; + close (arr[1]); + close (arr[0]); ]] ) ], - [[mhd_cv_macro_page_size_usable_static="yes"]], [[mhd_cv_macro_page_size_usable_static="no"]] + [[mhd_cv_socketpair_usable='yes']], + [[mhd_cv_socketpair_usable='no']] ) ] ) - AS_VAR_IF([[mhd_cv_macro_page_size_usable_static]], [["yes"]], - [AC_DEFINE([[MHD_USE_PAGE_SIZE_MACRO_STATIC]],[[1]],[Define if you have PAGE_SIZE macro usable for static init])] + ], + [AS_UNSET([mhd_cv_socketpair_usable])] + ) + + AS_VAR_IF([[mhd_cv_socketpair_usable]], [["yes"]], + [ + use_itc='socketpair' + enable_itc="$use_itc" + AC_DEFINE([[MHD_ITC_SOCKETPAIR_]], [[1]], [Define to use socketpair for inter-thread communication]) + ], + [ + AS_VAR_IF([[enable_itc]], [["socketpair"]], [ + AC_MSG_ERROR([[socketpair(3) is not usable, consider using other type of inter-thread communication]])] ) ] ) ] ) -# Check for inter-thread signaling type -AC_ARG_ENABLE([[itc]], - [AS_HELP_STRING([[--enable-itc=TYPE]], [use TYPE of inter-thread communication (pipe, socketpair, eventfd) [auto]])], [], - [[enable_itc='auto']] +AS_IF([[test -z "$use_itc"]], [AC_MSG_ERROR([[cannot find usable type of inter-thread communication]])]) + + +MHD_CHECK_FUNC([accept4], + [[ +#if defined(HAVE_SYS_TYPES_H) +# include <sys/types.h> +#endif +#include <sys/socket.h> + ]], + [[ + struct sockaddr sk_addr; + socklen_t addr_size; + i][f (0 > accept4(0, &sk_addr, &addr_size, 0)) + return 3; + ]] ) +MHD_CHECK_FUNC([gmtime_r], + [[ +#if defined(HAVE_SYS_TYPES_H) +# include <sys/types.h> +#endif +#include <time.h> + ]], + [[ + time_t timer = (time_t) 0; + struct tm res; -AS_CASE([[$enable_itc]], - [[pipe]], [[:]], - [[socketpair]], [[:]], - [[eventfd]], [[:]], - [[auto]], [AS_VAR_IF([[os_is_windows]], [["yes"]], [[enable_itc='socketpair']])], - [[eventFD]], [[enable_itc='eventfd']], - [[socket]], [[enable_itc='socketpair']], - [[no]], [AC_MSG_ERROR([[inter-thread communication cannot be disabled]])], - [AC_MSG_ERROR([[unrecognized type "$enable_itc" of inter-thread communication specified by "--enable-itc=$enable_itc"]])] + i][f (&res != gmtime_r(&timer, &res)) + return 3; + ]] +) +MHD_CHECK_FUNC([memmem], + [[ +#if defined(HAVE_STDDEF_H) +# include <stddef.h> +#elif defined(HAVE_STDLIB_H) +# include <stdlib.h> +#endif /* HAVE_STDLIB_H */ +#include <string.h> + ]], + [[ + const char *haystack = "abc"; + size_t hslen = 3; + const char *needle = "b"; + size_t needlelen = 1; + + i][f ((haystack + 1) != memmem(haystack, hslen, needle, needlelen)) + return 3; + ]] +) +MHD_CHECK_FUNC([snprintf], + [[ +#include <stdio.h> + ]], + [[ + char buf[2]; + + i][f (1 != snprintf(buf, 2, "a")) + return 3; + /* Do not use the next check to avoid compiler warning */ + /* i][f (4 != snprintf(buf, 2, "abcd")) + return 4; */ + ]] +) +AC_CHECK_DECL([gmtime_s], + [ + AC_MSG_CHECKING([[whether gmtime_s is in C11 form]]) + AC_LINK_IFELSE( + [ AC_LANG_PROGRAM( + [[ +#define __STDC_WANT_LIB_EXT1__ 1 +#include <time.h> +#ifdef __cplusplus +extern "C" +#endif + struct tm* gmtime_s(const time_t* time, struct tm* result); + ]], [[ + static struct tm res; + static time_t t = 0; + gmtime_s (&t, &res); + ]]) + ], + [ + AC_DEFINE([HAVE_C11_GMTIME_S], [1], [Define to 1 if you have the `gmtime_s' function in C11 form.]) + AC_MSG_RESULT([[yes]]) + ], + [ + AC_MSG_RESULT([[no]]) + AC_MSG_CHECKING([[whether gmtime_s is in W32 form]]) + AC_LINK_IFELSE( + [ AC_LANG_PROGRAM( + [[ +#include <time.h> +#ifdef __cplusplus +extern "C" +#endif +errno_t gmtime_s(struct tm* _tm, const time_t* time); + ]], [[ + static struct tm res; + static time_t t = 0; + gmtime_s (&res, &t); + ]]) + ], + [ + AC_DEFINE([HAVE_W32_GMTIME_S], [1], [Define to 1 if you have the `gmtime_s' function in W32 form.]) + AC_MSG_RESULT([[yes]]) + ], + [AC_MSG_RESULT([[no]]) + ]) + ]) + ], [], + [[#define __STDC_WANT_LIB_EXT1__ 1 +#include <time.h>]]) + + +AC_CHECK_DECL([SOCK_NONBLOCK], [AC_DEFINE([HAVE_SOCK_NONBLOCK], [1], [SOCK_NONBLOCK is defined in a socket header])], [], + [[ +#if defined(HAVE_SYS_TYPES_H) +# include <sys/types.h> +#endif +#if defined(HAVE_SYS_SOCKET_H) +# include <sys/socket.h> +#elif defined(HAVE_WINSOCK2_H) +# include <winsock2.h> +#elif defined(HAVE_UNISTD_H) +# include <unistd.h> +#endif + ]] +) + +MHD_FIND_LIB([clock_gettime],[[#include <time.h>]], + [[ + struct timespec tp; + i][f (0 > clock_gettime(CLOCK_REALTIME, &tp)) + return 3; + ]], + [rt], + [ + AC_DEFINE([HAVE_CLOCK_GETTIME], [1], [Define to '1' if you have clock_gettime() function]) + AS_VAR_IF([[mhd_cv_find_lib_clock_gettime]],[["none required"]], [], + [ + MHD_LIBDEPS_PKGCFG="${mhd_cv_find_lib_clock_gettime} $MHD_LIBDEPS_PKGCFG" + ] + ) + ],[], + [MHD_LIBDEPS] +) + +MHD_CHECK_FUNC([clock_get_time], + [[ +#include <mach/clock.h> +#include <mach/mach.h> + ]], + [[ + clock_serv_t cs; + mach_timespec_t mt; + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cs); + clock_get_time(cs, &mt); + mach_port_deallocate(mach_task_self(), cs); + ]] +) + +MHD_CHECK_FUNC([gethrtime], + [[ +#ifdef HAVE_SYS_TIME_H +/* Solaris define gethrtime() in sys/time.h */ +#include <sys/time.h> +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_TIME_H +/* HP-UX define gethrtime() in time.h */ +#include <time.h> +#endif /* HAVE_TIME_H */ + ]], + [[ + hrtime_t hrt = gethrtime(); + i][f (0 == hrt) + return 3; + ]] +) + +AS_VAR_IF([ac_cv_header_time_h], ["yes"], + [ + MHD_CHECK_FUNC([timespec_get], + [[ +#include <time.h> + +#ifndef TIME_UTC +#error TIME_UTC must be defined to use timespec_get() +choke me now +#endif + ]], + [[ + struct timespec ts; + i][f (TIME_UTC != timespec_get (&ts, TIME_UTC)) + return 3; + ]] + ) + ] ) -AS_UNSET([[use_itc]]) -AS_IF([[test "x$enable_itc" = "xeventfd" || test "x$enable_itc" = "xauto"]], +MHD_CHECK_FUNC_GETTIMEOFDAY + +# IPv6 +AC_CACHE_CHECK([for IPv6],[mhd_cv_have_inet6], [ - MHD_CHECK_LINK_RUN([[f][or working eventfd(2)]],[[mhd_cv_eventfd_usable]],[[mhd_cv_eventfd_usable='assuming no']], - [ - AC_LANG_SOURCE([[ -#include <sys/eventfd.h> + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <stdio.h> +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#elif defined(HAVE_UNISTD_H) #include <unistd.h> - -int main(void) -{ - unsigned char buf[8]; - int ret; - int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (0 > efd) - return 2; - ret = 0; - buf[3] = 1; - if (8 != write(efd, buf, 8)) - ret = 3; - else - { - if (8 != read(efd, buf, 8)) - ret = 4; - } - close(efd); - return ret; -} +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_WINSOCK2_H +#include <winsock2.h> +#endif +#ifdef HAVE_WS2TCPIP_H +#include <ws2tcpip.h> +#endif + ]], [[ + int af=AF_INET6; + struct sockaddr_in6 sa; + printf("%d %p\n", (int) af, (void*) &sa); ]] ) ], - [ - use_itc='eventfd' - enable_itc="$use_itc" - AC_DEFINE([[_MHD_ITC_EVENTFD]], [[1]], [Define to use eventFD for inter-thread communication]) - ], - [ - AS_VAR_IF([[enable_itc]], [["eventfd"]], [AC_MSG_ERROR([[eventfd(2) is not usable, consider using other type of inter-thread communication]])]) - ] - ) - AS_VAR_IF([mhd_cv_eventfd_usable],["assuming no"], - [AC_MSG_WARN([if you have 'eventfd' support enabled on your target system consider overriding test result by "mhd_cv_eventfd_usable=yes" configure parameter])] + [AS_VAR_SET([mhd_cv_have_inet6],["yes"])], + [AS_VAR_SET([mhd_cv_have_inet6],["no"])] ) ] ) +AS_VAR_IF([mhd_cv_have_inet6],["yes"], + [AC_DEFINE([HAVE_INET6], [1], [Define to '1' if you have IPv6 headers])] +) -AS_IF([[test "x$enable_itc" = "xpipe" || test "x$enable_itc" = "xauto"]], [ - AS_VAR_IF([[os_is_native_w32]], [["yes"]], [], [ - AC_CACHE_CHECK([[whether pipe(3) is usable]], [[mhd_cv_pipe_usable]], [ - AC_LINK_IFELSE([ - AC_LANG_PROGRAM([ -AC_INCLUDES_DEFAULT -#ifdef HAVE_UNISTD_H -#include <unistd.h> +MHD_CHECK_FUNC([[sysconf]], [[#include <unistd.h>]], [[long a = sysconf(0); if (a) return 1;]]) + +MHD_CHECK_FUNC([[sysctl]], [[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> #endif - ], [[ - int arr[2]; - int res; - res = pipe(arr); - if (res != 0) return 33; - close (arr[0]); - close (arr[1]); - ]]) - ], [[mhd_cv_pipe_usable='yes']], [[mhd_cv_pipe_usable='no']]) - ]) - AS_VAR_IF([[mhd_cv_pipe_usable]], [["yes"]], [ - use_itc='pipe' - enable_itc="$use_itc" - AC_DEFINE([[_MHD_ITC_PIPE]], [[1]], [Define to use pipe for inter-thread communication]) - MHD_CHECK_LINK_RUN([[whether pipe2(2) is usable]],[[mhd_cv_pipe2_usable]], - [ - # Cross-compiling - AS_CASE([${host_os}], [kfreebsd*-gnu], [[mhd_cv_pipe2_usable='assuming no']], - [[mhd_cv_pipe2_usable='assuming yes']]) - ], - [ - AC_LANG_PROGRAM([ -AC_INCLUDES_DEFAULT -#ifdef HAVE_FCNTL_H -#include <fcntl.h> +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> #endif -#ifdef HAVE_UNISTD_H +#if defined(HAVE_STDDEF_H) +#include <stddef.h> +#elif defined(HAVE_STDLIB_H) +#include <stdlib.h> +#endif + ]], [[ + int mib[2] = {0, 0}; /* Avoid any platform-specific values */ + i][f (sysctl(mib, 2, NULL, NULL, NULL, 0)) return 1; + ]], + [ + AC_CHECK_DECLS([CTL_NET,PF_INET,IPPROTO_ICMP,ICMPCTL_ICMPLIM],[],[], + [[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif /* HAVE_SYS_TYPES_H */ +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif /* HAVE_SYS_SYSCTL_H */ +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif /* HAVE_SYS_SYSCTL_H */ +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#elif defined(HAVE_UNISTD_H) #include <unistd.h> #endif - ], [[ - int arr[2]; - int res; - res = pipe2(arr, O_CLOEXEC | O_NONBLOCK); - if (res != 0) return 33; - close (arr[0]); - close (arr[1]); - ]] - ) - ], - [AC_DEFINE([[HAVE_PIPE2_FUNC]], [[1]], [Define if you have usable pipe2(2) function])] - ) - ], [ - AS_VAR_IF([[enable_itc]], [["pipe"]], [AC_MSG_ERROR([[pipe(3) is not usable, consider using other type of inter-thread communication]])]) - ]) - ]) -]) +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif /* HAVE_NETINET_IN_SYSTM_H */ +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif /* HAVE_NETINET_IP_H */ +#ifdef HAVE_NETINET_IP_ICMP_H +#include <netinet/ip_icmp.h> +#endif /* HAVE_NETINET_IP_ICMP_H */ +#ifdef HAVE_NETINET_ICMP_VAR_H +#include <netinet/icmp_var.h> +#endif /* HAVE_NETINET_ICMP_VAR_H */ + ]] + ) + ] +) -AS_IF([[test "x$enable_itc" = "xsocketpair" || test "x$enable_itc" = "xauto"]], [ - AS_VAR_IF([[os_is_native_w32]], [["yes"]], [[mhd_cv_socketpair_usable='yes']], [ - AC_CACHE_CHECK([[whether socketpair(3) is usable]], [[mhd_cv_socketpair_usable]], [ - AC_LINK_IFELSE([ - AC_LANG_PROGRAM([ -AC_INCLUDES_DEFAULT +MHD_CHECK_FUNC([[sysctlbyname]], [[ #ifdef HAVE_SYS_TYPES_H #include <sys/types.h> #endif -#ifdef HAVE_SYS_SOCKET_H -#include <sys/socket.h> +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> #endif - ], [[ - int arr[2]; - int res; -#if defined(AF_LOCAL) - res = socketpair(AF_LOCAL, SOCK_STREAM, 0, arr); -#elif defined(AF_UNIX) - res = socketpair(AF_UNIX, SOCK_STREAM, 0, arr); -#else -#error AF_LOCAL and AF_UNIX are both undefined - choke me now; +#if defined(HAVE_STDDEF_H) +#include <stddef.h> +#elif defined(HAVE_STDLIB_H) +#include <stdlib.h> #endif - if (res != 0) return 1 - ]]) - ], [[mhd_cv_socketpair_usable='yes']], [[mhd_cv_socketpair_usable='no']]) - ]) - ]) - AS_VAR_IF([[mhd_cv_socketpair_usable]], [["yes"]], [ - use_itc='socketpair' - enable_itc="$use_itc" - AC_DEFINE([[_MHD_ITC_SOCKETPAIR]], [[1]], [Define to use socketpair for inter-thread communication]) - ], [ - AS_VAR_IF([[enable_itc]], [["socketpair"]], [AC_MSG_ERROR([[socketpair(3) is not usable, consider using other type of inter-thread communication]])]) - ]) -]) + ]], [[sysctlbyname("test", NULL, NULL, NULL, 0);]] +) -AS_IF([[test -z "$use_itc"]], [AC_MSG_ERROR([[cannot find usable type of inter-thread communication]])]) +MHD_CHECK_FUNC([[usleep]], [[#include <unistd.h>]], [[usleep(100000);]]) +MHD_CHECK_FUNC([[nanosleep]], [[#include <time.h>]], [[struct timespec ts2, ts1 = {0, 0}; nanosleep(&ts1, &ts2);]]) + +# NOTE: require setting of errattr_CFLAGS above +CFLAGS="${CFLAGS_ac} ${user_CFLAGS} ${errattr_CFLAGS}" +AC_CACHE_CHECK([whether $CC supports __attribute__((used))],[mhd_cv_cc_attr_used], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +extern __attribute__((used)) int +test_func(void); + +extern __attribute__((used)) int +test_func(void) {return 0;} + +int main(void) {return test_func();} + ]]) + ], + [mhd_cv_cc_attr_used="yes"],[mhd_cv_cc_attr_used="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_used],["yes"], + [ + AC_DEFINE([HAVE_ATTR_USED],[1], + [Define to '1' if your compiler supports __attribute__((used))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__((pure))],[mhd_cv_cc_attr_pure], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +extern __attribute__((pure)) int +test_func(const char *ptr); + +extern __attribute__((pure)) int +test_func(const char *ptr) {return (0 == (*ptr));} + +int main(void) {return test_func("");} + ]]) + ], + [mhd_cv_cc_attr_pure="yes"],[mhd_cv_cc_attr_pure="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_pure],["yes"], + [ + AC_DEFINE([HAVE_ATTR_PURE],[1], + [Define to '1' if your compiler supports __attribute__((pure))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__((const))],[mhd_cv_cc_attr_const], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +extern __attribute__((const)) int +test_func(int a); + +extern __attribute__((const)) int +test_func(int a) {return a + 1;} + +int main(void) {return test_func(-1);} + ]]) + ], + [mhd_cv_cc_attr_const="yes"],[mhd_cv_cc_attr_const="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_const],["yes"], + [ + AC_DEFINE([HAVE_ATTR_CONST],[1], + [Define to '1' if your compiler supports __attribute__((const))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__((visibility("default")))],[mhd_cv_cc_attr_visibility_default], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +extern __attribute__((visibility("default"))) int +test_extrn_func(void); + +extern __attribute__((visibility("default"))) int +test_extrn_func(void) {return 0;} + +int main(void) {return test_extrn_func();} + ]]) + ], + [mhd_cv_cc_attr_visibility_default="yes"],[mhd_cv_cc_attr_visibility_default="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_visibility_default],["yes"], + [ + AC_DEFINE([HAVE_ATTR_VISIBILITY_DEFAULT],[1], + [Define to '1' if your compiler supports __attribute__((visibility("default")))] + ) + + AC_CACHE_CHECK([whether $CC supports __attribute__((visibility("internal")))],[mhd_cv_cc_attr_visibility_internal], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +__attribute__((visibility("internal"))) int +test_intern_func(void); + +__attribute__((visibility("internal"))) int +test_intern_func(void) {return 0;} +int main(void) {return test_intern_func();} + ]]) + ], + [mhd_cv_cc_attr_visibility_internal="yes"],[mhd_cv_cc_attr_visibility_internal="no"] + ) + ] + ) + AS_VAR_IF([mhd_cv_cc_attr_visibility_internal],["yes"], + [ + AC_DEFINE([HAVE_ATTR_VISIBILITY_INTERNAL],[1], + [Define to '1' if your compiler supports __attribute__((visibility("internal")))] + ) + ] + ) -MHD_CHECK_FUNC([accept4], - [[ -#if defined(HAVE_SYS_TYPES_H) -# include <sys/types.h> -#endif -#include <sys/socket.h> - ]], - [[ - struct sockaddr sk_addr; - socklen_t addr_size; - i][f (0 > accept4(0, &sk_addr, &addr_size, 0)) - return 3; - ]] + CFLAGS="${user_CFLAGS}" + MHD_CHECK_CC_CFLAG([-fvisibility=hidden],[CFLAGS_ac], + [ + MHD_APPEND_FLAG_TO_VAR([MHD_LIB_CFLAGS],[-fvisibility=hidden]) + ], + [ + AC_MSG_WARN([[$CC supports __attribute__((visibility("default"))), but does not support -fvisibility=hidden. Check compiler and compiler flags.]]) + ] + ) + ] ) -MHD_CHECK_FUNC([gmtime_r], - [[ -#if defined(HAVE_SYS_TYPES_H) -# include <sys/types.h> -#endif -#include <time.h> - ]], - [[ - time_t timer = (time_t) 0; - struct tm res; +CFLAGS="${CFLAGS_ac} ${user_CFLAGS} ${errattr_CFLAGS}" +AC_CACHE_CHECK([whether $CC supports __attribute__ ((warn_unused_result))],[mhd_cv_cc_attr_warn_unused_res], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((warn_unused_result)) int +test_func(void) {return 0; } - i][f (&res != gmtime_r(&timer, &res)) - return 3; - ]] +int main(void) { return test_func(); } + ]]) + ], + [mhd_cv_cc_attr_warn_unused_res="yes"],[mhd_cv_cc_attr_warn_unused_res="no"] + ) + ] ) -MHD_CHECK_FUNC([memmem], - [[ -#if defined(HAVE_STDDEF_H) -# include <stddef.h> -#elif defined(HAVE_STDLIB_H) -# include <stdlib.h> -#endif /* HAVE_STDLIB_H */ -#include <string.h> - ]], - [[ - const char *haystack = "abc"; - size_t hslen = 3; - const char *needle = "b"; - size_t needlelen = 1; +AS_VAR_IF([mhd_cv_cc_attr_warn_unused_res],["yes"], + [ + AC_DEFINE([HAVE_ATTR_WARN_UNUSED_RES],[1], + [Define to '1' if your compiler supports __attribute__ ((warn_unused_result))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((returns_nonnull))],[mhd_cv_cc_attr_ret_nonnull], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((returns_nonnull)) int * +test_func(void) { + static int i = 0; + return &i; +} - i][f ((haystack + 1) != memmem(haystack, hslen, needle, needlelen)) - return 3; - ]] +int main(void) { + return *(test_func()); +} + ]]) + ], + [mhd_cv_cc_attr_ret_nonnull="yes"],[mhd_cv_cc_attr_ret_nonnull="no"] + ) + ] ) -MHD_CHECK_FUNC([snprintf], - [[ -#include <stdio.h> - ]], - [[ - char buf[2]; +AS_VAR_IF([mhd_cv_cc_attr_ret_nonnull],["yes"], + [ + AC_DEFINE([HAVE_ATTR_RET_NONNULL],[1], + [Define to '1' if your compiler supports __attribute__ ((returns_nonnull))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((nonnull))],[mhd_cv_cc_attr_nonnull], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((nonnull)) int +test_func(int *p) {return 0 == *p;} - i][f (1 != snprintf(buf, 2, "a")) - return 3; - /* Do not use the next check to avoid compiler warning */ - /* i][f (4 != snprintf(buf, 2, "abcd")) - return 4; */ - ]] +int main(void) { + int i = 0; + return test_func(&i) ? 0 : 5; +} + ]]) + ], + [mhd_cv_cc_attr_nonnull="yes"],[mhd_cv_cc_attr_nonnull="no"] + ) + ] ) -AC_CHECK_DECL([gmtime_s], +AS_VAR_IF([mhd_cv_cc_attr_nonnull],["yes"], [ - AC_MSG_CHECKING([[whether gmtime_s is in C11 form]]) - AC_LINK_IFELSE( - [ AC_LANG_PROGRAM( - [[ -#define __STDC_WANT_LIB_EXT1__ 1 -#include <time.h> -#ifdef __cplusplus -extern "C" -#endif - struct tm* gmtime_s(const time_t* time, struct tm* result); - ]], [[ - static struct tm res; - static time_t t = 0; - gmtime_s (&t, &res); + AC_DEFINE([HAVE_ATTR_NONNULL],[1], + [Define to '1' if your compiler supports __attribute__ ((nonnull))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((nonnull(NUM)))],[mhd_cv_cc_attr_nonnull_num], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((nonnull(2))) int +test_func(int *p1, int *p2) { + return (((int *)(void *)0) == p1) && (0 == *p2); +} + +int main(void) { + int i = 0; + return test_func( (int *)(void *)0, &i) ? 0 : 5; +} ]]) - ], - [ - AC_DEFINE([HAVE_C11_GMTIME_S], [1], [Define to 1 if you have the `gmtime_s' function in C11 form.]) - AC_MSG_RESULT([[yes]]) - ], - [ - AC_MSG_RESULT([[no]]) - AC_MSG_CHECKING([[whether gmtime_s is in W32 form]]) - AC_LINK_IFELSE( - [ AC_LANG_PROGRAM( - [[ -#include <time.h> -#ifdef __cplusplus -extern "C" -#endif -errno_t gmtime_s(struct tm* _tm, const time_t* time); - ]], [[ - static struct tm res; - static time_t t = 0; - gmtime_s (&res, &t); - ]]) - ], - [ - AC_DEFINE([HAVE_W32_GMTIME_S], [1], [Define to 1 if you have the `gmtime_s' function in W32 form.]) - AC_MSG_RESULT([[yes]]) - ], - [AC_MSG_RESULT([[no]]) - ]) - ]) - ], [], - [[#define __STDC_WANT_LIB_EXT1__ 1 -#include <time.h>]]) + ], + [mhd_cv_cc_attr_nonnull_num="yes"],[mhd_cv_cc_attr_nonnull_num="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_nonnull_num],["yes"], + [ + AC_DEFINE([HAVE_ATTR_NONNULL_NUM],[1], + [Define to '1' if your compiler supports __attribute__ ((nonnull(NUM)))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((access (read_only,NUM)))],[mhd_cv_cc_attr_access_read], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((access (read_only,1))) int +test_func(int *p) {return 0 == *p;} +int main(void) { + int i = 0; + return test_func(&i) ? 0 : 5; +} + ]]) + ], + [mhd_cv_cc_attr_access_read="yes"],[mhd_cv_cc_attr_access_read="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_access_read],["yes"], + [ + AC_DEFINE([HAVE_ATTR_ACCESS_READ],[1], + [Define to '1' if your compiler supports __attribute__ ((access (read_only,NUM)))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((access (write_only,NUM)))],[mhd_cv_cc_attr_access_write], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((access (write_only,1))) void +test_func(int *p) {*p = 0;} -AC_CHECK_DECL([SOCK_NONBLOCK], [AC_DEFINE([HAVE_SOCK_NONBLOCK], [1], [SOCK_NONBLOCK is defined in a socket header])], [], - [[ -#if defined(HAVE_SYS_TYPES_H) -# include <sys/types.h> -#endif -#if defined(HAVE_SYS_SOCKET_H) -# include <sys/socket.h> -#elif defined(HAVE_WINSOCK2_H) -# include <winsock2.h> -#endif - ]] +int main(void) { + int i = 1; + test_func(&i); + return i; +} + ]]) + ], + [mhd_cv_cc_attr_access_write="yes"],[mhd_cv_cc_attr_access_write="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_access_write],["yes"], + [ + AC_DEFINE([HAVE_ATTR_ACCESS_WRITE],[1], + [Define to '1' if your compiler supports __attribute__ ((access (write_only,NUM)))] + ) + ] ) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((access (read_write,NUM)))],[mhd_cv_cc_attr_access_read_write], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((access (read_write,1))) void +test_func(int *p) {*p = *p - 1;} -MHD_FIND_LIB([clock_gettime],[[#include <time.h>]], - [[ - struct timespec tp; - i][f (0 > clock_gettime(CLOCK_REALTIME, &tp)) - return 3; - ]], - [rt], +int main(void) { + int i = 1; + test_func(&i); + return i; +} + ]]) + ], + [mhd_cv_cc_attr_access_read_write="yes"],[mhd_cv_cc_attr_access_read_write="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_access_read_write],["yes"], [ - AC_DEFINE([HAVE_CLOCK_GETTIME], [1], [Define to '1' if you have clock_gettime() function]) - AS_VAR_IF([[mhd_cv_find_lib_clock_gettime]],[["none required"]], [], - [ - MHD_LIBDEPS_PKGCFG="${mhd_cv_find_lib_clock_gettime} $MHD_LIBDEPS_PKGCFG" - ] + AC_DEFINE([HAVE_ATTR_ACCESS_READ_WRITE],[1], + [Define to '1' if your compiler supports __attribute__ ((access (read_write,NUM)))] ) - ],[], - [MHD_LIBDEPS] + ] ) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((access (read_only,NUM,NUM_SIZE)))],[mhd_cv_cc_attr_access_read_size], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((access (read_only,2,1))) int +test_func(unsigned int num, int *arr) {return 2 == arr[num - 1];} -MHD_CHECK_FUNC([clock_get_time], - [[ -#include <mach/clock.h> -#include <mach/mach.h> - ]], - [[ - clock_serv_t cs; - mach_timespec_t mt; - host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cs); - clock_get_time(cs, &mt); - mach_port_deallocate(mach_task_self(), cs); - ]] +int main(void) { + int arr[4] = {5, 4, 3, 2}; + return test_func(4, arr) ? 0 : 5; +} + ]]) + ], + [mhd_cv_cc_attr_access_read_size="yes"],[mhd_cv_cc_attr_access_read_size="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_access_read_size],["yes"], + [ + AC_DEFINE([HAVE_ATTR_ACCESS_READ_SIZE],[1], + [Define to '1' if your compiler supports __attribute__ ((access (read_only,NUM,NUM_SIZE)))] + ) + ] ) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((access (write_only,NUM,NUM_SIZE)))],[mhd_cv_cc_attr_access_write_size], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((access (write_only,2,1))) void +test_func(unsigned int num, int *arr) {arr[num-1] = 0;} -MHD_CHECK_FUNC([gethrtime], - [[ -#ifdef HAVE_SYS_TIME_H -/* Solaris define gethrtime() in sys/time.h */ -#include <sys/time.h> -#endif /* HAVE_SYS_TIME_H */ -#ifdef HAVE_TIME_H -/* HP-UX define gethrtime() in time.h */ -#include <time.h> -#endif /* HAVE_TIME_H */ - ]], - [[ - hrtime_t hrt = gethrtime(); - i][f (0 == hrt) - return 3; - ]] +int main(void) { + int arr[4] = {5, 4, 3, 2}; + test_func(4, arr); + return arr[3]; +} + ]]) + ], + [mhd_cv_cc_attr_access_write_size="yes"],[mhd_cv_cc_attr_access_write_size="no"] + ) + ] ) - -AS_VAR_IF([ac_cv_header_time_h], ["yes"], +AS_VAR_IF([mhd_cv_cc_attr_access_write_size],["yes"], [ - MHD_CHECK_FUNC([timespec_get], - [[ -#include <time.h> - -#ifndef TIME_UTC -#error TIME_UTC must be defined to use timespec_get() -choke me now -#endif - ]], - [[ - struct timespec ts; - i][f (TIME_UTC != timespec_get (&ts, TIME_UTC)) - return 3; - ]] + AC_DEFINE([HAVE_ATTR_ACCESS_WRITE_SIZE],[1], + [Define to '1' if your compiler supports __attribute__ ((access (write_only,NUM,NUM_SIZE)))] ) ] ) - -MHD_CHECK_FUNC_GETTIMEOFDAY - -# IPv6 -AC_CACHE_CHECK([for IPv6],[mhd_cv_have_inet6], +AC_CACHE_CHECK([whether $CC supports __attribute__ ((access (read_write,NUM,NUM_SIZE)))],[mhd_cv_cc_attr_access_read_write_size], [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#include <stdio.h> -#ifdef HAVE_NETINET_IN_H -#include <netinet/in.h> -#endif -#ifdef HAVE_SYS_SOCKET_H -#include <sys/socket.h> -#endif -#ifdef HAVE_WINSOCK2_H -#include <winsock2.h> -#endif -#ifdef HAVE_WS2TCPIP_H -#include <ws2tcpip.h> -#endif - ]], [[ - int af=AF_INET6; - int pf=PF_INET6; - struct sockaddr_in6 sa; - printf("%d %d %p\n", af, pf, (void*) &sa); - ]] - ) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((access (read_write,2,1))) void +test_func(unsigned int num, int *arr) {arr[num-1] = arr[1] - 4;} + +int main(void) { + int arr[4] = {5, 4, 3, 2}; + test_func(4, arr); + return arr[3]; +} + ]]) ], - [AS_VAR_SET([mhd_cv_have_inet6],["yes"])], - [AS_VAR_SET([mhd_cv_have_inet6],["no"])] + [mhd_cv_cc_attr_access_read_write_size="yes"],[mhd_cv_cc_attr_access_read_write_size="no"] ) ] ) -AS_VAR_IF([mhd_cv_have_inet6],["yes"], - [AC_DEFINE([HAVE_INET6], [1], [Define to '1' if you have IPv6 headers])] +AS_VAR_IF([mhd_cv_cc_attr_access_read_write_size],["yes"], + [ + AC_DEFINE([HAVE_ATTR_ACCESS_READ_WRITE_SIZE],[1], + [Define to '1' if your compiler supports __attribute__ ((access (read_write,NUM,NUM_SIZE)))] + ) + ] ) - -MHD_CHECK_FUNC([[sysconf]], [[#include <unistd.h>]], [[long a = sysconf(0); if (a) return 1;]]) - -MHD_CHECK_FUNC([[sysctl]], [[ +AC_CACHE_CHECK([whether $CC supports __attribute__ ((fd_arg_read (NUM)))],[mhd_cv_cc_attr_fd_arg_read], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#ifndef _CRT_NONSTDC_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS 1 +#endif #ifdef HAVE_SYS_TYPES_H #include <sys/types.h> #endif -#ifdef HAVE_SYS_SYSCTL_H -#include <sys/sysctl.h> +#include <stdio.h> +#ifdef HAVE_IO_H +#include <io.h> #endif -#if defined(HAVE_STDDEF_H) -#include <stddef.h> -#elif defined(HAVE_STDLIB_H) +#ifdef HAVE_STDLIB_H #include <stdlib.h> #endif - ]], [[ - int mib[2] = {0, 0}; /* Avoid any platform-specific values */ - i][f (sysctl(mib, 2, NULL, NULL, NULL, 0)) return 1; - ]], +#include <fcntl.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +static __attribute__ ((fd_arg_read (1))) int +test_func(int fd) +{ + int data_in; + int read_size; + read_size = (int) read(fd, &data_in, sizeof(data_in)); + if (read_size < (int) sizeof(data_in)) return 4; + return data_in; +} + +int main(void) { + int fd = open("test.txt", O_RDONLY); + int res = test_func(fd); + close (fd); + return res; +} + ]]) + ], + [mhd_cv_cc_attr_fd_arg_read="yes"],[mhd_cv_cc_attr_fd_arg_read="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_fd_arg_read],["yes"], [ - AC_CHECK_DECLS([CTL_NET,PF_INET,IPPROTO_ICMP,ICMPCTL_ICMPLIM],[],[], - [[ -#ifdef HAVE_SYS_TYPES_H -#include <sys/types.h> -#endif /* HAVE_SYS_TYPES_H */ -#ifdef HAVE_SYS_SYSCTL_H -#include <sys/sysctl.h> -#endif /* HAVE_SYS_SYSCTL_H */ -#ifdef HAVE_SYS_SYSCTL_H -#include <sys/sysctl.h> -#endif /* HAVE_SYS_SYSCTL_H */ -#ifdef HAVE_SYS_SOCKET_H -#include <sys/socket.h> -#endif /* HAVE_SYS_SOCKET_H */ -#ifdef HAVE_NETINET_IN_SYSTM_H -#include <netinet/in_systm.h> -#endif /* HAVE_NETINET_IN_SYSTM_H */ -#ifdef HAVE_NETINET_IN_H -#include <netinet/in.h> -#endif /* HAVE_NETINET_IN_H */ -#ifdef HAVE_NETINET_IP_H -#include <netinet/ip.h> -#endif /* HAVE_NETINET_IP_H */ -#ifdef HAVE_NETINET_IP_ICMP_H -#include <netinet/ip_icmp.h> -#endif /* HAVE_NETINET_IP_ICMP_H */ -#ifdef HAVE_NETINET_ICMP_VAR_H -#include <netinet/icmp_var.h> -#endif /* HAVE_NETINET_ICMP_VAR_H */ - ]] + AC_DEFINE([HAVE_ATTR_FD_ARG_READ],[1], + [Define to '1' if your compiler supports __attribute__ ((fd_arg_read (NUM)))] ) ] ) +AC_CACHE_CHECK([whether $CC supports __attribute__ ((null_terminated_string_arg (NUM)))],[mhd_cv_cc_attr_null_term_str], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static __attribute__ ((null_terminated_string_arg (1))) int +test_func(const char *str) {return (0 == str[0]) ? 5 : 0;} -MHD_CHECK_FUNC([[sysctlbyname]], [[ -#ifdef HAVE_SYS_TYPES_H -#include <sys/types.h> -#endif -#ifdef HAVE_SYS_SYSCTL_H -#include <sys/sysctl.h> -#endif -#if defined(HAVE_STDDEF_H) -#include <stddef.h> -#elif defined(HAVE_STDLIB_H) -#include <stdlib.h> -#endif - ]], [[sysctlbyname("test", NULL, NULL, NULL, 0);]] +int main(void) { + return test_func("test"); +} + ]]) + ], + [mhd_cv_cc_attr_null_term_str="yes"],[mhd_cv_cc_attr_null_term_str="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_null_term_str],["yes"], + [ + AC_DEFINE([HAVE_ATTR_NULL_TERM_STR],[1], + [Define to '1' if your compiler supports __attribute__ ((null_terminated_string_arg (NUM)))] + ) + ] ) +AC_CACHE_CHECK([whether $CC supports __attribute__((enum_extensibility (closed)))],[mhd_cv_cc_attr_enum_extns_closed], + [ + SAVE_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag="yes" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +enum __attribute__((enum_extensibility (closed))) Test_Enum +{Val_A = 0, Val_B = 3}; -MHD_CHECK_FUNC([[usleep]], [[#include <unistd.h>]], [[usleep(100000);]]) -MHD_CHECK_FUNC([[nanosleep]], [[#include <time.h>]], [[struct timespec ts2, ts1 = {0, 0}; nanosleep(&ts1, &ts2);]]) +int main(void) +{ + enum Test_Enum var = Val_A; + return (var != Val_B) ? 0 : 3; +} + ]]) + ], + [mhd_cv_cc_attr_enum_extns_closed="yes"],[mhd_cv_cc_attr_enum_extns_closed="no"] + ) + ac_c_werror_flag="$SAVE_ac_c_werror_flag" + AS_UNSET([SAVE_ac_c_werror_flag]) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_enum_extns_closed],["yes"], + [ + AC_DEFINE([HAVE_ATTR_ENUM_EXTNS_CLOSED],[1], + [Define to '1' if your compiler supports __attribute__((enum_extensibility (closed)))] + ) + ] +) +AC_CACHE_CHECK([whether $CC supports __attribute__((flag_enum))],[mhd_cv_cc_attr_flag_enum], + [ + SAVE_ac_c_werror_flag="$ac_c_werror_flag" + ac_c_werror_flag="yes" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#ifdef HAVE_ATTR_ENUM_EXTNS_CLOSED +#define ATTR_EXTNS_CLOSED_IF_SUPPORTED __attribute__((enum_extensibility (closed))) +#else +#define ATTR_EXTNS_CLOSED_IF_SUPPORTED /* empty */ +#endif +enum ATTR_EXTNS_CLOSED_IF_SUPPORTED __attribute__((flag_enum)) Test_Enum +{Flag_A = 1 << 0, Flag_B = 1 << 1, Flag_C = 1 << 2, Flag_D = 1 << 3}; -HIDDEN_VISIBILITY_CFLAGS="" -AH_TEMPLATE([_MHD_EXTERN],[defines how to decorate public symbols w][hile building the library]) -CFLAGS="${user_CFLAGS}" -MHD_CHECK_CC_CFLAG([-fvisibility=hidden],[CFLAGS_ac], +int main(void) +{ + enum Test_Enum var = Flag_A; + var |= Flag_C; + var = var | Flag_D | Flag_A; + return (var != Flag_B) ? 0 : 3; +} + ]]) + ], + [mhd_cv_cc_attr_flag_enum="yes"],[mhd_cv_cc_attr_flag_enum="no"] + ) + ac_c_werror_flag="$SAVE_ac_c_werror_flag" + AS_UNSET([SAVE_ac_c_werror_flag]) + ] +) +AS_VAR_IF([mhd_cv_cc_attr_flag_enum],["yes"], [ - # NOTE: require setting of errattr_CFLAGS above - CFLAGS="${CFLAGS_ac} -fvisibility=hidden ${user_CFLAGS} ${errattr_CFLAGS}" - AC_CACHE_CHECK([whether $CC supports __attribute__((visibility("default")))],[mhd_cv_cc_attr_visibility], - [ - AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ -extern __attribute__((visibility("default"))) int test_extrn_func(void); + AC_DEFINE([HAVE_ATTR_FLAG_ENUM],[1], + [Define to '1' if your compiler supports __attribute__((flag_enum))] + ) + ] +) +AC_CACHE_CHECK([[whether $CC supports array[static N] with fixed N as a function parameter]],[mhd_cv_cc_func_param_arr_static_fixed], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static int +test_func(int arr[static 4]) { return arr[2] - 4; } -int test_extrn_func(void) {return 0;} - ]]) - ], - [mhd_cv_cc_attr_visibility="yes"],[mhd_cv_cc_attr_visibility="no"] - ) - ] +int main(void) { + int arr[4] = {5, 4, 3, 2}; + return test_func(arr); +} + ]]) + ], + [mhd_cv_cc_func_param_arr_static_fixed="yes"],[mhd_cv_cc_func_param_arr_static_fixed="no"] ) - AS_VAR_IF([mhd_cv_cc_attr_visibility],["yes"], - [ - HIDDEN_VISIBILITY_CFLAGS="-fvisibility=hidden" - AS_IF([test "x$os_is_native_w32" = "xyes" && test "x$enable_shared" = "xyes"], - [AC_DEFINE([_MHD_EXTERN], [__attribute__((visibility("default"))) __declspec(dllexport) extern])], - [AC_DEFINE([_MHD_EXTERN], [__attribute__((visibility("default"))) extern])] - ) + ] +) +AS_VAR_IF([mhd_cv_cc_func_param_arr_static_fixed],["yes"], + [ + AC_DEFINE([HAVE_FUNC_PARAM_ARR_STATIC_FIXED],[1], + [[Define to '1' if your compiler supports 'array[static N]' with fixed N as function parameter]] + ) + ] +) +AC_CACHE_CHECK([[whether $CC supports array[static N] with variable N as a function parameter]],[mhd_cv_cc_func_param_arr_static_var], + [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +static int +test_func(int num, int arr[static num]) { return arr[num-2] - 4; } + +int main(void) { + int arr[4] = {5, 4, 3, 2}; + return test_func(4, arr); +} + ]]) ], - [ - AC_MSG_WARN([$CC supports -fvisibility, but does not support __attribute__((visibility("default"))). Check compiler and compiler flags.]) - AC_DEFINE([_MHD_EXTERN], [extern]) - ] + [mhd_cv_cc_func_param_arr_static_var="yes"],[mhd_cv_cc_func_param_arr_static_var="no"] + ) + ] +) +AS_VAR_IF([mhd_cv_cc_func_param_arr_static_var],["yes"], + [ + AC_DEFINE([HAVE_FUNC_PARAM_ARR_STATIC_VAR],[1], + [[Define to '1' if your compiler supports 'array[static N]' with variable N as a function parameter]] ) - ],[AC_DEFINE([_MHD_EXTERN], [extern])] + ] ) CFLAGS="${CFLAGS_ac} ${user_CFLAGS}" -AC_SUBST([HIDDEN_VISIBILITY_CFLAGS]) # libcurl (required for testing) AC_ARG_ENABLE([curl], @@ -3406,10 +4582,10 @@ AS_VAR_IF([[found_sendfile]], [["no"]], static void empty_func(void) { /* Check for declaration */ +#ifndef sendfile (void)sendfile; +#endif } -/* Declare again to check form match */ -ssize_t sendfile(int, int, off_t*, size_t); ]], [[ int fd1=0, fd2=2; @@ -3448,18 +4624,24 @@ AS_VAR_IF([[found_sendfile]], [["no"]], AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[ +#ifdef HAVE_SYS_TYPES_H #include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +#endif #include <sys/uio.h> static void empty_func(void) { /* Check for declaration */ +#ifndef sendfile (void)sendfile; +#endif } -/* Declare again to check form match */ -int sendfile(int, int, off_t, size_t, - struct sf_hdtr*, off_t*, int); ]], [[ int fd1=0, fd2=1; @@ -3467,7 +4649,7 @@ int sendfile(int, int, off_t, size_t, size_t s = 5; off_t r1; int r2; - r2 = sendfile (fd1, fd2, o, s, (void*)0, &r1, 0); + r2 = sendfile (fd1, fd2, o, s, (struct sf_hdtr *)0, &r1, 0); if (r2) empty_func(); ]] @@ -3489,25 +4671,31 @@ AS_VAR_IF([[found_sendfile]], [["no"]], AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[ +#ifdef HAVE_SYS_TYPES_H #include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +#endif #include <sys/uio.h> static void empty_func(void) { /* Check for declaration */ +#ifndef sendfile (void)sendfile; +#endif } -/* Declare again to check form match */ -int sendfile(int, int, off_t, off_t*, - struct sf_hdtr *, int); ]], [[ int fd=0, s=1; off_t o = 0; off_t l = 5; int r; - r = sendfile (fd, s, o, &l, (void*)0, 0); + r = sendfile (fd, s, o, &l, (struct sf_hdtr *)0, 0); if (r) empty_func(); ]] @@ -3526,24 +4714,30 @@ int sendfile(int, int, off_t, off_t*, AS_VAR_IF([[found_sendfile]], [["no"]], [ - AC_MSG_CHECKING([[for Solaris-style sendfile(3)]]) + AC_MSG_CHECKING([[for old Solaris-style sendfile(3)]]) SAVE_LIBS="$LIBS" LIBS="-lsendfile $LIBS" AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[ +#ifdef HAVE_SYS_TYPES_H #include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +#endif #include <sys/sendfile.h> static void empty_func(void) { /* Check for declaration */ +#ifndef sendfile (void)sendfile; +#endif } -/* Declare again to check form match */ -ssize_t sendfile(int out_fd, int in_fd, - off_t *off, size_t len); ]], [[ int fd1=0, fd2=1; @@ -3557,19 +4751,30 @@ ssize_t sendfile(int out_fd, int in_fd, ) ], [ - AC_DEFINE([HAVE_SOLARIS_SENDFILE], [1], [Define to 1 if you have Solaris-style sendfile(3).]) + # Solaris uses the same format as GNU/Linux. Old versions need libsendfile. + AC_DEFINE([HAVE_LINUX_SENDFILE], [1], [Define to 1 if you have linux-style sendfile(2).]) found_sendfile="yes, Solaris-style" MHD_LIBDEPS="-lsendfile $MHD_LIBDEPS" MHD_LIBDEPS_PKGCFG="-lsendfile $MHD_LIBDEPS_PKGCFG" AC_MSG_RESULT([[yes]]) MHD_CHECK_FUNC([sendfile64], [[ +#ifdef HAVE_SYS_TYPES_H #include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +#endif #include <sys/sendfile.h> ]], [[ off64_t f_offset = (off64_t) 0; +#ifndef sendfile64 + (void)sendfile64; +#endif if (0 > sendfile64 (0, 1, &f_offset, 1)) return 3; ]] @@ -3585,19 +4790,48 @@ AS_IF([[test "x$found_sendfile" = "xno" && test "x$enable_sendfile" = "xyes"]], [AC_MSG_ERROR([[sendfile() usage was requested by configure parameter, but no usable sendfile() function is detected]])] ) -# optional: enable error and informational messages -AC_MSG_CHECKING([[whether to generate text messages]]) +# optional: disable log and HTTP automatic messages AC_ARG_ENABLE([messages], - [AS_HELP_STRING([--disable-messages], - [disable error, warning messages and text bodies for ] - [automatic HTTP responses (to reduce the binary size)])], - [enable_messages=${enableval}], - [enable_messages=yes]) -AS_IF([[test "x$enable_messages" = "xyes"]], - [ AC_DEFINE([HAVE_MESSAGES],[1],[Define to 1 to enable support for text messages.]) ], - [[ enable_messages=no ]]) -AC_MSG_RESULT([[$enable_messages]]) -AM_CONDITIONAL([HAVE_MESSAGES], [test "x$enable_messages" != "xno"]) + [ + AS_HELP_STRING([--disable-messages], + [disable log messages and text bodies for ] + [automatic HTTP responses (to reduce the binary size)] + ) + ], + [],[enable_messages="yes"] +) + +AC_MSG_CHECKING([[whether to support internal logging functionality and build messages for log]]) +AC_ARG_ENABLE([log-messages], + [ + AS_HELP_STRING([--disable-log-messages], + [disable logger functionality and exclude log mesages from binary] + ) + ], + [],[enable_log_messages="$enable_messages"] +) +AS_VAR_IF([enable_log_messages],["yes"], + [AC_DEFINE([HAVE_LOG_FUNCTIONALITY],[1],[Define to '1' to enable internal logging and log messages.])], + [enable_log_messages="no"] +) +AC_MSG_RESULT([[$enable_log_messages]]) +AM_CONDITIONAL([HAVE_LOG_FUNCTIONALITY], [test "x$enable_log_messages" != "xno"]) + +AC_MSG_CHECKING([[whether to build text bodies for automatic HTTP response messages]]) +AC_ARG_ENABLE([http-messages], + [ + AS_HELP_STRING([--disable-http-messages], + [use empty bodies for automatic HTTP responses (less verbose for clients)] + ) + ], + [],[enable_http_messages="$enable_messages"] +) +AS_VAR_IF([enable_http_messages],["yes"], + [AC_DEFINE([HAVE_HTTP_AUTO_MESSAGES_BODIES],[1],[Define to '1' to enable verbose text bodies for automatic HTTP replies.])], + [enable_http_messages="no"] +) +AC_MSG_RESULT([[$enable_http_messages]]) +AM_CONDITIONAL([HAVE_HTTP_AUTO_MESSAGES_BODIES], [test "x$enable_http_messages" != "xno"]) # optional: have postprocessor? @@ -4070,7 +5304,7 @@ AS_VAR_IF([enable_dauth], ["yes"], #include <stdint.h> ]], [[ - static int arr[((int) 2) - 4 * (int)(${enable_dauth_def_max_nc} != ((uint32_t)${enable_dauth_def_max_nc}))]; + static int arr[((int) 2) - 4 * (int)(${enable_dauth_def_max_nc} != ((uint_fast32_t)${enable_dauth_def_max_nc}))]; (void) arr; ]] ) @@ -4409,7 +5643,13 @@ AS_IF([test "x$enable_dauth" != "xno"], AC_CACHE_CHECK([[for calloc()]], [[mhd_cv_have_func_calloc]], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#ifdef HAVE_STDLIB_H #include <stdlib.h> +#elif defined(HAVE_MALLOC_H) +#include <malloc.h> +#else +#include <unistd.h> +#endif ]],[[void * ptr = calloc(1, 2); if (ptr) return 1;]]) ], [[mhd_cv_have_func_calloc='yes']], @@ -4419,6 +5659,7 @@ AC_CACHE_CHECK([[for calloc()]], [[mhd_cv_have_func_calloc]], ) AS_VAR_IF([[mhd_cv_have_func_calloc]], [["yes"]], [AC_DEFINE([[HAVE_CALLOC]], [[1]], [Define to 1 if you have the usable `calloc' function.])]) +AM_CONDITIONAL([HAVE_SYS_CALLOC], [[test "x${mhd_cv_have_func_calloc}" = "xyes"]]) # Some systems have IPv6 disabled in kernel at run-time AS_IF([[test "x${mhd_cv_have_inet6}" = "xyes" && test "x${cross_compiling}" = "xno"]], @@ -4511,6 +5752,8 @@ int main(void) AS_VAR_IF([mhd_cv_ipv6_for_testing],["yes"], [AC_DEFINE([[USE_IPV6_TESTING]], [[1]], [Define to 1 if your kernel supports IPv6 and IPv6 is enabled and useful for testing.])] ) +AM_CONDITIONAL([USE_IPV6_TESTING], [[test "x$mhd_cv_ipv6_for_testing" = "xyes"]]) + AS_VAR_IF([enable_tools],["yes"], [ @@ -4542,7 +5785,7 @@ AS_VAR_IF([enable_tools],["yes"], i][f (1 != pstat_getdynamic(&psd_data, sizeof(psd_data), (size_t)1, 0)) return 2; i][f (0 >= psd_data.psd_proc_cnt) - return 3; + return 3; ]] ) MHD_CHECK_FUNC([vxCpuEnabledGet],[[#include <vxCpuLib.h>]], @@ -5948,15 +7191,22 @@ AM_CONDITIONAL([HAVE_EXPERIMENTAL], [test "x$enable_experimental" = "xyes"]) AC_CONFIG_FILES([ -libmicrohttpd.pc +src/mhd2/libmicrohttpd2.pc +w32/common/microhttpd_dll_res_vc.rc Makefile contrib/Makefile doc/Makefile doc/doxygen/libmicrohttpd.doxy doc/doxygen/Makefile +doc/examples/Makefile m4/Makefile src/Makefile +src/incl_priv/Makefile src/include/Makefile +src/mhd2/Makefile +src/tests/Makefile +src/tests/basic/Makefile +src/examples2/Makefile ]) AC_OUTPUT @@ -6011,11 +7261,13 @@ AC_MSG_NOTICE([GNU libmicrohttpd ${PACKAGE_VERSION} Configuration Summary: Threading lib: ${USE_THREADS} Inter-thread comm: ${use_itc} Shutdown of listening socket triggers select: ${mhd_cv_host_shtdwn_trgr_select} - poll support: ${enable_poll=no} + select() support: ${enable_select} + poll() support: ${enable_poll=no} epoll support: ${enable_epoll=no} sendfile used: ${found_sendfile} HTTPS support: ${MSG_HTTPS} - Messages: ${enable_messages} + Logging support: ${enable_log_messages} + Verbose auto replies: ${enable_http_messages} Cookie parsing: ${enable_cookie} Postproc: ${enable_postprocessor} Basic auth.: ${enable_bauth} @@ -6033,7 +7285,7 @@ AC_MSG_NOTICE([GNU libmicrohttpd ${PACKAGE_VERSION} Configuration Summary: Build shared lib: ${enable_shared} Build docs: ${enable_doc} Build examples: ${enable_examples} - Build tools: ${enable_examples} + Build tools: ${enable_tools} Test with libcurl: ${MSG_CURL} Heavy tests: ${use_heavy_tests_MSG} Fuzzing tests: ${run_zzuf_tests_MSG=no} diff --git a/m4/mhd_shutdown_socket_trigger.m4 b/m4/mhd_shutdown_socket_trigger.m4 @@ -107,14 +107,14 @@ AC_DEFUN([_MHD_RUN_CHECK_SOCKET_SHUTDOWN_TRIGGER],[dnl # ifdef HAVE_NETINET_TCP_H # include <netinet/tcp.h> # endif - typedef int MHD_socket; + typedef int MHD_Socket; # define MHD_INVALID_SOCKET (-1) # define MHD_POSIX_SOCKETS 1 #else # include <winsock2.h> # include <ws2tcpip.h> # include <windows.h> - typedef SOCKET MHD_socket; + typedef SOCKET MHD_Socket; # define MHD_INVALID_SOCKET (INVALID_SOCKET) # define MHD_WINSOCK_SOCKETS 1 #endif @@ -176,7 +176,7 @@ static void* select_thrd_func(void* param) #endif fd_set rs; struct timeval tmot = {0, 0}; - MHD_socket fd = *((MHD_socket*)param); + MHD_Socket fd = *((MHD_Socket*)param); FD_ZERO(&rs); FD_SET(fd, &rs); @@ -196,10 +196,10 @@ static void* select_thrd_func(void* param) } -static MHD_socket create_socket(void) +static MHD_Socket create_socket(void) { return socket (AF_INET, SOCK_STREAM, 0); } -static void close_socket(MHD_socket fd) +static void close_socket(MHD_Socket fd) { #ifdef MHD_POSIX_SOCKETS close(fd); @@ -208,10 +208,10 @@ static void close_socket(MHD_socket fd) #endif } -static MHD_socket +static MHD_Socket create_socket_listen(int port) { - MHD_socket fd; + MHD_Socket fd; struct sockaddr_in sock_addr; fd = create_socket(); if (MHD_INVALID_SOCKET == fd) @@ -240,7 +240,7 @@ create_socket_listen(int port) static long long test_run_select(int timeout_millsec, int use_shutdown, long long delay_before_shutdown) { pthread_t select_thrd; - MHD_socket fd; + MHD_Socket fd; #ifdef HAVE_GETTIMEOFDAY struct timeval start, stop; #else diff --git a/src/Makefile.am b/src/Makefile.am @@ -1,6 +1,14 @@ # This Makefile.am is in the public domain -SUBDIRS = include . +if BUILD_EXAMPLES +EXAMPLES_DIRS = examples2 +endif + +SUBDIRS = . incl_priv include mhd2 $(EXAMPLES_DIRS) tests + +if BUILD_TOOLS +#SUBDIRS += tools +endif EXTRA_DIST = \ datadir/cert-and-key.pem \ diff --git a/src/examples2/.gitignore b/src/examples2/.gitignore @@ -0,0 +1 @@ +minimal_example2 diff --git a/src/examples2/Makefile.am b/src/examples2/Makefile.am @@ -0,0 +1,32 @@ +# This Makefile.am is in the public domain +SUBDIRS = . + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + $(CPPFLAGS_ac) + +AM_CFLAGS = $(CFLAGS_ac) + +AM_LDFLAGS = $(LDFLAGS_ac) + +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/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 = \ + minimal_example2 + +minimal_example2_SOURCES = \ + minimal_example2.c diff --git a/src/examples2/minimal_example2.c b/src/examples2/minimal_example2.c @@ -0,0 +1,112 @@ +/* + 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 <stdlib.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!\n"; + + (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) || (65535 < port)) + { + fprintf (stderr, + "The PORT must be a numeric value between 1 and 65535.\n"); + return 2; + } + d = MHD_daemon_create (&req_cb, + NULL); + if (NULL == d) + { + fprintf (stderr, + "Failed to create MHD daemon.\n"); + return 3; + } + 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))) + { + fprintf (stderr, + "Failed to set MHD daemon run parameters.\n"); + } + else + { + if (MHD_SC_OK != + MHD_daemon_start (d)) + { + fprintf (stderr, + "Failed to start MHD daemon.\n"); + } + else + { + printf ("The MHD daemon is listening on port %d\n" + "Press ENTER to stop.\n", port); + (void) fgetc (stdin); + } + } + printf ("Stopping... "); + fflush (stdout); + MHD_daemon_destroy (d); + printf ("OK\n"); + return 0; +} diff --git a/src/incl_priv/Makefile.am b/src/incl_priv/Makefile.am @@ -0,0 +1,3 @@ +# This Makefile.am is in the public domain + +noinst_HEADERS = mhd_sys_options.h diff --git a/src/incl_priv/mhd_sys_options.h b/src/incl_priv/mhd_sys_options.h @@ -0,0 +1,550 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2016-2024 Karlson2k (Evgeny Grin) + + This library 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. + + This library 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/include/mhd_sys_options.h + * @brief additional automatic macros for MHD_config.h + * @author Karlson2k (Evgeny Grin) + * + * This file includes MHD_config.h and adds automatic macros based on values + * in MHD_config.h, compiler built-in macros and commandline-defined macros + * (but not based on values defined in other headers). Works also as a guard + * to prevent double inclusion of MHD_config.h + * + * This file must be included always before other headers as this header + * defines macros that control behaviour of other included headers. + */ + +#ifndef MHD_SYS_OPTIONS_H +#define MHD_SYS_OPTIONS_H 1 + +#ifndef HAVE_CONFIG_H +#error HAVE_CONFIG_H must be defind +#endif + +#include "mhd_config.h" + +/** + * Macro to make it easy to mark text for translation. Note that + * we do not actually call gettext() in MHD, but we do make it + * easy to create a ".po" file so that applications that do want + * to translate error messages can do so. + */ +#define _(String) (String) + +#ifdef HAVE_ATTR_VISIBILITY_DEFAULT +# define MHD_VISIBILITY_EXTERN __attribute__((visibility ("default"))) +#else +# define MHD_VISIBILITY_EXTERN /* empty */ +#endif + +#if ! defined(_WIN32) || \ + (! defined(DLL_EXPORT) /* Defined by libtool for shared version */ \ + && ! defined(MHD_W32DLL) /* Defined by MS VS projects for MHD DLL */) +# define MHD_EXPORTED /* empty */ +#else +# define MHD_EXPORTED __declspec(dllexport) +#endif + +#if defined(HAVE_ATTR_USED) \ + && (defined(PIC) || defined(DLL_EXPORT) || defined(MHD_W32DLL)) +/* Used externally, only for functions in shared library */ +# define MHD_EXTERN_USED __attribute__((used)) +#else +# define MHD_EXTERN_USED /* empty */ +#endif + +#if defined(MHD_EXTERN_) && defined(BUILDING_MHD_LIB) +# undef MHD_EXTERN_ +#endif /* MHD_EXTERN_ && BUILDING_MHD_LIB */ + +#ifndef MHD_EXTERN_ +# ifdef BUILDING_MHD_LIB +/* Building MHD itself */ +# define MHD_EXTERN_ \ + extern MHD_VISIBILITY_EXTERN MHD_EXPORTED MHD_EXTERN_USED +# else /* ! BUILDING_MHD_LIB */ +/* Test or example code, using MHD as a library */ +# define MHD_EXTERN_ extern +# endif /* ! BUILDING_MHD_LIB */ +#endif /* ! MHD_EXTERN_ */ + +#ifdef HAVE_ATTR_VISIBILITY_INTERNAL +/* To be used with internal non-static functions */ +# define MHD_VISIBILITY_INTERNAL __attribute__((visibility ("internal"))) +#else +/* To be used with internal non-static functions */ +# define MHD_VISIBILITY_INTERNAL /* empty */ +#endif + +/* To be used with internal non-static functions */ +#define MHD_INTERNAL MHD_VISIBILITY_INTERNAL + +#ifdef HAVE_ATTR_PURE +# define MHD_FN_PURE_ __attribute__((pure)) +#else +# define MHD_FN_PURE_ /* empty */ +#endif + +#ifdef HAVE_ATTR_CONST +# define MHD_FN_CONST_ __attribute__((const)) +#else +# define MHD_FN_CONST_ MHD_FN_PURE_ +#endif + +#ifdef HAVE_ATTR_WARN_UNUSED_RES +# define MHD_FN_MUST_CHECK_RESULT_ __attribute__ ((warn_unused_result)) +#else +# define MHD_FN_MUST_CHECK_RESULT_ /* empty */ +#endif + +#ifdef HAVE_ATTR_RET_NONNULL +# define MHD_FN_RETURNS_NONNULL_ __attribute__ ((returns_nonnull)) +#else +# define MHD_FN_RETURNS_NONNULL_ /* empty */ +#endif + +#ifdef HAVE_ATTR_NONNULL_NUM +# define MHD_FN_PAR_NONNULL_(param_num) __attribute__ ((nonnull (param_num))) +#else +# define MHD_FN_PAR_NONNULL_(param_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_NONNULL +# define MHD_FN_PAR_NONNULL_ALL_ __attribute__ ((nonnull)) +#else +# define MHD_FN_PAR_NONNULL_ALL_ /* empty */ +#endif + +#ifdef HAVE_ATTR_ACCESS_READ +# define MHD_FN_PAR_IN_(param_num) \ + __attribute__ ((access (read_only,param_num))) +#else +# define MHD_FN_PAR_IN_(param_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_ACCESS_READ_SIZE +# define MHD_FN_PAR_IN_SIZE_(param_num,size_num) \ + __attribute__ ((access (read_only,param_num,size_num))) +#else +# define MHD_FN_PAR_IN_SIZE_(param_num,size_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_ACCESS_READ_WRITE +# define MHD_FN_PAR_OUT_(param_num) \ + __attribute__ ((access (write_only,param_num))) +#else +# define MHD_FN_PAR_OUT_(param_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_ACCESS_WRITE +# define MHD_FN_PAR_OUT_SIZE_(param_num,size_num) \ + __attribute__ ((access (write_only,param_num,size_num))) +#else +# define MHD_FN_PAR_OUT_SIZE_(param_num,size_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_ACCESS_READ_WRITE +# define MHD_FN_PAR_INOUT_(param_num) \ + __attribute__ ((access (read_write,param_num))) +#else +# define MHD_FN_PAR_INOUT_(param_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_ACCESS_READ_WRITE_SIZE +# define MHD_FN_PAR_INOUT_SIZE_(param_num,size_num) \ + __attribute__ ((access (read_write,param_num,size_num))) +#else +# define MHD_FN_PAR_INOUT_SIZE_(param_num,size_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_FD_ARG_READ +# define MHD_FN_PAR_FD_READ_(param_num) \ + __attribute__ ((fd_arg_read (param_num))) +#else +# define MHD_FN_PAR_FD_READ_(param_num) /* empty */ +#endif + +#ifdef HAVE_ATTR_NULL_TERM_STR +# define MHD_FN_PAR_CSTR_(param_num) \ + __attribute__ ((null_terminated_string_arg (param_num))) +#else +# define MHD_FN_PAR_CSTR_(param_num) /* empty */ +#endif + +#ifdef HAVE_FUNC_PARAM_ARR_STATIC_FIXED +# define MHD_FN_PAR_FIX_ARR_SIZE_(size) static size +#else +# define MHD_FN_PAR_FIX_ARR_SIZE_(size) size +#endif + +#ifdef HAVE_FUNC_PARAM_ARR_STATIC_VAR +# define MHD_FN_PAR_DYN_ARR_SIZE_(size) static size +#else +# define MHD_FN_PAR_DYN_ARR_SIZE_(size) 1 +#endif + +#ifdef HAVE_ATTR_ENUM_EXTNS_CLOSED +# define MHD_FIXED_ENUM_ __attribute__((enum_extensibility (closed))) +#else +# define MHD_FIXED_ENUM_ /* empty */ +#endif + +#ifdef HAVE_ATTR_FLAG_ENUM +# define MHD_FLAGS_ENUM_ __attribute__((flag_enum)) +#else +# define MHD_FLAGS_ENUM_ /* empty */ +#endif /* MHD_FLAGS_ENUM_ */ + +#define MHD_FIXED_FLAGS_ENUM_ MHD_FIXED_ENUM_ MHD_FLAGS_ENUM_ + +/* 'inline' and 'restrict' are defined in mhd_config.h if needed */ +#define MHD_INLINE inline + +#define MHD_RESTRICT restrict + +#ifdef BUILDING_MHD_LIB +# define MHD_FIXED_ENUM_APP_SET_ /* empty */ /* handle unknown values set by the app */ +#else +# define MHD_FIXED_ENUM_APP_SET_ MHD_FIXED_ENUM_ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FLAGS_ENUM_APP_SET_ MHD_FLAGS_ENUM_ +#else +# define MHD_FLAGS_ENUM_APP_SET_ MHD_FLAGS_ENUM_ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FIXED_FLAGS_ENUM_APP_SET_ MHD_FLAGS_ENUM_ /* handle unknown values set by the app */ +#else +# define MHD_FIXED_FLAGS_ENUM_APP_SET_ MHD_FIXED_FLAGS_ENUM_ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FIXED_ENUM_MHD_SET_ MHD_FIXED_ENUM_ +#else +# define MHD_FIXED_ENUM_MHD_SET_ /* empty */ /* enum can be extended in next MHD versions */ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FLAGS_ENUM_MHD_SET_ MHD_FLAGS_ENUM_ +#else +# define MHD_FLAGS_ENUM_MHD_SET_ MHD_FLAGS_ENUM_ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FIXED_FLAGS_ENUM_MHD_SET_ MHD_FIXED_FLAGS_ENUM_ +#else +# define MHD_FIXED_FLAGS_ENUM_MHD_SET_ MHD_FLAGS_ENUM_ /* enum can be extended in next MHD versions */ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FIXED_ENUM_MHD_APP_SET_ /* empty */ /* handle unknown values set by the app */ +#else +# define MHD_FIXED_ENUM_MHD_APP_SET_ /* empty */ /* enum can be extended in next MHD versions */ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FLAGS_ENUM_MHD_APP_SET_ MHD_FLAGS_ENUM_ +#else +# define MHD_FLAGS_ENUM_MHD_APP_SET_ MHD_FLAGS_ENUM_ +#endif + +#ifdef BUILDING_MHD_LIB +# define MHD_FIXED_FLAGS_ENUM_MHD_APP_SET_ MHD_FLAGS_ENUM_ /* handle unknown values set by the app */ +#else +# define MHD_FIXED_FLAGS_ENUM_MHD_APP_SET_ MHD_FLAGS_ENUM_ /* enum can be extended in next MHD versions */ +#endif + +/** + * Automatic string with the name of the current function + */ +#if defined(HAVE___FUNC__) +# define MHD_FUNC_ __func__ +# define MHD_HAVE_MHD_FUNC_ 1 +#elif defined(HAVE___FUNCTION__) +# define MHD_FUNC_ __FUNCTION__ +# define MHD_HAVE_MHD_FUNC_ 1 +#elif defined(HAVE___PRETTY_FUNCTION__) +# define MHD_FUNC_ __PRETTY_FUNCTION__ +# define MHD_HAVE_MHD_FUNC_ 1 +#else +# define MHD_FUNC_ "**name unavailable**" +# ifdef MHD_HAVE_MHD_FUNC_ +# undef MHD_HAVE_MHD_FUNC_ +# endif /* MHD_HAVE_MHD_FUNC_ */ +#endif + +/* Some platforms (FreeBSD, Solaris, W32) allow to override + default FD_SETSIZE by defining it before including + headers. */ +#ifdef FD_SETSIZE +/* FD_SETSIZE defined in command line or in mhd_config.h */ +#elif defined(_WIN32) || defined(__CYGWIN__) +/* Platform with WinSock and without overridden FD_SETSIZE */ +# ifdef _WIN64 +# define FD_SETSIZE 4096 /* Override default small value (64) */ +# else +# define FD_SETSIZE 1024 /* Override default small value (64) */ +# endif +#else /* !FD_SETSIZE && !W32 */ +/* System default value of FD_SETSIZE is used */ +# define MHD_FD_SETSIZE_IS_DEFAULT_ 1 +#endif /* !FD_SETSIZE && !W32 */ + +#if defined(HAVE_LINUX_SENDFILE) || defined(HAVE_FREEBSD_SENDFILE) || \ + defined(HAVE_DARWIN_SENDFILE) +/* Have any supported sendfile() function. */ +# define MHD_USE_SENDFILE 1 +#endif /* HAVE_LINUX_SENDFILE || HAVE_FREEBSD_SENDFILE + || HAVE_DARWIN_SENDFILE */ + +#if defined(MHD_USE_POSIX_THREADS) || defined(MHD_USE_W32_THREADS) +# ifndef MHD_USE_THREADS +# define MHD_USE_THREADS 1 +# endif +#endif /* MHD_USE_POSIX_THREADS || MHD_USE_W32_THREADS */ + +/** + * Macro to drop 'const' qualifier from pointer. + * Try to avoid compiler warning. + * To be used *only* to deal with broken external APIs, which require non-const + * pointer to unmodifiable data. + * Must not be used to transform pointers for internal MHD needs. + */ +#ifdef HAVE_UINTPTR_T +# define mhd_DROP_CONST(ptr) ((void *) ((uintptr_t) ((const void *) (ptr)))) +#else +# define mhd_DROP_CONST(ptr) ((void *) ((const void *) (ptr))) +#endif + + +#if defined(OS390) +#define _OPEN_THREADS +#define _OPEN_SYS_SOCK_IPV6 +#define _OPEN_MSGQ_EXT +#define _LP64 +#endif + +#if defined(_WIN32) && ! defined(__CYGWIN__) +/* Declare POSIX-compatible names */ +# define _CRT_DECLARE_NONSTDC_NAMES 1 +/* Do not warn about POSIX name usage */ +# define _CRT_NONSTDC_NO_WARNINGS 1 +# ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0600 +# else /* _WIN32_WINNT */ +# if _WIN32_WINNT < 0x0501 +#error "Headers for Windows XP or later are required" +# endif /* _WIN32_WINNT < 0x0501 */ +# endif /* _WIN32_WINNT */ +# ifndef WIN32_LEAN_AND_MEAN +/* Do not include unneeded parts of W32 headers. */ +# define WIN32_LEAN_AND_MEAN 1 +# endif /* !WIN32_LEAN_AND_MEAN */ +#endif /* _WIN32 && ! __CYGWIN__ */ + +#if defined(__MINGW32__) +# ifdef __USE_MINGW_ANSI_STDIO +# define __USE_MINGW_ANSI_STDIO 0 /* Force use native printf, the code is well-adapted */ +# endif +#endif + +#if defined(__VXWORKS__) || defined(__vxworks) || defined(OS_VXWORKS) +#define RESTRICT __restrict__ +#endif /* __VXWORKS__ || __vxworks || OS_VXWORKS */ + +#if defined(LINUX) && (defined(HAVE_SENDFILE64) || defined(HAVE_LSEEK64)) && \ + ! defined(_LARGEFILE64_SOURCE) +/* On Linux, special macro is required to enable definitions of some xxx64 functions */ +#define _LARGEFILE64_SOURCE 1 +#endif + +#ifdef HAVE_C11_GMTIME_S +/* Special macro is required to enable C11 definition of gmtime_s() function */ +#define __STDC_WANT_LIB_EXT1__ 1 +#endif /* HAVE_C11_GMTIME_S */ + +#if ! defined(_DEBUG) && ! defined(NDEBUG) +# ifndef DEBUG /* Used by some toolchains */ +# define NDEBUG 1 /* Use NDEBUG by default */ +# else /* DEBUG */ +# define _DEBUG 1 /* Non-standart macro */ +# endif /* DEBUG */ +#endif /* !_DEBUG && !NDEBUG */ + +#if defined(MHD_FAVOR_FAST_CODE) && defined(MHD_FAVOR_SMALL_CODE) +#error MHD_FAVOR_FAST_CODE and MHD_FAVOR_SMALL_CODE are both defined. +#error Cannot favor speed and size at the same time. +#endif /* MHD_FAVOR_FAST_CODE && MHD_FAVOR_SMALL_CODE */ + +/* Define MHD_FAVOR_FAST_CODE to force fast code path or + define MHD_FAVOR_SMALL_CODE to choose compact code path */ +#if ! defined(MHD_FAVOR_FAST_CODE) && ! defined(MHD_FAVOR_SMALL_CODE) +/* Try to detect user preferences */ +/* Defined by GCC and many compatible compilers */ +# if defined(__OPTIMIZE_SIZE__) +# define MHD_FAVOR_SMALL_CODE 1 +# elif defined(__OPTIMIZE__) +# define MHD_FAVOR_FAST_CODE 1 +# endif /* __OPTIMIZE__ */ +#endif /* !MHD_FAVOR_FAST_CODE && !MHD_FAVOR_SMALL_CODE */ + +#if ! defined(MHD_FAVOR_FAST_CODE) && ! defined(MHD_FAVOR_SMALL_CODE) +/* Use faster code by default */ +# define MHD_FAVOR_FAST_CODE 1 +#endif /* !MHD_FAVOR_FAST_CODE && !MHD_FAVOR_SMALL_CODE */ + +#if defined(MHD_FAVOR_SMALL_CODE) && defined(MHD_static_inline_) +# undef MHD_static_inline_ +# define MHD_static_inline_ static inline /* give compiler more freedom */ +#endif + +#ifndef MHD_ASAN_ACTIVE +#if (defined(__GNUC__) || defined(_MSC_VER)) && defined(__SANITIZE_ADDRESS__) +#define MHD_ASAN_ACTIVE 1 +#elif defined(__has_feature) +#if __has_feature (address_sanitizer) +#define MHD_ASAN_ACTIVE 1 +#endif /* __has_feature(address_sanitizer) */ +#endif /* __has_feature */ +#endif /* MHD_ASAN_ACTIVE */ + +#if defined(MHD_ASAN_ACTIVE) && defined(HAVE_SANITIZER_ASAN_INTERFACE_H) && \ + (defined(FUNC_PTRCOMPARE_CAST_WORKAROUND_WORKS) || \ + (defined(FUNC_ATTR_PTRCOMPARE_WORKS) && \ + defined(FUNC_ATTR_PTRSUBTRACT_WORKS)) || \ + defined(FUNC_ATTR_NOSANITIZE_WORKS)) +#ifndef MHD_ASAN_POISON_ACTIVE +/* User ASAN poisoning could be used */ +#warning User memory poisoning is not active +#endif /* ! MHD_ASAN_POISON_ACTIVE */ +#else /* ! (MHD_ASAN_ACTIVE && HAVE_SANITIZER_ASAN_INTERFACE_H && + (FUNC_ATTR_PTRCOMPARE_WORKS || FUNC_ATTR_NOSANITIZE_WORKS)) */ +#ifdef MHD_ASAN_POISON_ACTIVE +#error User memory poisoning is active, but conditions are not suitable +#endif /* MHD_ASAN_POISON_ACTIVE */ +#endif /* ! (MHD_ASAN_ACTIVE && HAVE_SANITIZER_ASAN_INTERFACE_H && + (FUNC_ATTR_PTRCOMPARE_WORKS || FUNC_ATTR_NOSANITIZE_WORKS)) */ + +#ifndef _MSC_FULL_VER +# define MHD_DATA_TRUNCATION_RUNTIME_CHECK_DISABLE_ /* empty */ +# define MHD_DATA_TRUNCATION_RUNTIME_CHECK_RESTORE_ /* empty */ +#else /* _MSC_FULL_VER */ +# define MHD_DATA_TRUNCATION_RUNTIME_CHECK_DISABLE_ \ + __pragma(runtime_checks("c", off)) +# define MHD_DATA_TRUNCATION_RUNTIME_CHECK_RESTORE_ \ + __pragma(runtime_checks("c", restore)) +#endif /* _MSC_FULL_VER */ + +/* Un-define some HAVE_DECL_* macro if they equal zero. + This should allow safely use #ifdef in the code. + Define HAS_DECL_* macros only if matching HAVE_DECL_* macro + has non-zero value. Unlike HAVE_DECL_*, macros HAS_DECL_* + cannot have zero value. */ +#ifdef HAVE_DECL__SC_NPROCESSORS_ONLN +# if 0 == HAVE_DECL__SC_NPROCESSORS_ONLN +# undef HAVE_DECL__SC_NPROCESSORS_ONLN +# else /* 0 != HAVE_DECL__SC_NPROCESSORS_ONLN */ +# define HAS_DECL__SC_NPROCESSORS_ONLN 1 +# endif /* 0 != HAVE_DECL__SC_NPROCESSORS_ONLN */ +#endif /* HAVE_DECL__SC_NPROCESSORS_ONLN */ + +#ifdef HAVE_DECL__SC_NPROCESSORS_CONF +# if 0 == HAVE_DECL__SC_NPROCESSORS_CONF +# undef HAVE_DECL__SC_NPROCESSORS_CONF +# else /* 0 != HAVE_DECL__SC_NPROCESSORS_CONF */ +# define HAS_DECL__SC_NPROCESSORS_CONF 1 +# endif /* 0 != HAVE_DECL__SC_NPROCESSORS_CONF */ +#endif /* HAVE_DECL__SC_NPROCESSORS_CONF */ + +#ifdef HAVE_DECL__SC_NPROC_ONLN +# if 0 == HAVE_DECL__SC_NPROC_ONLN +# undef HAVE_DECL__SC_NPROC_ONLN +# else /* 0 != HAVE_DECL__SC_NPROC_ONLN */ +# define HAS_DECL__SC_NPROC_ONLN 1 +# endif /* 0 != HAVE_DECL__SC_NPROC_ONLN */ +#endif /* HAVE_DECL__SC_NPROC_ONLN */ + +#ifdef HAVE_DECL__SC_CRAY_NCPU +# if 0 == HAVE_DECL__SC_CRAY_NCPU +# undef HAVE_DECL__SC_CRAY_NCPU +# else /* 0 != HAVE_DECL__SC_CRAY_NCPU */ +# define HAS_DECL__SC_CRAY_NCPU 1 +# endif /* 0 != HAVE_DECL__SC_CRAY_NCPU */ +#endif /* HAVE_DECL__SC_CRAY_NCPU */ + +#ifdef HAVE_DECL_CTL_HW +# if 0 == HAVE_DECL_CTL_HW +# undef HAVE_DECL_CTL_HW +# else /* 0 != HAVE_DECL_CTL_HW */ +# define HAS_DECL_CTL_HW 1 +# endif /* 0 != HAVE_DECL_CTL_HW */ +#endif /* HAVE_DECL_CTL_HW */ + +#ifdef HAVE_DECL_HW_NCPUONLINE +# if 0 == HAVE_DECL_HW_NCPUONLINE +# undef HAVE_DECL_HW_NCPUONLINE +# else /* 0 != HAVE_DECL_HW_NCPUONLINE */ +# define HAS_DECL_HW_NCPUONLINE 1 +# endif /* 0 != HAVE_DECL_HW_NCPUONLINE */ +#endif /* HAVE_DECL_HW_NCPUONLINE */ + +#ifdef HAVE_DECL_HW_AVAILCPU +# if 0 == HAVE_DECL_HW_AVAILCPU +# undef HAVE_DECL_HW_AVAILCPU +# else /* 0 != HAVE_DECL_HW_AVAILCPU */ +# define HAS_DECL_HW_AVAILCPU 1 +# endif /* 0 != HAVE_DECL_HW_AVAILCPU */ +#endif /* HAVE_DECL_HW_AVAILCPU */ + +#ifdef HAVE_DECL_HW_NCPU +# if 0 == HAVE_DECL_HW_NCPU +# undef HAVE_DECL_HW_NCPU +# else /* 0 != HAVE_DECL_HW_NCPU */ +# define HAS_DECL_HW_NCPU 1 +# endif /* 0 != HAVE_DECL_HW_NCPU */ +#endif /* HAVE_DECL_HW_NCPU */ + +#ifdef HAVE_DECL_CPU_SETSIZE +# if 0 == HAVE_DECL_CPU_SETSIZE +# undef HAVE_DECL_CPU_SETSIZE +# else /* 0 != HAVE_DECL_CPU_SETSIZE */ +# define HAS_DECL_CPU_SETSIZE 1 +# endif /* 0 != HAVE_DECL_CPU_SETSIZE */ +#endif /* HAVE_DECL_CPU_SETSIZE */ + +#ifndef MHD_DAUTH_DEF_TIMEOUT_ +# define MHD_DAUTH_DEF_TIMEOUT_ 90 +#endif /* ! MHD_DAUTH_DEF_TIMEOUT_ */ +#ifndef MHD_DAUTH_DEF_MAX_NC_ +# define MHD_DAUTH_DEF_MAX_NC_ 1000 +#endif /* ! MHD_DAUTH_DEF_MAX_NC_ */ + +/* Eclipse parse compatibility */ +#ifdef __CDT_PARSER__ +# undef MHD_NORETURN_ +# define MHD_NORETURN_ __attribute__((__noreturn__)) +#endif + +/* Avoid interference with third-party headers */ +#undef HAVE_CONFIG_H + +#endif /* MHD_SYS_OPTIONS_H */ diff --git a/src/include/mhd_future.h b/src/include/mhd_future.h @@ -0,0 +1,358 @@ +#ifndef MHD_EXTERN_ +# if ! defined(_WIN32) +# define MHD_EXTERN_ extern +# else /* defined(_WIN32) */ +# if ! defined(MHD_W32LIB) +# define MHD_EXTERN_ extern +# else /* defined(_WIN32) && efined(MHD_W32LIB) */ +/* Define MHD_W32DLL when using MHD as W32 .DLL to speed up linker a little */ +# define MHD_EXTERN_ extern __declspec(dllimport) +# endif +# endif +#endif + + +#ifndef MHD_FIXED_ENUM_ +# define MHD_FIXED_ENUM_ /* empty */ +#endif /* MHD_FIXED_ENUM_ */ +#ifndef MHD_FLAGS_ENUM_ +# define MHD_FLAGS_ENUM_ /* empty */ +#endif /* MHD_FLAGS_ENUM_ */ + +#ifndef MHD_FIXED_FLAGS_ENUM_ +# define MHD_FIXED_FLAGS_ENUM_ MHD_FIXED_ENUM_ MHD_FLAGS_ENUM_ +#endif + +#ifndef MHD_FIXED_ENUM_APP_SET_ +/* The enum is set by an application to the fixed list of values */ +# define MHD_FIXED_ENUM_APP_SET_ MHD_FIXED_ENUM_ +#endif + +#ifndef MHD_FLAGS_ENUM_APP_SET_ +/* The enum is set by an application, it is a bitmap */ +# define MHD_FLAGS_ENUM_APP_SET_ MHD_FLAGS_ENUM_ +#endif + +#ifndef MHD_FIXED_FLAGS_ENUM_APP_SET_ +/* The enum is set by an application to the fixed bitmap values */ +# define MHD_FIXED_FLAGS_ENUM_APP_SET_ MHD_FIXED_FLAGS_ENUM_ +#endif + +#ifndef MHD_FIXED_ENUM_MHD_SET_ +/* The enum is set by MHD to the fixed list of values */ +# define MHD_FIXED_ENUM_MHD_SET_ /* enum can be extended in next MHD versions */ +#endif + +#ifndef MHD_FLAGS_ENUM_MHD_SET_ +/* The enum is set by MHD, it is a bitmap */ +# define MHD_FLAGS_ENUM_MHD_SET_ MHD_FLAGS_ENUM_ +#endif + +#ifndef MHD_FIXED_FLAGS_ENUM_MHD_SET_ +/* The enum is set by MHD to the fixed bitmap values */ +# define MHD_FIXED_FLAGS_ENUM_MHD_SET_ MHD_FLAGS_ENUM_ /* enum can be extended in next MHD versions */ +#endif + +#ifndef MHD_FIXED_ENUM_MHD_APP_SET_ +/* The enum is set by both MHD and app to the fixed list of values */ +# define MHD_FIXED_ENUM_MHD_APP_SET_ /* enum can be extended in next MHD versions */ +#endif + +#ifndef MHD_FLAGS_ENUM_MHD_APP_SET_ +/* The enum is set by both MHD and app, it is a bitmap */ +# define MHD_FLAGS_ENUM_MHD_APP_SET_ MHD_FLAGS_ENUM_ +#endif + +#ifndef MHD_FIXED_FLAGS_ENUM_MHD_APP_SET_ +/* The enum is set by both MHD and app to the fixed bitmap values */ +# define MHD_FIXED_FLAGS_ENUM_MHD_APP_SET_ MHD_FLAGS_ENUM_ /* enum can be extended in next MHD versions */ +#endif + + +#ifndef MHD_FN_CONST_ +# define MHD_FN_CONST_ /* empty */ +#endif /* ! MHD_FN_CONST_ */ +#ifndef MHD_FN_PURE_ +# define MHD_FN_PURE_ /* empty */ +#endif /* ! MHD_FN_PURE_ */ +#ifndef MHD_FN_MUST_CHECK_RESULT_ +# define MHD_FN_MUST_CHECK_RESULT_ /* empty */ +#endif /* ! MHD_FN_MUST_CHECK_RESULT_ */ +#ifndef MHD_FN_PAR_NONNULL_ +# define MHD_FN_PAR_NONNULL_(param_num) /* empty */ +#endif /* ! MHD_FN_PAR_NONNULL_ */ +#ifndef MHD_FN_PAR_NONNULL_ALL_ +# define MHD_FN_PAR_NONNULL_ALL_ /* empty */ +#endif /* ! MHD_FN_PAR_NONNULL_ALL_ */ +#ifndef MHD_FN_PAR_IN_ +# define MHD_FN_PAR_IN_(param_num) /* empty */ +#endif /* !MHD_FN_PAR_IN_ */ +#ifndef MHD_FN_PAR_IN_SIZE_ +# define MHD_FN_PAR_IN_SIZE_(param_num,size_num) /* empty */ +#endif /* !MHD_FN_PAR_IN_SIZE_ */ +#ifndef MHD_FN_PAR_OUT_ +# define MHD_FN_PAR_OUT_(param_num) /* empty */ +#endif /* !MHD_FN_PAR_OUT_ */ +#ifndef MHD_FN_PAR_OUT_SIZE_ +# define MHD_FN_PAR_OUT_SIZE_(param_num,size_num) /* empty */ +#endif /* !MHD_FN_PAR_OUT_SIZE_ */ +#ifndef MHD_FN_PAR_INOUT_ +# define MHD_FN_PAR_INOUT_(param_num) /* empty */ +#endif /* !MHD_FN_PAR_INOUT_ */ +#ifndef MHD_FN_PAR_INOUT_SIZE_ +# define MHD_FN_PAR_INOUT_SIZE_(param_num,size_num) /* empty */ +#endif /* !MHD_FN_PAR_INOUT_SIZE_ */ +#ifndef MHD_FN_PAR_FD_READ_ +# define MHD_FN_PAR_FD_READ_(param_num) /* empty */ +#endif /* !MHD_FN_PAR_FD_READ_ */ +#ifndef MHD_FN_PAR_CSTR_ +# define MHD_FN_PAR_CSTR_(param_num) /* empty */ +#endif /* ! MHD_FN_PAR_CSTR_ */ +#ifndef MHD_FN_RETURNS_NONNULL_ +# define MHD_FN_RETURNS_NONNULL_ /* empty */ +#endif /* ! MHD_FN_RETURNS_NONNULL_ */ + + +/** + * Which threading and polling mode should be used by MHD? + */ +enum MHD_FIXED_ENUM_MHD_SET_ MHD_ThreadingPollingMode +{ + /** + * Use an external event loop. + * Application uses #MHD_get_watched_fds()/#MHD_get_watched_fds_update() + * and #MHD_process_watched_fds() with level triggered sockets + * polling (like select() or poll()). + */ + MHD_TM_EXTERNAL_EVENT_LOOP_WFD_LEVEL = 10 + , + /** + * Use an external event loop. + * Application uses #MHD_get_watched_fds()/#MHD_get_watched_fds_update() + * and #MHD_process_watched_fds() with edge triggered sockets polling. + */ + MHD_TM_EXTERNAL_EVENT_LOOP_WFD_EDGE = 11 + , +}; + +/* FUTURE: + (with eventually option "number of masters") + MHD_TM_WORKER_THREADS_WITH_MASTER_LISTENER = 3 */ + + +// Alternative style + +struct MHD_WatchedFD +{ + /** + * The watched socket. + * Ignored if set by application to #MHD_INVALID_SOCKET. TODO: Improve wording + */ + MHD_Socket fd; + + /** + * Indicates that socket should be watched for specific network state + * (when set by #MHD_get_watched_fds(), #MHD_get_watched_fds_update()) + * / the network state of the socket (when used for + * #MHD_process_watched_fds()) + */ + enum MHD_FdState state; +}; + +/** + * Get the full list of the sockets that must be watched by application. + * + * The application may use this function each time to get a full list of + * the sockets for watch or may use #MHD_get_watched_fds_update() to + * get the incremental updates. + * + * // TODO: add introspection reference + * + * @param daemon the daemon to get the list + * @param num_elements the number of elements in @a fds list + * @param[out] wfds the arrays of @a num_elements of sockets to be watched + * by application, the unused elements (if any) at + * the end of the array are filled with + * { MHD_INVALID_SOCKET, MHD_FD_STATE_NONE } + * @param[out] max_wait the pointer to value set to maximum wait time + * for the network events, in microseconds + * @return ::MHD_SC_OK on success, + * error code otherwise + * @ingroup event + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_get_watched_fds ( + struct MHD_Daemon *daemon, + unsigned int num_elements, + struct MHD_WatchedFD wfds[MHD_FN_PAR_DYN_ARR_SIZE_ (num_elements)], + uint_fast64_t *max_wait) +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_OUT_(3) MHD_FN_PAR_NONNULL_(3) +MHD_FN_PAR_OUT_(4) MHD_FN_PAR_NONNULL_ (4); + + +enum MHD_WatchedFdAction +{ + /** + * New watched FD, to be added to the list + */ + MHD_WFA_ADD = 1 + , + /** + * Update watching interest in already watched FD + */ + MHD_WFA_UPDATE = 2 + , + /** + * Delete FD from watching list + */ + MHD_WFA_REMOVE = 3 + , + /** + * No action. Used to fill the end of the array + * The matching FD is always #MHD_INVALID_SOCKET. + */ + MHD_WFA_NONE = 0 +}; + +struct MHD_WatchedFdUpdate +{ + /** + * The required action: add/update/delete + */ + enum MHD_WatchedFdAction action; + + /** + * The watched FD to add, update or delete. + */ + struct MHD_WatchedFD watched_fd; +}; + +/** + * Get the update of the list of the sockets that must be watched + * by application. + * This function provides an update to the list of watched sockets + * since the last call of #MHD_get_watched_fds() or + * #MHD_get_watched_fds_update(). + * If this function is called before #MHD_get_watched_fds() then it + * returns full list of sockets to watch with action #MHD_WFA_ADD. + * + * @param daemon the daemon to get the list + * @param num_elements the number of elements in @a fds list + * @param[out] wfdus the arrays of @a num_elements to update the list + * of watched sockets, the unused elements (if any) at + * the end of the array are filled with + * { MHD_WFA_NONE, { MHD_INVALID_SOCKET, MHD_FD_STATE_NONE } } + * @param[out] max_wait the pointer to value set to maximum wait time + * for the network events, in microseconds + * @return ::MHD_SC_OK on success, + * error code otherwise + * @ingroup event + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_get_watched_fds_update ( + struct MHD_Daemon *daemon, + unsigned int num_elements, + struct MHD_WatchedFdUpdate wfdus[MHD_FN_PAR_DYN_ARR_SIZE_ (num_elements)], + uint_fast64_t *max_wait) +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_OUT_(3) MHD_FN_PAR_NONNULL_(3) +MHD_FN_PAR_OUT_(4) MHD_FN_PAR_NONNULL_ (4); +; + + +/** + * Perform round of sockets processing, including receiving, sending, + * data processing, sockets closing and other things. + * @param daemon the daemon to process + * @param num_elements the number of elements in the @a fds array + * @param fds the array of watched sockets, must be complete list of + * all watched sockets level sockets triggering used or + * could be just partial list if edge sockets triggering used + * @return ::MHD_SC_OK on success, + * otherwise error code TODO: complete list of error codes + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_process_watched_fds ( + struct MHD_Daemon *daemon, + unsigned int num_elements, + const struct MHD_WatchedFD fds[MHD_FN_PAR_DYN_ARR_SIZE_ (num_elements)]) +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_IN_(3) MHD_FN_PAR_NONNULL_ (3); + + +// FIXME: convert introspection +/** + * Obtain timeout value for polling function for this daemon. + * + * This function set value to amount of milliseconds for which polling + * function (`select()` or `poll()`) should at most block, not the + * timeout value set for connections. + * It is important to always use this function, even if connection + * timeout is not set, as in some cases MHD may already have more + * data to process on next turn (data pending in TLS buffers, + * connections are already ready with epoll etc.) and returned timeout + * will be zero. + * + * @param[in,out] daemon daemon to query for timeout + * @param[out] timeout set to the timeout (in milliseconds), + * #MHD_WAIT_INDEFINITELY if timeouts are // FIXME: redesigned + * not used (or no connections exist that would + * necessitate the use of a timeout right now) + * @return #MHD_SC_OK on success, otherwise + * an error code + * @ingroup event + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_daemon_ext_polling_get_max_wait (struct MHD_Daemon *daemon, + uint_fast64_t *timeout) +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_OUT_ (2); + +// FIXME: gana? table for RFC 7541... +// TODO: extract https://www.rfc-editor.org/rfc/rfc7541.html#appendix-A +enum MHD_PredefinedHeader; + + +/** + * Get last occurrence of a particular header value under + * the given @a skt. + * + * The pointer to the string in @a value is valid until the response + * is queued. If the data is needed beyond this point, it should be copied. + * + * @param[in,out] request request to get values from + * @param kind what kind of value are we looking for + * @param skt the header to look for based on RFC 7541 Appendix A. + * @param[out] value the found value, the str pointer set to + * NULL if nothing is found + * @return #MHD_SC_OK if found, + * error code otherwise + * @ingroup request + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_request_lookup_value_by_static_header (struct MHD_Request *request, + enum MHD_ValueKind kind, + enum MHD_PredefinedHeader skt, + struct MHD_StringNullable *value) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (4); // TODO: convert like previous + + +/* FUTURE: + (with eventually option "number of masters") + MHD_TM_WORKER_THREADS_WITH_MASTER_LISTENER = 3 */ + + +// FIXME: remove completely? +/** + * Configure DH parameters (dh.pem) to use for the TLS key + * exchange. + * + * @param daemon daemon to configure tls for + * @param dh parameters to use + * @return #MHD_SC_OK upon success; TODO: define failure modes + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_daemon_tls_mem_dhparams (struct MHD_Daemon *daemon, + const char *dh) +MHD_FN_PAR_NONNULL_ (1); diff --git a/src/include/this_API_is_TERRIBLE.txt b/src/include/this_API_is_TERRIBLE.txt @@ -0,0 +1,137 @@ +- The header is NOT readable: +-- Having the **third** version of the every type of options ("documenting...") + makes reading even worse. Absolutely unclear *why* the reader should treat + the excluded code as an actual code. Not clear how it is supposed to work. + I hardly believe that many readers will just blindly follow disabled part + of the header. In practice it would be the opposite: many readers just + ignore disabled parts of the headers (as IDEs gray-out or even hide them). +-- IDEs will be cryptic as well when decoding hierarchical macros (the options + itself, then macros that set the options). +-- IDEs will not point to "generated ... documenting", when one would try + to follow the definition/declaration +-- Trying to "grep" for the keyword (as previously suggested way to find the + declaration) is giving too many false results. This makes understanding + even more complicated and even less straightforward. +-- '#include "microhttpd2_generated_daemon_options.h"' is far from "generated + code documenting..." +-- All macros that switch from "static inline" to macros with compound + literals are not clear for the user. Even right now it is not clear how + they are supposed to work. Not clear that one form is used for C compilers, + while another form is used for C++ compilers (and, actually, the third + possible version, for not compatible compilers, for C89, and before C++11 + however it is not the largest concern). It is not clear that app code is + supposed to look the same in both C and C++. Actually, even with modern + compilers (and language versions) there are three possible combinations: + - macros for option with macro to make an array (C), + - static functions for options with macro to make an array (C++ with clang), + - static functions with vector (C++ in general). + And this is repeated for every section with the options. +-- Functions with ALL CAPS (like MHD_D_OPTION_WORK_MODE) are hard to read. ALL + CAPS are usually macros. +-- The "parts" of the main headers are badly visualised by IDEs as they are + incomplete (do not have macros used in declarations, like + MHD_FIXED_ENUM_APP_SET_). +-- Having both MHD_D_O_XXX and (two times of each) MHD_D_OPTION_XXX is + confusion and not obvious. Even you was confused with similar names and + used MHD_D_OPTION_XXX as switch values, while it must be MHD_D_O_XXX. +-- Try to work with simple things: find how to set "epoll" syscall; find how + to enable internal thread pool. The header is written in the way that + is very inconvenient to find needed options and the way how to set them. +-- The API should be self-documenting. This is main goal defined everywhere. + This API is **NOT** self-documenting. Three levels of macros for basic + things, like "work mode" is not acceptable! + +- This design is less secure by nature. The sizes of arrays or memory + allocations cannot be checked by compilers (like + "size_t num, int arr[num]"). +- Automatic generated setting function cannot check for build-time configurable + features availability, neither for system/platform available features. + Only daemon_start() can make any checks leaving no chance for the application + to understand where the problem is. The result is less detailed and less + flexible API. + + +- Some generated settings processing functions are incorrect. When setting is + documented to ignore some specific parameter value (like MHD_INVALID_SOCKET), + it must NOT override previously set value. This requires even more + complicated generation. +- Some options (MHD_D_O_BIND_SA, MHD_D_O_RANDOM_ENTROPY) require copying of + user memory. Need either further heavy customizing or kind of manual + exceptions. Future "large" options will need malloc() and copy for each(!) of + them during "set" phase, without error checking for the options content. +- Generated settings and set function cannot skip code parts excluded by + configure parameters, making the library less flexible and less "micro". +- Some settings require different storage format and parameters format. + For example, when parameter is copied, the storage format shouldn't be + pointer to const as we need to free it. + +It is supposed to make the header maintainable so someone may take care about +it later. BUT +- The header is hardly maintainable: +-- The structure of generated parts, static parts, databases and the build + system is not obvious at all. You need to dig very deep just to understand + now to start modifying it. It is NOT a good design. + The worst thing that actually the current version is not good enough for + the production. The production version has to be even more complicated, + which makes it even less maintainable. +-- Simple question: what if it will be needed to change the formatting of + header? Which file should I edit? Is it obvious? For example, change + the indent. +-- Adding new type of settings (for example, for the HTTP stream settings) is + complicated and multiply already large number of input files. +-- It is always not nice that relatively often modifiable file (the main + header) cannot be modified directly. Every time when I change something + in the main header, I have to run the build system before starting using + the new header. This may require update of the generated makefiles or re-run + the configure. Currently re-run of configure takes 6 minutes on W32. + With the additional checks for build host compilers (and other new checks) + it will be even longer. So, more then 6 minutes after edit of some part + of the header before getting the result. No efficient at all! +-- Quite inconvenient edit generated sources file by editing other files. Even + minor corrections (like change list of included files) becomes problematic, + as it may change the generation logic. In long term it is a bad design. +-- To insert the "generated code documenting" in different parts of the header, + more slicing of the main header is required. Currently it must be at least + four parts of the header, with every additional type of option the number + will increase further. It is easy to lost between parts of the main header. + Editing of any part of the header becoming more time-consuming, as every + time it is required to find which part is needed to be edited. +-- Too error-prone, like not working macro for "per_ip_limit". All macro + parameters must be different from any token used within the macro. +-- The future parameters that require larger size will need pointers, + which break "the beauty" of the client use, which is the main goal. + +- The build system for the header is broken currently. +-- Need to add the section to the configure for the build compilers, document + additional parameters, pass them correctly to makefiles, make custom build + rules for binaries for build host (libtool cannot be used), make everything + similar to rules for target host +-- Need to fix makefiles for out-of-tree builds +-- Need to fix makefiles for parallel builds (this is not easy! check the "po" + part) +-- Need to fix the generator itself. Currently it is not POSIX, is using + unportable extensions. Need to make it with proper memory handling and + correct checking for all errors. "It works fast" will not be an excuse for + security audits. It sounds like "I'll commit a crime, but I'll do it very + quickly, so it will not be a crime". Memory leaks are not acceptable for + proper design, even on developers machines. + I expect also many reports from users that use any kind of "sanitizers" or + analysers that immaculately report tons of problems. + In short: we cannot afford this code in our repo. +-- More inputs requires more checks to keep them in sync. More test, more + makefile rules, more problems with "make distcheck". +-- All these fixes makes build system even more complicated, even less obvious, + even less readable, even harder maintainable. Problems will be multiplied + with additional types of the settings. + +! Errors: Doxy lines are too long. +! Errors: Some doxy lines are broken (do not start with " *") +! Errors: Several "@param"s are in single doxy line +! Errors: MHD_D_OPTION_PER_IP_LIMIT does not work - parameter token used as member name +! Wrong: The body of some static functions with single parameter are formed as macros + +Positive: ++ The exclude of "portability" macros is fine (actually, the excluded part is + not about the portability only). However, even this part is easier to use + when it is fully integrated. To keep it separated the scope must be reduced, + to avoid having types declarations in it. diff --git a/src/mhd2/.gitignore b/src/mhd2/.gitignore @@ -0,0 +1,2 @@ +/w32_lib_res.rc +/libmicrohttpd2.pc diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am @@ -0,0 +1,160 @@ +# This Makefile.am is in the public domain + +AM_CPPFLAGS = \ + -I$(srcdir)/../incl_priv \ + -I$(srcdir)/../include \ + $(CPPFLAGS_ac) + +AM_CFLAGS = $(CFLAGS_ac) + +if USE_COVERAGE + AM_CFLAGS += --coverage +endif + +AM_LDFLAGS = $(LDFLAGS_ac) + +AM_TESTS_ENVIRONMENT = $(TESTS_ENVIRONMENT_ac) + +lib_LTLIBRARIES = \ + libmicrohttpd2.la + +noinst_DATA = +MOSTLYCLEANFILES = + +libmicrohttpd2_la_SOURCES = \ + $(CONFIG_HEADER) \ + autoinit_funcs.h \ + sys_null_macro.h sys_base_types.h sys_bool_type.h \ + sys_sockets_types.h sys_sockets_headers.h sys_ip_headers.h \ + sys_errno.h sys_file_fd.h sys_malloc.h \ + sys_select.h sys_poll.h \ + sys_sendfile.h \ + compat_calloc.h \ + mhd_assert.h \ + mhd_tristate.h \ + mhd_socket_type.h mhd_sockets_macros.h \ + mhd_sockets_funcs.c mhd_sockets_funcs.h \ + mhd_socket_error.c mhd_socket_error.h \ + mhd_atomic_counter.c mhd_atomic_counter.h \ + mhd_str.c mhd_str.h \ + mhd_str_macros.h mhd_str_types.h \ + mhd_buffer.h \ + mhd_limits.h \ + mhd_iovec.h \ + mhd_panic.c mhd_panic.h \ + mhd_lib_init.c mhd_lib_init_impl.h mhd_lib_init.h \ + mhd_dlinked_list.h \ + mhd_connection.h \ + mhd_locks.h \ + mhd_itc.c mhd_itc.h mhd_itc_types.h \ + mhd_threads.c mhd_threads.h sys_thread_entry_type.h \ + mhd_mono_clock.c mhd_mono_clock.h \ + mhd_mempool.c mhd_mempool.h \ + mhd_recv.c mhd_recv.h \ + mhd_send.c mhd_send.h \ + mhd_daemon.h \ + mhd_public_api.h \ + mhd_request.h mhd_reply.h mhd_response.h \ + http_method.h http_prot_ver.h \ + http_status_str.c http_status_str.h \ + action.c mhd_action.h \ + dcc_action.c mhd_dcc_action.h \ + events_process.c events_process.h \ + daemon_logger.c daemon_logger.h \ + daemon_logger_default.c daemon_logger_default.h \ + daemon_options.h daemon_set_options.c \ + daemon_create.c \ + daemon_start.c \ + daemon_get_info.c \ + daemon_add_conn.c daemon_add_conn.h \ + daemon_funcs.c daemon_funcs.h \ + conn_data_process.c conn_data_process.h \ + conn_data_recv.c conn_data_recv.h \ + conn_data_send.c conn_data_send.h \ + conn_mark_ready.h \ + request_funcs.c request_funcs.h \ + request_get_value.c request_get_value.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 \ + response_funcs.c response_funcs.h \ + response_set_options.c response_set_options.h response_options.h \ + stream_funcs.c stream_funcs.h \ + stream_process_states.c stream_process_states.h \ + stream_process_request.c stream_process_request.h \ + stream_process_reply.c stream_process_reply.h + +if ! HAVE_SYS_CALLOC +libmicrohttpd2_la_SOURCES += \ + compat_calloc.c +endif + + +libmicrohttpd2_la_CPPFLAGS = \ + $(AM_CPPFLAGS) $(MHD_LIB_CPPFLAGS) $(MHD_TLS_LIB_CPPFLAGS) \ + -DBUILDING_MHD_LIB=1 +libmicrohttpd2_la_CFLAGS = \ + $(AM_CFLAGS) $(MHD_LIB_CFLAGS) $(MHD_TLS_LIB_CFLAGS) +libmicrohttpd2_la_LDFLAGS = \ + $(AM_LDFLAGS) $(MHD_LIB_LDFLAGS) $(MHD_TLS_LIB_LDFLAGS) \ + $(W32_MHD_LIB_LDFLAGS) \ + -export-dynamic -no-undefined \ + -version-info @LIB_VER_CURRENT@:@LIB_VER_REVISION@:@LIB_VER_AGE@ +libmicrohttpd2_la_LIBADD = \ + $(MHD_LIBDEPS) $(MHD_TLS_LIBDEPS) + + +AM_V_RC = $(am__v_RC_@AM_V@) +am__v_RC_ = $(am__v_RC_@AM_DEFAULT_V@) +am__v_RC_0 = @echo " RC " $@; +am__v_RC_1 = + +# General rule is not required, but keep it just in case +# Note: windres does not understand '-isystem' flag, so all +# possible '-isystem' flags are replaced by simple '-I' flags. +.rc.lo: + $(AM_V_RC) RC_ALL_CPPFLAGS=`echo ' $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) ' | $(SED) -e 's/ -isystem / -I/g'`; \ + $(LIBTOOL) $(AM_V_lt) --tag=RC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(RC) $(RCFLAGS) $(DEFS) $${RC_ALL_CPPFLAGS} $< -o $@ + +# Note: windres does not understand '-isystem' flag, so all +# possible '-isystem' flags are replaced by simple '-I' flags. +libmicrohttpd2_la-w32_lib_res.lo: $(builddir)/w32_lib_res.rc + $(AM_V_RC) RC_ALL_CPPFLAGS=`echo ' $(DEFAULT_INCLUDES) $(INCLUDES) $(libmicrohttpd_la_CPPFLAGS) $(CPPFLAGS) ' | $(SED) -e 's/ -isystem / -I/g'`; \ + $(LIBTOOL) $(AM_V_lt) --tag=RC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(RC) $(RCFLAGS) $(DEFS) $${RC_ALL_CPPFLAGS} $(builddir)/w32_lib_res.rc -o $@ + +if HAVE_W32 +MHD_DLL_RES_LO = libmicrohttpd2_la-w32_lib_res.lo +else +MHD_DLL_RES_LO = +endif + +EXTRA_libmicrohttpd2_la_DEPENDENCIES = $(MHD_DLL_RES_LO) +libmicrohttpd2_la_LIBADD += $(MHD_DLL_RES_LO) + +#TESTS = $(check_PROGRAMS) + +update-po-POTFILES.in: $(top_srcdir)/po/POTFILES.in + +$(top_srcdir)/po/POTFILES.in: $(srcdir)/Makefile.am + @echo "Creating $@" + @echo src/include/microhttpd2.h > "$@" && \ + for src in $(am__libmicrohttpd2_la_SOURCES_DIST) ; do \ + echo "$(subdir)/$$src" >> "$@" ; \ + done + +.PHONY: update-po-POTFILES.in + +EXTRA_DIST = \ + libmicrohttpd2.pc.in + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libmicrohttpd2.pc + +$(CONFIG_HEADER): $(builddir)/../incl_priv/mhd_config.h.in $(top_srcdir)/configure $(top_builddir)/config.status + @echo "cd $(srcdir)/../incl_priv && $(MAKE) $(AM_MAKEFLAGS) mhd_config.h" && \ + $(am__cd) $(srcdir)/../incl_priv && $(MAKE) $(AM_MAKEFLAGS) mhd_config.h + +$(builddir)/../incl_priv/mhd_config.h.in: $(top_srcdir)/configure.ac + @echo "cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh" && \ + $(am__cd) $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh diff --git a/src/mhd2/action.c b/src/mhd2/action.c @@ -0,0 +1,184 @@ +/* + 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/action.c + * @brief The definition of the MHD_action_*() and MHD_upload_action_*() + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "mhd_action.h" +#include "mhd_request.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 * +MHD_action_suspend (struct MHD_Request *request) +{ + struct MHD_Action *const restrict head_act = &(request->app_act.head_act); + if (mhd_ACTION_NO_ACTION != head_act->act) + return (const struct MHD_Action *) NULL; + + head_act->act = mhd_ACTION_SUSPEND; + return head_act; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) const struct MHD_Action * +MHD_action_from_response (struct MHD_Request *request, + struct MHD_Response *response) +{ + struct MHD_Action *const restrict head_act = &(request->app_act.head_act); + if (mhd_ACTION_NO_ACTION != head_act->act) + return (const struct MHD_Action *) NULL; + if (NULL == response) + return (const struct MHD_Action *) NULL; + + mhd_response_check_frozen_freeze (response); + + head_act->act = mhd_ACTION_RESPONSE; + head_act->data.response = response; + + return head_act; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) const struct MHD_Action * +MHD_action_process_upload (struct MHD_Request *request, + size_t large_buffer_size, + MHD_UploadCallback uc_full, + void *uc_full_cls, + MHD_UploadCallback uc_inc, + void *uc_inc_cls) +{ + struct MHD_Action *const restrict head_act = &(request->app_act.head_act); + if (mhd_ACTION_NO_ACTION != head_act->act) + return (const struct MHD_Action *) NULL; + if (0 == large_buffer_size) + { + if (NULL != uc_full) + return (const struct MHD_Action *) NULL; + if (NULL == uc_inc) + return (const struct MHD_Action *) NULL; + } + else + { + if (NULL == uc_full) + return (const struct MHD_Action *) NULL; + } + + head_act->act = mhd_ACTION_UPLOAD; + head_act->data.upload.large_buffer_size = large_buffer_size; + head_act->data.upload.full.cb = uc_full; + head_act->data.upload.full.cls = uc_full_cls; + head_act->data.upload.inc.cb = uc_inc; + head_act->data.upload.inc.cls = uc_inc_cls; + + return head_act; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) const struct MHD_Action * +MHD_action_post_processor (struct MHD_Request *request, + size_t pp_buffer_size, + size_t pp_stream_limit, + enum MHD_HTTP_PostEncoding enc, + MHD_PostDataReader reader, + void *reader_cls, + MHD_PostDataFinished done_cb, + void *done_cb_cls) +{ + struct MHD_Action *const restrict head_act = + &(request->app_act.head_act); + if (mhd_ACTION_NO_ACTION != head_act->act) + return (const struct MHD_Action *) NULL; + if (NULL == done_cb) + return (const struct MHD_Action *) NULL; + + head_act->act = mhd_ACTION_POST_PROCESS; + head_act->data.post_process.pp_buffer_size = pp_buffer_size; + head_act->data.post_process.pp_stream_limit = pp_stream_limit; + head_act->data.post_process.enc = enc; + head_act->data.post_process.reader = reader; + head_act->data.post_process.reader_cls = reader_cls; + head_act->data.post_process.done_cb = done_cb; + head_act->data.post_process.done_cb_cls = done_cb_cls; + + return head_act; +} + + +MHD_EXTERN_ MHD_FN_RETURNS_NONNULL_ MHD_FN_PAR_NONNULL_ALL_ +const struct MHD_UploadAction * +MHD_upload_action_suspend (struct MHD_Request *request) +{ + struct MHD_UploadAction *const restrict upl_act = + &(request->app_act.upl_act); + if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) + return (const struct MHD_UploadAction *) NULL; + + upl_act->act = mhd_UPLOAD_ACTION_SUSPEND; + + return upl_act; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) const struct MHD_UploadAction * +MHD_upload_action_from_response (struct MHD_Request *request, + struct MHD_Response *response) +{ + struct MHD_UploadAction *const restrict upl_act = + &(request->app_act.upl_act); + if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) + return (const struct MHD_UploadAction *) NULL; + + mhd_response_check_frozen_freeze (response); + + upl_act->act = mhd_UPLOAD_ACTION_RESPONSE; + upl_act->data.response = response; + + return upl_act; +} + + +MHD_EXTERN_ MHD_FN_RETURNS_NONNULL_ const struct MHD_UploadAction * +MHD_upload_action_continue (struct MHD_Request *request) +{ + struct MHD_UploadAction *const restrict upl_act = + &(request->app_act.upl_act); + if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) + return (const struct MHD_UploadAction *) NULL; + + upl_act->act = mhd_UPLOAD_ACTION_CONTINUE; + + return upl_act; +} diff --git a/src/mhd2/autoinit_funcs.h b/src/mhd2/autoinit_funcs.h @@ -0,0 +1,303 @@ +/* + * AutoinitFuncs: Automatic Initialization and Deinitialization Functions + * Copyright(C) 2014-2023 Karlson2k (Evgeny Grin) + * + * This header 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. + * + * This header 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 header; if not, see + * <http://www.gnu.org/licenses/>. + */ + +/* + General usage is simple: include this header, declare or define two + functions with zero parameters (void) and any return type: one for + initialisation and one for deinitialisation, add + _SET_INIT_AND_DEINIT_FUNCS(FuncInitName, FuncDeInitName) to the code + and functions will be automatically called during application startup + and shutdown. + This is useful for libraries as libraries don't have direct access + to main() functions. + Example: + ------------------------------------------------- + #include <stdlib.h> + #include "autoinit_funcs.h" + + int someVar; + void* somePtr; + + void libInit(void) + { + someVar = 3; + somePtr = malloc(100); + } + + void libDeinit(void) + { + free(somePtr); + } + + _SET_INIT_AND_DEINIT_FUNCS(libInit,libDeinit); + ------------------------------------------------- + + If initialiser or deinitialiser function is not needed, just define + it as empty function. + + This header should work with GCC, clang, MSVC (2010 or later) and + SunPro / Sun Studio / Oracle Solaris Studio / Oracle Developer Studio + compiler. + Supported C and C++ languages; application, static and dynamic (DLL) + libraries; non-optimized (Debug) and optimised (Release) compilation + and linking. + + For more information see header code and comments in code. + */ +#ifndef AUTOINIT_FUNCS_INCLUDED +#define AUTOINIT_FUNCS_INCLUDED 1 + +/** +* Current version of the header in packed BCD form. +* 0x01093001 = 1.9.30-1. +*/ +#define AUTOINIT_FUNCS_VERSION 0x01001000 + +#if defined(__GNUC__) || defined(__clang__) +/* if possible - check for supported attribute */ +#ifdef __has_attribute +#if ! __has_attribute (constructor) || ! __has_attribute (destructor) +#define _GNUC_ATTR_CONSTR_NOT_SUPPORTED 1 +#endif /* !__has_attribute(constructor) || !__has_attribute(destructor) */ +#endif /* __has_attribute */ +#endif /* __GNUC__ */ + +/* "__has_attribute__ ((constructor))" is supported by GCC, clang and + Sun/Oracle compiler starting from version 12.1. */ +#if ((defined(__GNUC__) || defined(__clang__)) && \ + ! defined(_GNUC_ATTR_CONSTR_NOT_SUPPORTED)) || \ + (defined(__SUNPRO_C) && __SUNPRO_C + 0 >= 0x5100) + +#define GNUC_SET_INIT_AND_DEINIT(FI,FD) \ + void __attribute__ ((constructor)) _GNUC_init_helper_ ## FI (void); \ + void __attribute__ ((destructor)) _GNUC_deinit_helper_ ## FD (void); \ + void __attribute__ ((constructor)) _GNUC_init_helper_ ## FI (void) \ + { (void) (FI) (); } \ + void __attribute__ ((destructor)) _GNUC_deinit_helper_ ## FD (void) \ + { (void) (FD) (); } \ + struct _GNUC_dummy_str_ ## FI {int i;} + +#define _SET_INIT_AND_DEINIT_FUNCS(FI,FD) GNUC_SET_INIT_AND_DEINIT (FI,FD) +#define _AUTOINIT_FUNCS_ARE_SUPPORTED 1 + +#elif defined(_MSC_FULL_VER) && _MSC_VER + 0 >= 1600 + +/* Make sure that your project/sources define: + _LIB if building a static library (_LIB is ignored if _CONSOLE is defined); + _USRDLL if building DLL-library; + not defined both _LIB and _USRDLL if building an application */ + +/* Define AUTOINIT_FUNCS_DECLARE_STATIC_REG if you need macro declaration + for registering static initialisation functions even if you building DLL */ +/* Define AUTOINIT_FUNCS_FORCE_STATIC_REG if you want to set main macro + _SET_INIT_AND_DEINIT_FUNCS to static version even if building a DLL */ + +/* Stringify macros */ +#define _INSTRMACRO(a) #a +#define _STRMACRO(a) _INSTRMACRO (a) + +#if ! defined(_USRDLL) || defined(AUTOINIT_FUNCS_DECLARE_STATIC_REG) \ + || defined(AUTOINIT_FUNCS_FORCE_STATIC_REG) + +/* Use "C" linkage for variable to simplify variable decoration */ +#ifdef __cplusplus +#define W32_INITVARDECL extern "C" +#else +#define W32_INITVARDECL extern +#endif + +/* How variable is decorated by compiler */ +#if (defined(_WIN32) || defined(_WIN64)) \ + && ! defined(_M_IX86) && ! defined(_X86_) +#if ! defined(_M_X64) && ! defined(_M_AMD64) && ! defined(_x86_64_) \ + && ! defined(_M_ARM) && ! defined(_M_ARM64) +#pragma message(__FILE__ "(" _STRMACRO(__LINE__) ") : warning AIFW001 : " \ + "Untested architecture, linker may fail with unresolved symbol") +#endif /* ! _M_X64 && ! _M_AMD64 && ! _x86_64_ && ! _M_ARM && ! _M_ARM64 */ +#define W32_VARDECORPREFIX +#define W32_DECORVARNAME(v) v +#define W32_VARDECORPREFIXSTR "" +#elif defined(_WIN32) && (defined(_M_IX86) || defined(_X86_)) +#define W32_VARDECORPREFIX _ +#define W32_DECORVARNAME(v) _ ## v +#define W32_VARDECORPREFIXSTR "_" +#else +#error Do not know how to decorate symbols for this architecture +#endif + +/* Internal variable prefix (can be any) */ +#define W32_INITHELPERVARNAME(f) _initHelperDummy_ ## f +#define W32_INITHELPERVARNAMEDECORSTR(f) \ + W32_VARDECORPREFIXSTR _STRMACRO (W32_INITHELPERVARNAME (f)) + +/* Declare section (segment), put variable pointing to init function to chosen segment, + force linker to always include variable to avoid omitting by optimiser */ +/* Initialisation function must be declared as + void __cdecl FuncName(void) */ +/* "extern" with initialisation value means that variable is declared AND defined. */ +#define W32_VFPTR_IN_SEG(S,F) \ + __pragma(section (S,long,read)) \ + __pragma (comment (linker, "/INCLUDE:" W32_INITHELPERVARNAMEDECORSTR (F))) \ + W32_INITVARDECL __declspec(allocate (S))void \ + (__cdecl * W32_INITHELPERVARNAME (F))(void) = &F + +/* Sections (segments) for pointers to initialisers/deinitialisers */ + +/* Semi-officially suggested section for early initialisers (called before + C++ objects initialisers), "void" return type */ +#define W32_SEG_INIT_EARLY ".CRT$XCT" +/* Semi-officially suggested section for late initialisers (called after + C++ objects initialisers), "void" return type */ +#define W32_SEG_INIT_LATE ".CRT$XCV" + +/* Unsafe sections (segments) for pointers to initialisers/deinitialisers */ + +/* C++ lib initialisers, "void" return type (reserved by the system!) */ +#define W32_SEG_INIT_CXX_LIB ".CRT$XCL" +/* C++ user initialisers, "void" return type (reserved by the system!) */ +#define W32_SEG_INIT_CXX_USER ".CRT$XCU" + + +/* Declare section (segment), put variable pointing to init function to chosen segment, + force linker to always include variable to avoid omitting by optimiser */ +/* Initialisation function must be declared as + int __cdecl FuncName(void) */ +/* Startup process is aborted if initialiser returns non-zero */ +/* "extern" with initialisation value means that variable is declared AND defined. */ +#define W32_IFPTR_IN_SEG(S,F) \ + __pragma(section (S,long,read)) \ + __pragma (comment (linker, "/INCLUDE:" W32_INITHELPERVARNAMEDECORSTR (F))) \ + W32_INITVARDECL __declspec(allocate (S))int \ + (__cdecl * W32_INITHELPERVARNAME (F))(void) = &F + +/* Unsafe sections (segments) for pointers to initialisers with + "int" return type */ + +/* C lib initialisers, "int" return type (reserved by the system!). + These initialisers are called before others. */ +#define W32_SEG_INIT_C_LIB ".CRT$XIL" +/* C user initialisers, "int" return type (reserved by the system!). + These initialisers are called before others. */ +#define W32_SEG_INIT_C_USER ".CRT$XIU" + + +/* Declare macro for different initialisers sections */ +/* Macro can be used several times to register several initialisers */ +/* Once function is registered as initialiser, it will be called automatically + during application startup */ +#define W32_REG_INIT_EARLY(F) W32_VFPTR_IN_SEG (W32_SEG_INIT_EARLY,F) +#define W32_REG_INIT_LATE(F) W32_VFPTR_IN_SEG (W32_SEG_INIT_LATE,F) + + +/* Not recommended / unsafe */ +/* "lib" initialisers are called before "user" initialisers */ +/* "C" initialisers are called before "C++" initialisers */ +#define W32_REG_INIT_C_USER(F) W32_FPTR_IN_SEG (W32_SEG_INIT_C_USER,F) +#define W32_REG_INIT_C_LIB(F) W32_FPTR_IN_SEG (W32_SEG_INIT_C_LIB,F) +#define W32_REG_INIT_CXX_USER(F) W32_FPTR_IN_SEG (W32_SEG_INIT_CXX_USER,F) +#define W32_REG_INIT_CXX_LIB(F) W32_FPTR_IN_SEG (W32_SEG_INIT_CXX_LIB,F) + +/* Choose main register macro based on language and program type */ +/* Assuming that _LIB or _USRDLL is defined for static or DLL-library */ +/* Macro can be used several times to register several initialisers */ +/* Once function is registered as initialiser, it will be called automatically + during application startup */ +/* Define AUTOINIT_FUNCS_FORCE_EARLY_INIT to force register as early + initialiser */ +/* Define AUTOINIT_FUNCS_FORCE_LATE_INIT to force register as late + initialiser */ +/* By default C++ static or DLL-library code and any C code and will be + registered as early initialiser, while C++ non-library code will be + registered as late initialiser */ +#if (! defined(__cplusplus) || \ + ((defined(_LIB) && ! defined(_CONSOLE)) || defined(_USRDLL)) || \ + defined(AUTOINIT_FUNCS_FORCE_EARLY_INIT)) && \ + ! defined(AUTOINIT_FUNCS_FORCE_LATE_INIT) +#define W32_REGISTER_INIT(F) W32_REG_INIT_EARLY (F) +#else +#define W32_REGISTER_INIT(F) W32_REG_INIT_LATE (F) +#endif + +#endif /* ! _USRDLL || ! AUTOINIT_FUNCS_DECLARE_STATIC_REG + || AUTOINIT_FUNCS_FORCE_STATIC_REG */ + + +#if ! defined(_USRDLL) || defined(AUTOINIT_FUNCS_FORCE_STATIC_REG) + +#include <stdlib.h> /* required for atexit() */ + +#define W32_SET_INIT_AND_DEINIT(FI,FD) \ + void __cdecl _W32_init_helper_ ## FI (void); \ + void __cdecl _W32_deinit_helper_ ## FD (void); \ + void __cdecl _W32_init_helper_ ## FI (void) \ + { (void) (FI) (); atexit (_W32_deinit_helper_ ## FD); } \ + void __cdecl _W32_deinit_helper_ ## FD (void) \ + { (void) (FD) (); } \ + W32_REGISTER_INIT (_W32_init_helper_ ## FI) +#else /* _USRDLL */ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif /* WIN32_LEAN_AND_MEAN */ + +#include <Windows.h> /* Required for DllMain */ + +/* If DllMain is already present in code, define AUTOINIT_FUNCS_CALL_USR_DLLMAIN + and rename DllMain to usr_DllMain */ +#ifndef AUTOINIT_FUNCS_CALL_USR_DLLMAIN +#define W32_SET_INIT_AND_DEINIT(FI,FD) \ + BOOL WINAPI DllMain (HINSTANCE hinst,DWORD reason,LPVOID unused); \ + BOOL WINAPI DllMain (HINSTANCE hinst,DWORD reason,LPVOID unused) \ + { (void) hinst; (void) unused; \ + if (DLL_PROCESS_ATTACH==reason) {(void) (FI) ();} \ + else if (DLL_PROCESS_DETACH==reason) {(void) (FD) ();} \ + return TRUE; \ + } struct _W32_dummy_strc_ ## FI {int i;} +#else /* AUTOINIT_FUNCS_CALL_USR_DLLMAIN */ +#define W32_SET_INIT_AND_DEINIT(FI,FD) \ + BOOL WINAPI usr_DllMain (HINSTANCE hinst,DWORD reason,LPVOID unused); \ + BOOL WINAPI DllMain (HINSTANCE hinst,DWORD reason,LPVOID unused); \ + BOOL WINAPI DllMain (HINSTANCE hinst,DWORD reason,LPVOID unused) \ + { if (DLL_PROCESS_ATTACH==reason) {(void) (FI) ();} \ + else if (DLL_PROCESS_DETACH==reason) {(void) (FD) ();} \ + return usr_DllMain (hinst,reason,unused); \ + } struct _W32_dummy_strc_ ## FI {int i;} +#endif /* AUTOINIT_FUNCS_CALL_USR_DLLMAIN */ +#endif /* _USRDLL */ + +#define _SET_INIT_AND_DEINIT_FUNCS(FI,FD) W32_SET_INIT_AND_DEINIT (FI,FD) +/* Indicate that automatic initialisers/deinitialisers are supported */ +#define _AUTOINIT_FUNCS_ARE_SUPPORTED 1 + +#else /* !__GNUC__ && !_MSC_FULL_VER */ + +/* Define EMIT_ERROR_IF_AUTOINIT_FUNCS_ARE_NOT_SUPPORTED before inclusion of header to + abort compilation if automatic initialisers/deinitialisers are not supported */ +#ifdef EMIT_ERROR_IF_AUTOINIT_FUNCS_ARE_NOT_SUPPORTED +#error \ + Compiler/platform does not support automatic calls of user-defined initializer and deinitializer +#endif /* EMIT_ERROR_IF_AUTOINIT_FUNCS_ARE_NOT_SUPPORTED */ + +/* Do nothing */ +#define _SET_INIT_AND_DEINIT_FUNCS(FI,FD) +/* Indicate that automatic initialisers/deinitialisers are not supported */ +#define _AUTOINIT_FUNCS_ARE_NOT_SUPPORTED 1 + +#endif /* !__GNUC__ && !_MSC_FULL_VER */ +#endif /* !AUTOINIT_FUNCS_INCLUDED */ diff --git a/src/mhd2/compat_calloc.c b/src/mhd2/compat_calloc.c @@ -0,0 +1,63 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Karlson2k (Evgeny Grin) + + 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/compat_calloc.c + * @brief The implementation of the calloc() replacement + * @author Karlson2k (Evgeny Grin) + */ + +#include "compat_calloc.h" +#ifndef HAVE_CALLOC + +#include <string.h> /* for memset() */ +#include "sys_malloc.h" + + +#ifdef __has_builtin +# if __has_builtin (__builtin_mul_overflow) +# define MHD_HAVE_MUL_OVERFLOW 1 +# endif +#elif defined(__GNUC__) && __GNUC__ + 0 >= 5 +# define MHD_HAVE_MUL_OVERFLOW 1 +#endif /* __GNUC__ >= 5 */ + +MHD_INTERNAL void * +mhd_calloc (size_t nelem, size_t elsize) +{ + size_t alloc_size; + void *ptr; +#ifdef MHD_HAVE_MUL_OVERFLOW + if (__builtin_mul_overflow (nelem, elsize, &alloc_size) || (0 == alloc_size)) + return NULL; +#else /* ! MHD_HAVE_MUL_OVERFLOW */ + alloc_size = nelem * elsize; + if ((0 == alloc_size) || (elsize != alloc_size / nelem)) + return NULL; +#endif /* ! MHD_HAVE_MUL_OVERFLOW */ + ptr = malloc (alloc_size); + if (NULL == ptr) + return NULL; + memset (ptr, 0, alloc_size); + return ptr; +} + + +#endif /* ! HAVE_CALLOC */ diff --git a/src/mhd2/compat_calloc.h b/src/mhd2/compat_calloc.h @@ -0,0 +1,65 @@ +/* + 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/compat_calloc.h + * @brief The header for the calloc() or the calloc() replacement declarations + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_COMPAT_CALLOC_H +#define MHD_COMPAT_CALLOC_H 1 + +#include "mhd_sys_options.h" + +#ifdef HAVE_CALLOC +# if defined(HAVE_STDLIB_H) +# include <stdlib.h> +# elif defined(HAVE_MALLOC_H) +# include <malloc.h> +# else +/* Try some set of headers, hoping the right header is included */ +# if defined(HAVE_UNISTD_H) +# include <unistd.h> +# endif +# include <stdio.h> +# include <string.h> +# endif + +# define mhd_calloc calloc +#else + +# include "sys_base_types.h" /* for size_t, NULL */ + + +/** + * Allocate memory for an array of @a nelem objects of @a elsize size and + * initialise all bytes to zero in the allocated memory area. + * @param nelem the number of elements to allocate + * @param elsize the size of single element + * @return the pointer to allocated memory area on success, + * the NULL pointer on failure. + */ +MHD_INTERNAL void * +mhd_calloc (size_t nelem, size_t elsize); + +#endif /* ! HAVE_CALLOC */ + +#endif /* ! MHD_COMPAT_CALLOC_H */ diff --git a/src/mhd2/conn_data_process.c b/src/mhd2/conn_data_process.c @@ -0,0 +1,181 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + 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/data_process.c + * @brief The implementation of data receiving, sending and processing + * functions for connection + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other + * contributors. + */ + +#include "mhd_sys_options.h" + +#include "conn_data_process.h" +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" + +#include "daemon_logger.h" + +#include "conn_data_recv.h" +#include "conn_data_send.h" +#include "stream_process_states.h" + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) +{ + const bool send_ready_state_known = + ((mhd_D_IS_USING_EDGE_TRIG (c->daemon)) || + (0 != (MHD_EVENT_LOOP_INFO_WRITE & c->event_loop_info))); + const bool has_sock_err = + (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk_ready)); + bool data_processed; + + data_processed = false; + + if (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info)) + { + bool use_recv; + use_recv = (0 != (mhd_SOCKET_NET_STATE_RECV_READY & c->sk_ready)); + use_recv = use_recv || + (has_sock_err && c->sk_nonblck); + + if (use_recv) + { + mhd_conn_data_recv (c, has_sock_err); + if (! mhd_conn_process_data (c)) + return false; + data_processed = true; + } + } + + if (0 != (MHD_EVENT_LOOP_INFO_WRITE & c->event_loop_info)) + { + bool use_send; + /* Perform sending if: + * + connection is ready for sending or + * + just formed send data, connection send ready status is not known and + * connection socket is non-blocking + * + detected network error on the connection, to check to the error */ + /* Assuming that after finishing receiving phase, connection send system + buffers should have some space as sending was performed before receiving + or has not been performed yet. */ + use_send = (0 != (mhd_SOCKET_NET_STATE_SEND_READY & c->sk_ready)); + use_send = use_send || + (data_processed && (! send_ready_state_known) && c->sk_nonblck); + use_send = use_send || + (has_sock_err && c->sk_nonblck); + + if (use_send) + { + mhd_conn_data_send (c); + if (! mhd_conn_process_data (c)) + return false; + data_processed = true; + } + } + if (! data_processed) + return mhd_conn_process_data (c); + return true; + +#if 0 // TODO: re-implement fasttrack as a single unified buffer sending + if (! force_close) + { + /* No need to check value of 'ret' here as closed connection + * cannot be in MHD_EVENT_LOOP_INFO_WRITE state. */ + if ( (MHD_EVENT_LOOP_INFO_WRITE == c->event_loop_info) && + write_ready) + { + MHD_connection_handle_write (c); + ret = MHD_connection_handle_idle (c); + states_info_processed = true; + } + } + else + { + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_WITH_ERROR); + return MHD_connection_handle_idle (c); + } + + if (! states_info_processed) + { /* Connection is not read or write ready, but external conditions + * may be changed and need to be processed. */ + ret = MHD_connection_handle_idle (c); + } + /* Fast track for fast connections. */ + /* If full request was read by single read_handler() invocation + and headers were completely prepared by single MHD_connection_handle_idle() + then try not to wait for next sockets polling and send response + immediately. + As writeability of socket was not checked and it may have + some data pending in system buffers, use this optimization + only for non-blocking sockets. */ + /* No need to check 'ret' as connection is always in + * MHD_CONNECTION_CLOSED state if 'ret' is equal 'MHD_NO'. */ + else if (on_fasttrack && c->sk_nonblck) + { + if (MHD_CONNECTION_HEADERS_SENDING == c->state) + { + MHD_connection_handle_write (c); + /* Always call 'MHD_connection_handle_idle()' after each read/write. */ + ret = MHD_connection_handle_idle (c); + } + /* If all headers were sent by single write_handler() and + * response body is prepared by single MHD_connection_handle_idle() + * call - continue. */ + if ((MHD_CONNECTION_UNCHUNKED_BODY_READY == c->state) || + (MHD_CONNECTION_CHUNKED_BODY_READY == c->state)) + { + MHD_connection_handle_write (c); + ret = MHD_connection_handle_idle (c); + } + } + + /* All connection's data and states are processed for this turn. + * If connection already has more data to be processed - use + * zero timeout for next select()/poll(). */ + /* Thread-per-connection do not need global zero timeout as + * connections are processed individually. */ + /* Note: no need to check for read buffer availability for + * TLS read-ready connection in 'read info' state as connection + * without space in read buffer will be marked as 'info block'. */ + if ( (! c->daemon->data_already_pending) && + (! mhd_D_HAS_THR_PER_CONN (c->daemon)) ) + { + if (0 != (MHD_EVENT_LOOP_INFO_PROCESS & c->event_loop_info)) + c->daemon->data_already_pending = true; +#ifdef HTTPS_SUPPORT + else if ( (c->tls_read_ready) && + (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info)) ) + c->daemon->data_already_pending = true; +#endif /* HTTPS_SUPPORT */ + } + return ret; +#endif +} diff --git a/src/mhd2/conn_data_process.h b/src/mhd2/conn_data_process.h @@ -0,0 +1,56 @@ +/* + 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/data_process.h + * @brief The declarations of data processing functions for connection + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DATA_PROCESS_H +#define MHD_DATA_PROCESS_H 1 + +#include "mhd_sys_options.h" +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Perform connection receiving, sending and processing data. + * @param c the connection to use + * @return true if data processed successfully, + * false if connection needs to be closed + */ +MHD_INTERNAL bool +mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Update "active" state, move to the activity lists if necessary. + * Update "event loop info" + * @param c the connection to use + * @return true if data processed successfully, + * false if connection needs to be closed + */ +MHD_INTERNAL bool +mhd_conn_update_active_state (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_DATA_PROCESS_H */ diff --git a/src/mhd2/conn_data_recv.c b/src/mhd2/conn_data_recv.c @@ -0,0 +1,157 @@ +/* + 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/conn_data_recv.c + * @brief The implementation of data receiving functions for connection + * @author Karlson2k (Evgeny Grin) + */ + +#include "conn_data_recv.h" + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_connection.h" + +#include "mhd_recv.h" +#include "stream_funcs.h" + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_data_recv (struct MHD_Connection *restrict c, + bool has_err) +{ + void *buf; + size_t buf_size; + size_t received; + enum mhd_SocketError res; + + mhd_assert (MHD_CONNECTION_CLOSED != c->state); + mhd_assert (NULL != c->read_buffer); + mhd_assert (c->read_buffer_size > c->read_buffer_offset); + mhd_assert (! has_err || \ + (0 != (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY))); + mhd_assert ((0 == (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY)) || \ + has_err); + + // TODO: TLS support: handshake/transport layer + + buf = c->read_buffer + c->read_buffer_offset; + buf_size = c->read_buffer_size - c->read_buffer_offset; + + res = mhd_recv (c,buf_size, buf, &received); + + 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 */ + uint_fast64_t dummy_buf; + res = mhd_recv (c, sizeof(dummy_buf), (char *) &dummy_buf, &received); + } + if (mhd_SOCKET_ERR_IS_HARD (res)) + { + c->sk_discnt_err = res; + c->sk_ready = + (enum mhd_SocketNetState) (((unsigned int) c->sk_ready) + | mhd_SOCKET_NET_STATE_ERROR_READY); + } + return; + } + + if (0 == received) + c->sk_rmt_shut_wr = true; + + c->read_buffer_offset += received; + mhd_stream_update_activity_mark (c); // TODO: centralise activity update + return; +} + + +#if 0 // TODO: report disconnect +if ((bytes_read < 0) || socket_error) +{ + if (MHD_ERR_CONNRESET_ == bytes_read) + { + if ( (MHD_CONNECTION_INIT < c->state) && + (MHD_CONNECTION_FULL_REQ_RECEIVED > c->state) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Socket has been disconnected when reading request.\n")); +#endif + c->discard_request = true; + } + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_READ_ERROR); + return; + } + +#ifdef HAVE_MESSAGES + if (MHD_CONNECTION_INIT != c->state) + MHD_DLOG (c->daemon, + _ ("Connection socket is closed when reading " \ + "request due to the error: %s\n"), + (bytes_read < 0) ? str_conn_error_ (bytes_read) : + "detected c closure"); +#endif + CONNECTION_CLOSE_ERROR (c, + NULL); + return; +} + +#if 0 // TODO: handle remote shut WR +if (0 == bytes_read) +{ /* Remote side closed c. */ // FIXME: Actually NOT! + c->sk_rmt_shut_wr = true; + if ( (MHD_CONNECTION_INIT < c->state) && + (MHD_CONNECTION_FULL_REQ_RECEIVED > c->state) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Connection was closed by remote side with incomplete " + "request.\n")); +#endif + c->discard_request = true; + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_CLIENT_ABORT); + } + else if (MHD_CONNECTION_INIT == c->state) + /* This termination code cannot be reported to the application + * because application has not been informed yet about this request */ + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_COMPLETED_OK); + else + MHD_connection_close_ (c, + MHD_REQUEST_TERMINATED_WITH_ERROR); + return; +} +#endif +#endif diff --git a/src/mhd2/conn_data_recv.h b/src/mhd2/conn_data_recv.h @@ -0,0 +1,50 @@ +/* + 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/conn_data_recv.h + * @brief The definition of data receiving functions for connection + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_CONN_DATA_RECV_H +#define MHD_CONN_DATA_RECV_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declarations */ + +/** + * Perform data receiving for the connection and try to detect the socket error + * type. + * + * @param c the connection to use + * @param has_err if 'true' then just check for the network error + * type is performed + */ +MHD_INTERNAL void +mhd_conn_data_recv (struct MHD_Connection *restrict c, + bool has_err) +MHD_FN_PAR_NONNULL_ALL_; + + +#endif /* ! MHD_CONN_DATA_RECV_H */ diff --git a/src/mhd2/conn_data_send.c b/src/mhd2/conn_data_send.c @@ -0,0 +1,308 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + 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/conn_data_send.c + * @brief The implementation of data sending functions for connection + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff, Evgeny Grin + * and other contributors. + */ + +#include "mhd_sys_options.h" + +#include "conn_data_send.h" +#include "sys_bool_type.h" +#include "sys_base_types.h" +#include "mhd_str_macros.h" + +#include "mhd_assert.h" + +#include "mhd_connection.h" +#include "mhd_response.h" + +#include "mhd_socket_error.h" + +#include "mhd_send.h" +#include "stream_funcs.h" + + +/** + * Check if we are done sending the write-buffer. + * If so, transition into "next_state". + * + * @param connection connection to check write status for + * @param next_state the next state to transition to + * @return #MHD_NO if we are not done, #MHD_YES if we are + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +check_write_done (struct MHD_Connection *restrict connection, + enum MHD_CONNECTION_STATE next_state) +{ + // TODO: integrate into states processing + if ( (connection->write_buffer_append_offset != + connection->write_buffer_send_offset) + /* || data_in_tls_buffers == true */ + ) + return false; + connection->write_buffer_append_offset = 0; + connection->write_buffer_send_offset = 0; + connection->state = next_state; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_data_send (struct MHD_Connection *restrict c) +{ + static const char http_100_continue_msg[] = + mdh_HTTP_1_1_100_CONTINUE_REPLY; + static const size_t http_100_continue_msg_len = + mhd_SSTR_LEN (mdh_HTTP_1_1_100_CONTINUE_REPLY); + enum mhd_SocketError res; + size_t sent; + + // TODO: assert check suspended + +#ifdef HTTPS_SUPPORT + // TODO: TLS support, handshake +#endif /* HTTPS_SUPPORT */ + + // TODO: MOVE out STATES PROCESSING + + res = mhd_SOCKET_ERR_INTERNAL; + + if (MHD_CONNECTION_CONTINUE_SENDING == c->state) + { + res = mhd_send_data (c, + http_100_continue_msg_len + - c->continue_message_write_offset, + http_100_continue_msg + + c->continue_message_write_offset, + true, + &sent); + if (mhd_SOCKET_ERR_NO_ERROR == res) + c->continue_message_write_offset += sent; + } + else if (MHD_CONNECTION_HEADERS_SENDING == c->state) + { + struct MHD_Response *const restrict resp = c->rp.response; + const size_t wb_ready = c->write_buffer_append_offset + - c->write_buffer_send_offset; + mhd_assert (c->write_buffer_append_offset >= \ + c->write_buffer_send_offset); + mhd_assert (NULL != resp); + mhd_assert ((mhd_CONN_MUST_UPGRADE != c->conn_reuse) || \ + (! c->rp.props.send_reply_body)); + + // TODO: support body generating alongside with header sending + + if ((c->rp.props.send_reply_body) && + (mhd_REPLY_CNTN_LOC_RESP_BUF == c->rp.cntn_loc)) + { + /* Send response headers alongside the response body, if the body + * data is available. */ + mhd_assert (mhd_RESPONSE_CONTENT_DATA_BUFFER == resp->cntn_dtype); + mhd_assert (! c->rp.props.chunked); + + res = mhd_send_hdr_and_body (c, + wb_ready, + c->write_buffer + + c->write_buffer_send_offset, + false, + resp->cntn_size, + (const char *) resp->cntn.buf, + true, + &sent); + } + else + { + /* This is response for HEAD request or reply body is not allowed + * for any other reason or reply body is dynamically generated. */ + /* Do not send the body data even if it's available. */ + res = mhd_send_hdr_and_body (c, + wb_ready, + c->write_buffer + + c->write_buffer_send_offset, + false, + 0, + NULL, + ((0 == resp->cntn_size) || + (! c->rp.props.send_reply_body)), + &sent); + } + 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, + * update both offsets. */ + 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); + } + + + } + + } + else if ((MHD_CONNECTION_UNCHUNKED_BODY_READY == c->state) || + (MHD_CONNECTION_CHUNKED_BODY_READY == c->state)) + { + struct MHD_Response *const restrict resp = c->rp.response; + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.rsp_cntn_read_pos < resp->cntn_size); + mhd_assert ((MHD_CONNECTION_CHUNKED_BODY_READY != c->state) || \ + (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc)); + if (mhd_REPLY_CNTN_LOC_RESP_BUF == c->rp.cntn_loc) + { + mhd_assert (mhd_RESPONSE_CONTENT_DATA_BUFFER == resp->cntn_dtype); + + res = mhd_send_data (c, + c->rp.rsp_cntn_read_pos - resp->cntn_size, + (const char *) resp->cntn.buf + + c->rp.rsp_cntn_read_pos, + true, + &sent); + } + else if (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc) + { + mhd_assert (c->write_buffer_append_offset > \ + c->write_buffer_send_offset); + + res = mhd_send_data (c, + c->write_buffer_append_offset + - c->write_buffer_send_offset, + c->write_buffer + c->write_buffer_send_offset, + true, + &sent); + } + else if (mhd_REPLY_CNTN_LOC_IOV == c->rp.cntn_loc) + { + mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == resp->cntn_dtype); + + res = mhd_send_iovec (c, + &c->rp.resp_iov, + true, + &sent); + } +#if defined(MHD_USE_SENDFILE) + else if (mhd_REPLY_CNTN_LOC_FILE == c->rp.cntn_loc) + { + mhd_assert (mhd_RESPONSE_CONTENT_DATA_FILE == resp->cntn_dtype); + + res = mhd_send_sendfile (c, &sent); + if (mhd_SOCKET_ERR_INTR == res) + { + if (! c->rp.response->cntn.file.use_sf) + { /* Switch to filereader */ + mhd_assert (! c->rp.props.chunked); + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF; + c->state = MHD_CONNECTION_UNCHUNKED_BODY_UNREADY; + } + } + } +#endif /* MHD_USE_SENDFILE */ + else + { + mhd_assert (0 && "Should be unreachable"); + res = mhd_SOCKET_ERR_INTERNAL; + } + + if (mhd_SOCKET_ERR_NO_ERROR == res) + { + if (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc) + { + enum MHD_CONNECTION_STATE next_state; + c->write_buffer_send_offset += sent; + // TODO: move it to data processing + if (MHD_CONNECTION_CHUNKED_BODY_READY == c->state) + next_state = + (c->rp.response->cntn_size == c->rp.rsp_cntn_read_pos) ? + MHD_CONNECTION_CHUNKED_BODY_SENT : + MHD_CONNECTION_CHUNKED_BODY_UNREADY; + else + next_state = + (c->rp.rsp_cntn_read_pos == resp->cntn_size) ? + MHD_CONNECTION_FULL_REPLY_SENT : + MHD_CONNECTION_UNCHUNKED_BODY_UNREADY; + check_write_done (c, + next_state); + } + else + { + c->rp.rsp_cntn_read_pos += sent; + if (c->rp.rsp_cntn_read_pos == resp->cntn_size) + c->state = MHD_CONNECTION_FULL_REPLY_SENT; + } + } + + } + else if (MHD_CONNECTION_FOOTERS_SENDING == c->state) + { + res = mhd_send_data (c, + c->write_buffer_append_offset + - c->write_buffer_send_offset, + c->write_buffer + + c->write_buffer_send_offset, + true, + &sent); + if (mhd_SOCKET_ERR_NO_ERROR == res) + { + c->write_buffer_send_offset += sent; + // TODO: move it to data processing + check_write_done (c, + MHD_CONNECTION_FULL_REPLY_SENT); + } + } + else + { + mhd_assert (0 && "Should be unreachable"); + res = mhd_SOCKET_ERR_INTERNAL; + } + + if (mhd_SOCKET_ERR_NO_ERROR == res) + { + mhd_stream_update_activity_mark (c); // TODO: centralise activity mark updates + } + else if (mhd_SOCKET_ERR_IS_HARD (res)) + { + c->sk_discnt_err = res; + c->sk_ready = + (enum mhd_SocketNetState) (((unsigned int) c->sk_ready) + | mhd_SOCKET_NET_STATE_ERROR_READY); + } +} diff --git a/src/mhd2/conn_data_send.h b/src/mhd2/conn_data_send.h @@ -0,0 +1,47 @@ +/* + 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/conn_data_send.h + * @brief The definition of data sending functions for connection + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_CONN_DATA_SEND_H +#define MHD_CONN_DATA_SEND_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declarations */ + +/** + * Perform data sending for the connection and try to detect the socket error + * type. + * + * @param c the connection to use + */ +MHD_INTERNAL void +mhd_conn_data_send (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +#endif /* ! MHD_CONN_DATA_SEND_H */ diff --git a/src/mhd2/conn_mark_ready.h b/src/mhd2/conn_mark_ready.h @@ -0,0 +1,97 @@ +/* + 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/conn_mark_ready.h + * @brief The definition of static functions to mark/unmark connection as + * "process ready". + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_CONN_MARK_READY_H +#define MHD_CONN_MARK_READY_H 1 + +#include "mhd_sys_options.h" + +#include "sys_null_macro.h" + +#include "mhd_assert.h" + +#include "mhd_connection.h" +#include "mhd_daemon.h" +#include "mhd_dlinked_list.h" + +/** + * Mark connection as "ready to process" and add it to the end of the + * "process ready" list if connection is not in the list. + * @param c the connection to mark + * @param d the daemon for the connection + */ +MHD_static_inline_ MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_mark_ready (struct MHD_Connection *restrict c, + struct MHD_Daemon *restrict d) +{ + mhd_assert (d == c->daemon); + if (c->in_proc_ready) + { + mhd_assert ((NULL != mhd_DLINKEDL_GET_NEXT (c, proc_ready)) || \ + (NULL != mhd_DLINKEDL_GET_PREV (c, proc_ready)) || \ + (c == mhd_DLINKEDL_GET_FIRST (&(d->events), proc_ready))); + return; /* Already marked and in the 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)); + + mhd_DLINKEDL_INS_LAST (&(d->events), c, proc_ready); + c->in_proc_ready = true; +} + + +/** + * Mark connection as "not ready to process" and remove it from the "process + * ready" list if connection is in the list. + * @param c the connection to mark + * @param d the daemon for the connection + */ +MHD_static_inline_ MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_mark_unready (struct MHD_Connection *restrict c, + struct MHD_Daemon *restrict d) +{ + mhd_assert (d == c->daemon); + if (! c->in_proc_ready) + { + mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (c, proc_ready)); + mhd_assert (NULL == mhd_DLINKEDL_GET_PREV (c, proc_ready)); + mhd_assert (c != mhd_DLINKEDL_GET_FIRST (&(d->events), proc_ready)); + mhd_assert (c != mhd_DLINKEDL_GET_LAST (&(d->events), proc_ready)); + return; /* Already unmarked and not in the list */ + } + mhd_assert ((NULL != mhd_DLINKEDL_GET_NEXT (c, proc_ready)) || \ + (NULL != mhd_DLINKEDL_GET_PREV (c, proc_ready)) || \ + (c == mhd_DLINKEDL_GET_FIRST (&(d->events), proc_ready))); + + mhd_DLINKEDL_DEL (&(d->events), c, proc_ready); + c->in_proc_ready = false; +} + + +#endif /* ! MHD_CONN_MARK_READY_H */ diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c @@ -0,0 +1,951 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff + + 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/daemon_add_conn.c + * @brief The implementations of MHD functions for adding new connections + * @author Karlson2k (Evgeny Grin) + * @author Daniel Pittman + * @author Christian Grothoff + * + * @warning Imported from MHD1 with minimal changes + * TODO: + * + Rewrite, + * + add per IP limit, + * + add app policy for new conn, + */ + +#include "mhd_sys_options.h" + +#include "daemon_add_conn.h" + +#include "sys_bool_type.h" +#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 +# include <sys/epoll.h> +#endif + +#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 "daemon_logger.h" +#include "mhd_mono_clock.h" +#include "mhd_mempool.h" +#include "events_process.h" + +#include "response_from.h" +#include "response_destroy.h" +#include "conn_mark_ready.h" + +#include "mhd_public_api.h" + + +/** + * Set initial internal states for the connection to start reading and + * processing incoming data. + * @param c the connection to process + */ +static void +connection_set_initial_state (struct MHD_Connection *restrict c) +{ + size_t read_buf_size; + + mhd_assert (MHD_CONNECTION_INIT == c->state); + + c->conn_reuse = mhd_CONN_KEEPALIVE_POSSIBLE; + c->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + + memset (&c->rq, 0, sizeof(c->rq)); + memset (&c->rp, 0, sizeof(c->rp)); + +#ifndef HAVE_NULL_PTR_ALL_ZEROS + mhd_DLINKEDL_INIT_LINKS (c, all_conn); + mhd_DLINKEDL_INIT_LINKS (c, proc_ready); + mhd_DLINKEDL_INIT_LINKS (c, by_timeout); + // TODO: set all other pointers manually +#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ + + c->write_buffer = NULL; + c->write_buffer_size = 0; + c->write_buffer_send_offset = 0; + c->write_buffer_append_offset = 0; + + c->continue_message_write_offset = 0; + + c->read_buffer_offset = 0; + read_buf_size = c->daemon->conns.cfg.mem_pool_size / 2; + c->read_buffer + = mhd_pool_allocate (c->pool, + read_buf_size, + false); + c->read_buffer_size = read_buf_size; +} + + +static void +notify_app_conn (struct MHD_Daemon *restrict daemon, + struct MHD_Connection *restrict connection, + bool closed) +{ + (void) daemon, (void) connection, (void) closed; + // TODO: implement +} + + +/** + * Do basic preparation work on the new incoming connection. + * + * This function do all preparation that is possible outside main daemon + * thread. + * @remark Could be called from any thread. + * + * @param daemon daemon that manages the connection + * @param client_socket socket to manage (MHD will expect + * to receive an HTTP request from this socket next). + * @param addr IP address of the client + * @param addrlen number of bytes in @a addr + * @param external_add indicate that socket has been added externally + * @param non_blck indicate that socket in non-blocking mode + * @param sk_spipe_supprs indicate that the @a client_socket has + * set SIGPIPE suppression + * @param sk_is_nonip _MHD_YES if this is not a TCP/IP socket + * @return pointer to the connection on success, NULL if this daemon could + * not handle the connection (i.e. malloc failed, etc). + * The socket will be closed in case of error; 'errno' is + * set to indicate further details about the error. + */ +static enum MHD_StatusCode +new_connection_prepare_ (struct MHD_Daemon *restrict daemon, + MHD_Socket client_socket, + const struct sockaddr_storage *restrict addr, + size_t addrlen, + bool external_add, + bool non_blck, + bool sk_spipe_supprs, + enum mhd_Tristate sk_is_nonip, + struct MHD_Connection **restrict conn_out) +{ + struct MHD_Connection *connection; + *conn_out = NULL; + + if (NULL == (connection = mhd_calloc (1, sizeof (struct MHD_Connection)))) + { + mhd_LOG_MSG (daemon, MHD_SC_CONNECTION_MALLOC_FAILURE, + "Failed to allocate memory for the new connection"); + mhd_socket_close (client_socket); + return MHD_SC_CONNECTION_MALLOC_FAILURE; + } + + if (! external_add) + { + connection->sk_corked = mhd_T_NO; + connection->sk_nodelay = mhd_T_NO; + } + else + { + connection->sk_corked = mhd_T_MAYBE; + connection->sk_nodelay = mhd_T_MAYBE; + } + + if (0 < addrlen) + { + if (NULL == (connection->addr = malloc (addrlen))) + { + mhd_LOG_MSG (daemon, MHD_SC_CONNECTION_MALLOC_FAILURE, + "Failed to allocate memory for the new connection"); + mhd_socket_close (client_socket); + free (connection); + return MHD_SC_CONNECTION_MALLOC_FAILURE; + } + memcpy (connection->addr, + addr, + addrlen); + } + else + connection->addr = NULL; + connection->addr_len = addrlen; + connection->socket_fd = client_socket; + connection->sk_nonblck = non_blck; + connection->is_nonip = sk_is_nonip; + connection->sk_spipe_suppress = sk_spipe_supprs; +#ifdef MHD_USE_THREADS + mhd_thread_handle_ID_set_invalid (&connection->tid); +#endif /* MHD_USE_THREADS */ + connection->daemon = daemon; + connection->connection_timeout_ms = daemon->conns.cfg.timeout; + connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ; + if (0 != connection->connection_timeout_ms) + connection->last_activity = MHD_monotonic_msec_counter (); + + // TODO: init TLS + *conn_out = connection; + + return MHD_SC_OK; +} + + +/** + * Finally insert the new connection to the list of connections + * served by the daemon and start processing. + * @remark To be called only from thread that process + * daemon's select()/poll()/etc. + * + * @param daemon daemon that manages the connection + * @param connection the newly created connection + * @return #MHD_YES on success, #MHD_NO on error + */ +static enum MHD_StatusCode +new_connection_process_ (struct MHD_Daemon *restrict daemon, + struct MHD_Connection *restrict connection) +{ + enum MHD_StatusCode res; + mhd_assert (connection->daemon == daemon); + + res = MHD_SC_OK; + /* Allocate memory pool in the processing thread so + * intensively used memory area is allocated in "good" + * (for the thread) memory region. It is important with + * NUMA and/or complex cache hierarchy. */ + connection->pool = mdh_pool_create (daemon->conns.cfg.mem_pool_size); + if (NULL == connection->pool) + { /* 'pool' creation failed */ + mhd_LOG_MSG (daemon, MHD_SC_POOL_MALLOC_FAILURE, \ + "Failed to allocate memory for the connection memory pool."); + res = MHD_SC_POOL_MALLOC_FAILURE; + } + else + { /* 'pool' creation succeed */ + + if (daemon->conns.block_new) + { /* Connections limit */ + mhd_LOG_MSG (daemon, MHD_SC_LIMIT_CONNECTIONS_REACHED, \ + "Server reached connection limit. " \ + "Closing inbound connection."); + res = MHD_SC_LIMIT_CONNECTIONS_REACHED; + } + else + { /* Have space for new connection */ + mhd_assert (daemon->conns.count < daemon->conns.cfg.count_limit); + daemon->conns.count++; + daemon->conns.block_new = + (daemon->conns.count >= daemon->conns.cfg.count_limit); + mhd_DLINKEDL_INS_LAST (&(daemon->conns), connection, all_conn); + if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != daemon->wmode_int) + mhd_DLINKEDL_INS_FIRST_D (&(daemon->conns.def_timeout), \ + connection, by_timeout); + + connection_set_initial_state (connection); + + notify_app_conn (daemon, connection, false); + +#ifdef MHD_USE_THREADS + if (mhd_DAEMON_TYPE_LISTEN_ONLY == daemon->threading.d_type) + { + mhd_assert ((mhd_POLL_TYPE_SELECT == daemon->events.poll_type) || \ + (mhd_POLL_TYPE_POLL == daemon->events.poll_type)); + if (! mhd_create_named_thread (&connection->tid, + "MHD-connection", + daemon->threading.cfg.stack_size, + &mhd_worker_connection, + connection)) + { +#ifdef EAGAIN + if (EAGAIN == errno) + { + mhd_LOG_MSG (daemon, MHD_SC_CONNECTION_THREAD_SYS_LIMITS_REACHED, + "Failed to create a new thread because it would " + "have exceeded the system limit on the number of " + "threads or no system resources available."); + res = MHD_SC_CONNECTION_THREAD_SYS_LIMITS_REACHED; + } + else +#endif /* EAGAIN */ + if (1) + { + mhd_LOG_MSG (daemon, MHD_SC_CONNECTION_THREAD_LAUNCH_FAILURE, + "Failed to create a thread."); + res = MHD_SC_CONNECTION_THREAD_LAUNCH_FAILURE; + } + } + else /* New thread has been created successfully */ + return MHD_SC_OK; /* *** Function success exit point *** */ + } + else +#else /* ! MHD_USE_THREADS */ + if (1) +#endif /* ! MHD_USE_THREADS */ + { /* No 'thread-per-connection' */ +#ifdef MHD_USE_THREADS + connection->tid = daemon->threading.tid; +#endif /* MHD_USE_THREADS */ +#ifdef MHD_USE_EPOLL + if (mhd_POLL_TYPE_EPOLL == daemon->events.poll_type) + { + struct epoll_event event; + + event.events = EPOLLIN | EPOLLOUT | EPOLLET; + event.data.ptr = connection; + if (0 != epoll_ctl (daemon->events.data.epoll.e_fd, + EPOLL_CTL_ADD, + connection->socket_fd, + &event)) + { + mhd_LOG_MSG (daemon, MHD_SC_EPOLL_CTL_ADD_FAILED, + "Failed to add connection socket to epoll."); + res = MHD_SC_EPOLL_CTL_ADD_FAILED; + } + else + { + if (0) // TODO: implement turbo + { + connection->sk_ready = mhd_SOCKET_NET_STATE_RECV_READY + | mhd_SOCKET_NET_STATE_SEND_READY; + mhd_conn_mark_ready (connection, daemon); + } + return MHD_SC_OK; /* *** Function success exit point *** */ + } + } + else /* No 'epoll' */ +#endif /* MHD_USE_EPOLL */ + return MHD_SC_OK; /* *** Function success exit point *** */ + } + + /* ** Below is a cleanup path ** */ + mhd_assert (MHD_SC_OK != res); + notify_app_conn (daemon, connection, true); + + if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != daemon->wmode_int) + mhd_DLINKEDL_DEL_D (&(daemon->conns.def_timeout), \ + connection, by_timeout); + + mhd_DLINKEDL_DEL (&(daemon->conns), connection, all_conn); + daemon->conns.count--; + daemon->conns.block_new = false; + } + mhd_pool_destroy (connection->pool); + } + /* Free resources allocated before the call of this functions */ + + // TODO: TLS support + + // TODO: per IP limit + + if (NULL != connection->addr) + free (connection->addr); + (void) mhd_socket_close (connection->socket_fd); + free (connection); + mhd_assert (MHD_SC_OK != res); + return res; /* *** Function failure exit point *** */ +} + + +/** + * The given client socket will be managed (and closed!) by MHD after + * this call and must no longer be used directly by the application + * afterwards. + * + * @param daemon daemon that manages the connection + * @param client_socket socket to manage (MHD will expect + * to receive an HTTP request from this socket next). + * @param addr IP address of the client + * @param addrlen number of bytes in @a addr + * @param external_add perform additional operations needed due + * to the application calling us directly + * @param non_blck indicate that socket in non-blocking mode + * @param sk_spipe_supprs indicate that the @a client_socket has + * set SIGPIPE suppression + * @param sk_is_nonip _MHD_YES if this is not a TCP/IP socket + * @return #MHD_YES on success, #MHD_NO if this daemon could + * not handle the connection (i.e. malloc failed, etc). + * The socket will be closed in any case; 'errno' is + * set to indicate further details about the error. + */ +static enum MHD_StatusCode +internal_add_connection (struct MHD_Daemon *daemon, + MHD_Socket client_socket, + const struct sockaddr_storage *addr, + size_t addrlen, + bool external_add, + bool non_blck, + bool sk_spipe_supprs, + enum mhd_Tristate sk_is_nonip) +{ + struct MHD_Connection *connection; + enum MHD_StatusCode res; + + /* Direct add to master daemon could never happen. */ + mhd_assert (! mhd_D_HAS_WORKERS (daemon)); + mhd_assert (mhd_FD_FITS_DAEMON (daemon, client_socket)); + + if ((! non_blck) && + ((mhd_POLL_TYPE_EPOLL == daemon->events.poll_type) || + (mhd_WM_INT_EXTERNAL_EVENTS_EDGE == daemon->wmode_int))) + { + mhd_LOG_MSG (daemon, MHD_SC_NONBLOCKING_REQUIRED, \ + "The daemon configuration requires non-blocking sockets, " + "the new socket has not been added."); + (void) mhd_socket_close (client_socket); + return MHD_SC_NONBLOCKING_REQUIRED; + } + res = new_connection_prepare_ (daemon, + client_socket, + addr, addrlen, + external_add, + non_blck, + sk_spipe_supprs, + sk_is_nonip, + &connection); + if (MHD_SC_OK != res) + return res; + + if (external_add) // TODO: support thread-unsafe + { + mhd_assert (0 && "Not implemented yet"); +#if 0 // TODO: support externally added + /* Connection is added externally and MHD is thread safe mode. */ + MHD_mutex_lock_chk_ (&daemon->new_connections_mutex); + DLL_insert (daemon->new_connections_head, + daemon->new_connections_tail, + connection); + daemon->have_new = true; + MHD_mutex_unlock_chk_ (&daemon->new_connections_mutex); + + /* The rest of connection processing must be handled in + * the daemon thread. */ + if ((mhd_ITC_IS_VALID (daemon->itc)) && + (! mhd_itc_activate (daemon->itc, "n"))) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Failed to signal new connection via inter-thread " \ + "communication channel.\n")); +#endif + } + return MHD_YES; +#endif + } + + return new_connection_process_ (daemon, connection); +} + + +#if 0 // TODO: implement +static void +new_connections_list_process_ (struct MHD_Daemon *daemon) +{ + struct MHD_Connection *local_head; + struct MHD_Connection *local_tail; + mhd_assert (daemon->events.act_req); + // mhd_assert (MHD_D_IS_THREAD_SAFE_ (daemon)); + + /* Detach DL-list of new connections from the daemon for + * following local processing. */ + MHD_mutex_lock_chk_ (&daemon->new_connections_mutex); + mhd_assert (NULL != daemon->new_connections_head); + local_head = daemon->new_connections_head; + local_tail = daemon->new_connections_tail; + daemon->new_connections_head = NULL; + daemon->new_connections_tail = NULL; + daemon->have_new = false; + MHD_mutex_unlock_chk_ (&daemon->new_connections_mutex); + (void) local_head; /* Mute compiler warning */ + + /* Process new connections in FIFO order. */ + do + { + struct MHD_Connection *c; /**< Currently processed connection */ + + c = local_tail; + DLL_remove (local_head, + local_tail, + c); + mhd_assert (daemon == c->daemon); + if (MHD_NO == new_connection_process_ (daemon, c)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Failed to start serving new connection.\n")); +#endif + (void) 0; + } + } while (NULL != local_tail); + +} + + +#endif + + +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_IN_ (4) MHD_EXTERN_ enum MHD_StatusCode +MHD_daemon_add_connection (struct MHD_Daemon *daemon, + MHD_Socket client_socket, + size_t addrlen, + const struct sockaddr *addr, + void *connection_cntx) +{ + bool sk_nonbl; + bool sk_spipe_supprs; + struct sockaddr_storage addrstorage; + // TODO: global daemon lock for external events + (void) connection_cntx; // FIXME: is it really needed? Where it is used? + + if ((! mhd_D_HAS_THREADS (daemon)) && + (daemon->conns.block_new)) + (void) 0; // FIXME: remove already pending connections? + + if (! mhd_D_TYPE_HAS_WORKERS (daemon->threading.d_type) + && daemon->conns.block_new) + { + (void) mhd_socket_close (client_socket); + return MHD_SC_LIMIT_CONNECTIONS_REACHED; + } + + if (0 != addrlen) + { + if (AF_INET == addr->sa_family) + { + if (sizeof(struct sockaddr_in) > addrlen) + { + mhd_LOG_MSG (daemon, MHD_SC_CONFIGURATION_WRONG_SA_SIZE, \ + "MHD_add_connection() has been called with " \ + "incorrect 'addrlen' value."); + (void) mhd_socket_close (client_socket); + return MHD_SC_CONFIGURATION_WRONG_SA_SIZE; + } +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + if ((0 != addr->sa_len) && + (sizeof(struct sockaddr_in) > (size_t) addr->sa_len) ) + { + mhd_LOG_MSG (daemon, MHD_SC_CONFIGURATION_WRONG_SA_SIZE, \ + "MHD_add_connection() has been called with " \ + "non-zero value of 'sa_len' member of " \ + "'struct sockaddr' which does not match 'sa_family'."); + (void) mhd_socket_close (client_socket); + return MHD_SC_CONFIGURATION_WRONG_SA_SIZE; + } +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + } +#ifdef HAVE_INET6 + if (AF_INET6 == addr->sa_family) + { + if (sizeof(struct sockaddr_in6) > addrlen) + { + mhd_LOG_MSG (daemon, MHD_SC_CONFIGURATION_WRONG_SA_SIZE, \ + "MHD_add_connection() has been called with " \ + "incorrect 'addrlen' value."); + (void) mhd_socket_close (client_socket); + return MHD_SC_CONFIGURATION_WRONG_SA_SIZE; + } +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + if ((0 != addr->sa_len) && + (sizeof(struct sockaddr_in6) > (size_t) addr->sa_len) ) + { + mhd_LOG_MSG (daemon, MHD_SC_CONFIGURATION_WRONG_SA_SIZE, \ + "MHD_add_connection() has been called with " \ + "non-zero value of 'sa_len' member of " \ + "'struct sockaddr' which does not match 'sa_family'."); + (void) mhd_socket_close (client_socket); + return MHD_SC_CONFIGURATION_WRONG_SA_SIZE; + } +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + } +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + if ((0 != addr->sa_len) && + (addrlen > (size_t) addr->sa_len)) + addrlen = (size_t) addr->sa_len; /* Use safest value */ +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +#endif /* HAVE_INET6 */ + } + + if (! mhd_FD_FITS_DAEMON (daemon, client_socket)) + { + mhd_LOG_MSG (daemon, MHD_SC_NEW_CONN_FD_OUTSIDE_OF_SET_RANGE, \ + "The new connection FD value is higher than allowed"); + (void) mhd_socket_close (client_socket); + return MHD_SC_NEW_CONN_FD_OUTSIDE_OF_SET_RANGE; + } + + if (! mhd_socket_nonblocking (client_socket)) + { + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NONBLOCKING_FAILED, \ + "Failed to set nonblocking mode on the new client socket."); + sk_nonbl = false; + } + else + sk_nonbl = true; + +#ifndef MHD_WINSOCK_SOCKETS + sk_spipe_supprs = false; +#else /* MHD_WINSOCK_SOCKETS */ + sk_spipe_supprs = true; /* Nothing to suppress on W32 */ +#endif /* MHD_WINSOCK_SOCKETS */ +#if defined(MHD_socket_nosignal_) + if (! sk_spipe_supprs) + sk_spipe_supprs = MHD_socket_nosignal_ (client_socket); + if (! sk_spipe_supprs) + { + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NOSIGPIPE_FAILED, \ + "Failed to suppress SIGPIPE on the new client socket."); +#ifndef MSG_NOSIGNAL + /* Application expects that SIGPIPE will be suppressed, + * but suppression failed and SIGPIPE cannot be suppressed with send(). */ + if (! daemon->sigpipe_blocked) + { + int err = MHD_socket_get_error_ (); + MHD_socket_close_ (client_socket); + MHD_socket_fset_error_ (err); + return MHD_SC_ACCEPT_CONFIGURE_NOSIGPIPE_FAILED; + } +#endif /* MSG_NOSIGNAL */ + } +#endif /* MHD_socket_nosignal_ */ + + if (1) // TODO: implement turbo + { + if (! mhd_socket_noninheritable (client_socket)) + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NOINHERIT_FAILED, \ + "Failed to set noninheritable mode on new client socket."); + } + + /* Copy to sockaddr_storage structure to avoid alignment problems */ + if (0 < addrlen) + memcpy (&addrstorage, addr, addrlen); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + addrstorage.ss_len = addrlen; /* Force set the right length */ +#endif /* HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN */ + +#if defined(MHD_USE_THREADS) + if (mhd_D_TYPE_HAS_WORKERS (daemon->threading.d_type)) + { + unsigned int i; + /* have a pool, try to find a pool with capacity; we use the + socket as the initial offset into the pool for load + balancing */ + unsigned int offset; +#ifdef MHD_WINSOCK_SOCKETS + uint_fast64_t osb = (uint_fast64_t) client_socket; + osb ^= (((uint_fast64_t) client_socket) >> 9); + osb ^= (((uint_fast64_t) client_socket) >> 18); + osb ^= (((uint_fast64_t) client_socket) >> 27); + osb ^= (((uint_fast64_t) client_socket) >> 36); + osb ^= (((uint_fast64_t) client_socket) >> 45); + osb ^= (((uint_fast64_t) client_socket) >> 54); + osb ^= (((uint_fast64_t) client_socket) >> 63); + offset = (unsigned int) osb; +#else + offset = (unsigned int) client_socket; +#endif + + for (i = 0; i < daemon->threading.hier.pool.num; ++i) + { + struct MHD_Daemon *const restrict worker = + daemon->threading.hier.pool.workers + + (i + offset) % daemon->threading.hier.pool.num; + if (worker->conns.block_new) + continue; + return internal_add_connection (worker, + client_socket, + &addrstorage, + addrlen, + true, + sk_nonbl, + sk_spipe_supprs, + mhd_T_MAYBE); + } + + /* all pools are at their connection limit, must refuse */ + (void) mhd_socket_close (client_socket); + return MHD_SC_LIMIT_CONNECTIONS_REACHED; + } +#endif /* MHD_USE_THREADS */ + + return internal_add_connection (daemon, + client_socket, + &addrstorage, + addrlen, + true, + sk_nonbl, + sk_spipe_supprs, + mhd_T_MAYBE); +} + + +MHD_INTERNAL enum mhd_DaemonAcceptResult +mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) +{ + struct sockaddr_storage addrstorage; + socklen_t addrlen; + MHD_Socket s; + MHD_Socket fd; + bool sk_nonbl; + bool sk_spipe_supprs; + bool sk_cloexec; + enum mhd_Tristate sk_non_ip; +#if defined(_DEBUG) && defined (USE_ACCEPT4) + const bool use_accept4 = ! daemon->dbg.avoid_accept4; +#elif defined (USE_ACCEPT4) + static const bool use_accept4 = true; +#else /* ! USE_ACCEPT4 && ! _DEBUG */ + static const bool use_accept4 = false; +#endif /* ! USE_ACCEPT4 && ! _DEBUG */ + +#ifdef MHD_USE_THREADS + mhd_assert ((! mhd_D_HAS_THREADS (daemon)) || \ + mhd_thread_handle_ID_is_current_thread (daemon->threading.tid)); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (daemon->threading.d_type)); +#endif /* MHD_USE_THREADS */ + + fd = daemon->net.listen.fd; + mhd_assert (MHD_INVALID_SOCKET != fd); + + addrlen = (socklen_t) sizeof (addrstorage); + memset (&addrstorage, + 0, + (size_t) addrlen); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + addrstorage.ss_len = addrlen; +#endif /* HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN */ + + /* Initialise with default values to avoid compiler warnings */ + sk_nonbl = false; + sk_spipe_supprs = false; + sk_cloexec = false; + s = MHD_INVALID_SOCKET; + +#ifdef USE_ACCEPT4 + if (use_accept4 && + (MHD_INVALID_SOCKET != + (s = accept4 (fd, + (struct sockaddr *) &addrstorage, + &addrlen, + SOCK_CLOEXEC_OR_ZERO | SOCK_NONBLOCK_OR_ZERO + | SOCK_NOSIGPIPE_OR_ZERO)))) + { + sk_nonbl = (SOCK_NONBLOCK_OR_ZERO != 0); +#ifndef MHD_WINSOCK_SOCKETS + sk_spipe_supprs = (SOCK_NOSIGPIPE_OR_ZERO != 0); +#else /* MHD_WINSOCK_SOCKETS */ + sk_spipe_supprs = true; /* Nothing to suppress on W32 */ +#endif /* MHD_WINSOCK_SOCKETS */ + sk_cloexec = (SOCK_CLOEXEC_OR_ZERO != 0); + } +#endif /* USE_ACCEPT4 */ +#if defined(_DEBUG) || ! defined(USE_ACCEPT4) + if (! use_accept4 && + (MHD_INVALID_SOCKET != + (s = accept (fd, + (struct sockaddr *) &addrstorage, + &addrlen)))) + { +#ifdef MHD_ACCEPT_INHERIT_NONBLOCK + sk_nonbl = daemon->listen_nonblk; +#else /* ! MHD_ACCEPT_INHERIT_NONBLOCK */ + sk_nonbl = false; +#endif /* ! MHD_ACCEPT_INHERIT_NONBLOCK */ +#ifndef MHD_WINSOCK_SOCKETS + sk_spipe_supprs = false; +#else /* MHD_WINSOCK_SOCKETS */ + sk_spipe_supprs = true; /* Nothing to suppress on W32 */ +#endif /* MHD_WINSOCK_SOCKETS */ + sk_cloexec = false; + } +#endif /* _DEBUG || !USE_ACCEPT4 */ + + if (MHD_INVALID_SOCKET == s) + { /* This could be a common occurrence with multiple worker threads */ + const int err = mhd_SCKT_GET_LERR (); + + if (mhd_SCKT_ERR_IS_EINVAL (err)) + return mhd_DAEMON_ACCEPT_NO_MORE_PENDING; /* can happen during shutdown */ // FIXME: remove? + if (mhd_SCKT_ERR_IS_DISCNN_BEFORE_ACCEPT (err)) + return mhd_DAEMON_ACCEPT_NO_MORE_PENDING; /* do not print error if client just disconnects early */ + if (mhd_SCKT_ERR_IS_EINTR (err)) + return mhd_DAEMON_ACCEPT_SKIPPED; + if (mhd_SCKT_ERR_IS_EAGAIN (err)) + return mhd_DAEMON_ACCEPT_NO_MORE_PENDING; + if (mhd_SCKT_ERR_IS_LOW_RESOURCES (err) ) + { + /* system/process out of resources */ + if (0 == daemon->conns.count) + { + /* Not setting 'block_new' flag, as there is no way it + would ever be cleared. Instead trying to produce + bit fat ugly warning. */ + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_SYSTEM_LIMIT_REACHED_INSTANTLY, \ + "Hit process or system resource limit at FIRST " \ + "connection. This is really bad as there is no sane " \ + "way to proceed. Will try busy waiting for system " \ + "resources to become magically available."); + } + else + { + daemon->conns.block_new = true; + mhd_LOG_PRINT (daemon, MHD_SC_ACCEPT_SYSTEM_LIMIT_REACHED, \ + mhd_LOG_FMT ("Hit process or system resource limit " \ + "at %u connections, temporarily " \ + "suspending accept(). Consider setting " \ + "a lower MHD_OPTION_CONNECTION_LIMIT."), \ + daemon->conns.count); + } + return mhd_DAEMON_ACCEPT_FAILED; + } + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_FAILED_UNEXPECTEDLY, + "Error accepting connection."); + return mhd_DAEMON_ACCEPT_FAILED; + } + + 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."); + (void) mhd_socket_close (s); + return mhd_DAEMON_ACCEPT_FAILED; + } + if (mhd_SOCKET_TYPE_IP == daemon->net.listen.type) + sk_non_ip = mhd_T_NO; + else if (mhd_SOCKET_TYPE_UNKNOWN == daemon->net.listen.type) + sk_non_ip = mhd_T_MAYBE; + else + sk_non_ip = mhd_T_YES; + if (0 >= addrlen) + { + if (mhd_SOCKET_TYPE_IP == daemon->net.listen.type) + mhd_LOG_MSG (daemon, MHD_SC_ACCEPTED_UNKNOWN_TYPE, \ + "Accepted socket has non-positive length of the address. " \ + "Processing the new socket as a socket with " \ + "unknown type."); + addrlen = 0; + sk_non_ip = mhd_T_MAYBE; + } + else if (((socklen_t) sizeof (addrstorage)) < addrlen) + { + /* Should not happen as 'sockaddr_storage' must be large enough to + * store any address supported by the system. */ + mhd_LOG_MSG (daemon, MHD_SC_ACCEPTED_SOCKADDR_TOO_LARGE, \ + "Accepted socket address is larger than expected by " \ + "system headers. Processing the new socket as a socket with " \ + "unknown type."); + addrlen = 0; + sk_non_ip = mhd_T_MAYBE; /* IP-type addresses must fit */ + } + else if (mhd_T_MAYBE == sk_non_ip) + { + if (AF_INET == ((struct sockaddr *) &addrstorage)->sa_family) + sk_non_ip = mhd_T_NO; +#ifdef HAVE_INET6 + else if (AF_INET6 == ((struct sockaddr *) &addrstorage)->sa_family) + sk_non_ip = mhd_T_NO; +#endif /* HAVE_INET6 */ + } + + if (! sk_nonbl && ! mhd_socket_nonblocking (s)) + { + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NONBLOCKING_FAILED, \ + "Failed to set nonblocking mode on incoming connection " \ + "socket."); + } + else + sk_nonbl = true; + + if (! sk_cloexec && ! mhd_socket_noninheritable (s)) + { + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NOINHERIT_FAILED, \ + "Failed to set non-inheritable mode on incoming connection " \ + "socket."); + } + +#if defined(MHD_socket_nosignal_) + if (! sk_spipe_supprs && ! MHD_socket_nosignal_ (s)) + { + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NOSIGPIPE_FAILED, + "Failed to suppress SIGPIPE on incoming connection " \ + "socket."); +#ifndef MSG_NOSIGNAL + /* Application expects that SIGPIPE will be suppressed, + * but suppression failed and SIGPIPE cannot be suppressed with send(). */ + if (! daemon->sigpipe_blocked) + { + (void) MHD_socket_close_ (s); + return MHD_NO; + } +#endif /* MSG_NOSIGNAL */ + } + else + sk_spipe_supprs = true; +#endif /* MHD_socket_nosignal_ */ + return (MHD_SC_OK == internal_add_connection (daemon, + s, + &addrstorage, + (size_t) addrlen, + false, + sk_nonbl, + sk_spipe_supprs, + sk_non_ip)) ? + 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) +{ + 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 (! c->in_proc_ready); + mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (c, proc_ready)); + mhd_assert (NULL == mhd_DLINKEDL_GET_PREV (c, proc_ready)); + mhd_assert (c != mhd_DLINKEDL_GET_FIRST (&(c->daemon->events), proc_ready)); + mhd_assert (c != mhd_DLINKEDL_GET_LAST (&(c->daemon->events), proc_ready)); + + 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); + + c->daemon->conns.count--; + c->daemon->conns.block_new = false; + + free (c); +} diff --git a/src/mhd2/daemon_add_conn.h b/src/mhd2/daemon_add_conn.h @@ -0,0 +1,96 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff + + 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/daemon_add_conn.h + * @brief The declaration of internal functions for adding new connections + * @author Karlson2k (Evgeny Grin) + * @author Daniel Pittman + * @author Christian Grothoff + */ + +#ifndef MHD_DAEMON_ADD_CONN +#define MHD_DAEMON_ADD_CONN 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Daemon; /* Forward declaration */ +struct MHD_Connection; /* Forward declaration */ + +/** + * The result of the accepting of the new connection + */ +enum mhd_DaemonAcceptResult +{ + /** + * New connection has been accepted successfully. + * Probably more connections are pending to be accepted. + */ + mhd_DAEMON_ACCEPT_SUCCESS = 0 + , + /** + * New connection has been skipped for some reason. + * It is OK to try to accept more connections right now. + */ + mhd_DAEMON_ACCEPT_SKIPPED = 1 << 0 + , + /** + * No more new connections are pending, the listen backlog is empty + */ + mhd_DAEMON_ACCEPT_NO_MORE_PENDING = 1 << 1 + , + /** + * Connection accept failed, but the listen backlog could be not empty. + * Do not try to accept more connection right now. + */ + mhd_DAEMON_ACCEPT_FAILED = 1 << 2 +}; + + +/** + * Accept an incoming connection and create the MHD_Connection object for + * it. This function also enforces policy by way of checking with the + * accept policy callback. + * @remark To be called only from thread that process + * daemon's select()/poll()/etc. + * + * @param daemon handle with the listen socket + * @return a #mhd_DaemonAcceptResult value + */ +MHD_INTERNAL enum mhd_DaemonAcceptResult +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 +mhd_conn_close_final (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +#endif /* ! MHD_DAEMON_ADD_CONN */ diff --git a/src/mhd2/daemon_create.c b/src/mhd2/daemon_create.c @@ -0,0 +1,112 @@ +/* + 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/daemon_create.c + * @brief The implementation of the MHD_daemon_create() + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "sys_malloc.h" + +#include "mhd_public_api.h" + +#include "compat_calloc.h" + +#include "mhd_daemon.h" +#include "daemon_options.h" + +#include "daemon_logger_default.h" + +#include "mhd_lib_init.h" + + +MHD_FN_MUST_CHECK_RESULT_ MHD_EXTERN_ struct MHD_Daemon * +MHD_daemon_create (MHD_RequestCallback req_cb, + void *req_cb_cls) +{ + struct MHD_Daemon *d; + struct DaemonOptions *s; + + MHD_GLOBAL_INIT_CHECK (); + + if (NULL == req_cb) + return NULL; + + d = (struct MHD_Daemon *) mhd_calloc (1, sizeof(struct MHD_Daemon)); + if (NULL == d) + return NULL; + + s = (struct DaemonOptions *) mhd_calloc (1, sizeof(struct DaemonOptions)); + if (NULL == s) + { + free (d); + return NULL; + } + /* calloc() does not guarantee that floating point values and pointers + are initialised to zero and NULL (respectfully). */ + /* Any floating point and pointer members must be initialised manually here */ +#ifndef HAVE_NULL_PTR_ALL_ZEROS + s->bind_sa.v_sa = NULL; + s->tls_key_cert.v_mem_key = NULL; + s->tls_key_cert.v_mem_cert = NULL; + s->tls_key_cert.v_mem_pass = NULL; + s->tls_client_ca = NULL; + s->tls_psk_callback.v_psk_cb = NULL; + s->tls_psk_callback.v_psk_cb_cls = NULL; + s->accept_policy.v_apc = NULL; + s->accept_policy.v_apc_cls = NULL; + s->early_uri_logger.v_cb = NULL; + s->early_uri_logger.v_cls = NULL; + s->daemon_ready_callback.v_cb = NULL; + s->daemon_ready_callback.v_cb_cls = NULL; + s->notify_connection.v_ncc = NULL; + s->notify_connection.v_cls = NULL; + s->notify_stream.v_nsc = NULL; + s->notify_stream.v_cls = NULL; + s->random_entropy.v_buf = NULL; + + d->log_params.v_log_cb = NULL; /* optional */ +#endif /* !HAVE_NULL_PTR_ALL_ZEROS */ + + s->listen_socket = MHD_INVALID_SOCKET; + s->fd_number_limit = MHD_INVALID_SOCKET; + + d->log_params.v_log_cb = mhd_logger_default; + d->req_cfg.cb = req_cb; + d->req_cfg.cb_cls = req_cb_cls; + d->settings = s; + + return d; +} + + +/* This is a workaround for GCC/binutils bug. + * To make sure that initialisation functions are called when MHD is used as + * a static library, put initialisation functions to the file with function + * that always referred/used by application/user of the library. + * If application does not refer any function, the initialiser call could be + * missed for the static library. + */ +#define MHD_LIB_INIT_IMPL_H_IN_DAEMON_CREATE_C 1 +#include "mhd_lib_init_impl.h" diff --git a/src/mhd2/daemon_funcs.c b/src/mhd2/daemon_funcs.c @@ -0,0 +1,182 @@ +/* + 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/daemon_funcs.c + * @brief The implementation of internal daemon-related functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "daemon_funcs.h" + +#include "sys_base_types.h" +#include "sys_malloc.h" + +#include "mhd_assert.h" +#include "mhd_itc.h" +#include "mhd_daemon.h" +#include "daemon_logger.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_MUST_CHECK_RESULT_ struct MHD_Daemon * +mhd_daemon_get_master_daemon (struct MHD_Daemon *restrict d) +{ +#ifdef MHD_USE_THREADS + if (mhd_D_HAS_MASTER (d)) + return d->threading.hier.master; +#endif /* MHD_USE_THREADS */ + return d; +} + + +#ifdef MHD_USE_THREADS + +MHD_INTERNAL bool +mhd_daemon_trigger_itc (struct MHD_Daemon *restrict d) +{ + mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); + if (! mhd_itc_activate (d->threading.itc)) + { + mhd_LOG_MSG (d, MHD_SC_ITC_USE_FAILED, \ + "Failed to communicate by ITC with the daemon thread."); + return false; + } + return true; +} + + +#endif /* MHD_USE_THREADS */ + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_daemon_resume_conns (struct MHD_Daemon *restrict d) +{ + (void) d; + mhd_assert (0 && "Not implemented yet"); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_MUST_CHECK_RESULT_ bool +mhd_daemon_claim_lbuf (struct MHD_Daemon *d, + size_t requested_size) +{ + bool ret; + struct MHD_Daemon *const masterd = mhd_daemon_get_master_daemon (d); + mhd_assert (0 != requested_size); + if (0 == masterd->req_cfg.large_buf.space_left) + return false; /* Shortcut for typical use without large buffer */ + + ret = false; + mhd_mutex_lock_chk (&(masterd->req_cfg.large_buf.lock)); + if (masterd->req_cfg.large_buf.space_left >= requested_size) + { + masterd->req_cfg.large_buf.space_left -= requested_size; + ret = true; + } + mhd_mutex_unlock_chk (&(masterd->req_cfg.large_buf.lock)); + return ret; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_daemon_reclaim_lbuf (struct MHD_Daemon *d, + size_t reclaimed_size) +{ + struct MHD_Daemon *const masterd = mhd_daemon_get_master_daemon (d); + mhd_assert (0 != reclaimed_size); + mhd_mutex_lock_chk (&(masterd->req_cfg.large_buf.lock)); + masterd->req_cfg.large_buf.space_left += reclaimed_size; + mhd_mutex_unlock_chk (&(masterd->req_cfg.large_buf.lock)); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ +MHD_FN_PAR_OUT_ (3) bool +mhd_daemon_get_lbuf (struct MHD_Daemon *restrict d, + size_t requested_size, + struct mhd_Buffer *restrict buf) +{ + if (! mhd_daemon_claim_lbuf (d, requested_size)) + { + buf->size = 0; + buf->buf = NULL; + return false; + } + buf->buf = (char *) malloc (requested_size); + if (NULL == buf->buf) + { + buf->size = 0; + mhd_daemon_reclaim_lbuf (d, requested_size); + return false; + } + buf->size = requested_size; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ +MHD_FN_PAR_INOUT_ (3) bool +mhd_daemon_grow_lbuf (struct MHD_Daemon *restrict d, + size_t grow_size, + struct mhd_Buffer *restrict buf) +{ + void *new_alloc; + mhd_assert (NULL != buf->buf || 0 == buf->size); + mhd_assert (0 != buf->size || NULL == buf->buf); + + if (! mhd_daemon_claim_lbuf (d, grow_size)) + return false; + + if (NULL == buf->buf) + new_alloc = malloc (grow_size); + else + new_alloc = realloc (buf->buf, buf->size + grow_size); + if (NULL == new_alloc) + { + mhd_daemon_reclaim_lbuf (d, grow_size); + return false; + } + + buf->buf = (char *) new_alloc; + buf->size += grow_size; + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (2) void +mhd_daemon_free_lbuf (struct MHD_Daemon *restrict d, + struct mhd_Buffer *restrict buf) +{ + if (0 == buf->size) + { + mhd_assert (NULL == buf->buf); + return; + } + free (buf->buf); + buf->buf = NULL; + mhd_daemon_reclaim_lbuf (d, buf->size); + buf->size = 0; +} diff --git a/src/mhd2/daemon_funcs.h b/src/mhd2/daemon_funcs.h @@ -0,0 +1,138 @@ +/* + 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/daemon_funcs.h + * @brief The declarations of internal daemon-related functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DAEMON_FUNCS_H +#define MHD_DAEMON_FUNCS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "mhd_buffer.h" + +struct MHD_Daemon; /* forward declaration */ + + +/** + * Get controlling daemon + * @param d the daemon to get controlling daemon + * @return the master daemon (possible the same as the @a d) + */ +MHD_INTERNAL struct MHD_Daemon * +mhd_daemon_get_master_daemon (struct MHD_Daemon *restrict d) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_; + +#ifdef MHD_USE_THREADS + +/** + * Trigger daemon ITC. + * This should cause daemon's thread to stop waiting for the network events + * and process pending information + * @param d the daemon object, ITC should be initialised + * @return true if succeed, false otherwise + */ +MHD_INTERNAL bool +mhd_daemon_trigger_itc (struct MHD_Daemon *restrict d); + +#endif /* MHD_USE_THREADS */ + + +/** + * Check whether any resuming connections are pending and resume them + * @param d the daemon to use + */ +MHD_INTERNAL void +mhd_daemon_resume_conns (struct MHD_Daemon *restrict d) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Request allocation of the large buffer + * @param d the daemon to use + * @param requested_size the requested size of allocation + * @return true if allocation allowed and counted, + * false otherwise + */ +MHD_INTERNAL bool +mhd_daemon_claim_lbuf (struct MHD_Daemon *restrict d, + size_t requested_size) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_; + + +/** + * Reclaim the large buffer allocation. + * Must be called when the allocation has been already freed. + * @param d the daemon to use + * @param reclaimed_size the deallocated size + */ +MHD_INTERNAL void +mhd_daemon_reclaim_lbuf (struct MHD_Daemon *restrict d, + size_t reclaimed_size) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Allocate the large buffer + * @param d the daemon to use + * @param requested_size the requested size of allocation + * @param[out] buf the buffer to allocate + * @return true if buffer is allocated, + * false otherwise + */ +MHD_INTERNAL bool +mhd_daemon_get_lbuf (struct MHD_Daemon *restrict d, + size_t requested_size, + struct mhd_Buffer *restrict buf) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ + MHD_FN_PAR_OUT_ (3); + +/** + * Grow the large buffer, which previously was allocated + * @param d the daemon to use + * @param grow_size the requested size of grow + * @param[in,out] buf the buffer to grow + * @return true if buffer has been grown, + * false otherwise + */ +MHD_INTERNAL bool +mhd_daemon_grow_lbuf (struct MHD_Daemon *restrict d, + size_t grow_size, + struct mhd_Buffer *restrict buf) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ + MHD_FN_PAR_INOUT_ (3); + + +/** + * Free large buffer. + * @param d the daemon to use + * @param[in,out] buf the buffer to free + */ +MHD_INTERNAL void +mhd_daemon_free_lbuf (struct MHD_Daemon *restrict d, + struct mhd_Buffer *restrict buf) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (2); + + +#endif /* ! MHD_DAEMON_FUNCS_H */ diff --git a/src/mhd2/daemon_get_info.c b/src/mhd2/daemon_get_info.c @@ -0,0 +1,87 @@ +/* + 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/daemon_get_info.c + * @brief The implementation of MHD_daemon_get_info_*() functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "sys_sockets_types.h" + +#include "mhd_socket_type.h" +#include "mhd_daemon.h" + +#include "mhd_public_api.h" + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_OUT_ (3) +MHD_FN_PURE_ enum MHD_StatusCode +MHD_daemon_get_info_fixed_sz (struct MHD_Daemon *daemon, + enum MHD_DaemonInfoFixedType info_type, + union MHD_DaemonInfoFixedData *return_value, + size_t return_value_size) +{ + switch (info_type) + { + case MHD_DAEMON_INFO_FIXED_LISTEN_SOCKET: + if (MHD_INVALID_SOCKET == daemon->net.listen.fd) + return MHD_SC_INFO_GET_TYPE_UNSUPPORTED; + if (sizeof(MHD_Socket) > return_value_size) + return MHD_SC_INFO_GET_BUFF_TOO_SMALL; + return_value->v_socket = daemon->net.listen.fd; + return MHD_SC_OK; + case MHD_DAEMON_INFO_FIXED_AGGREAGATE_FD: +#ifdef MHD_USE_EPOLL + if (! mhd_D_IS_USING_EPOLL (daemon)) + return MHD_SC_INFO_GET_TYPE_UNSUPPORTED; + if (sizeof(int) > return_value_size) + return MHD_SC_INFO_GET_BUFF_TOO_SMALL; + return_value->v_fd = daemon->events.data.epoll.e_fd; + return MHD_SC_OK; +#else + return MHD_SC_INFO_GET_TYPE_NOT_SUPP_BY_BUILD; +#endif + break; + case MHD_DAEMON_INFO_FIXED_BIND_PORT: + if (MHD_INVALID_SOCKET == daemon->net.listen.fd) + return MHD_SC_INFO_GET_TYPE_UNSUPPORTED; + if (mhd_SOCKET_TYPE_UNKNOWN > daemon->net.listen.type) + return MHD_SC_INFO_GET_TYPE_UNSUPPORTED; + if (0 == daemon->net.listen.port) + { + if (mhd_SOCKET_TYPE_IP != daemon->net.listen.type) + return MHD_SC_INFO_GET_TYPE_UNSUPPORTED; + return MHD_SC_INFO_GET_TYPE_UNAVAILALBE; + } + if (sizeof(return_value->v_port) > return_value_size) + return MHD_SC_INFO_GET_BUFF_TOO_SMALL; + return_value->v_port = daemon->net.listen.port; + return MHD_SC_OK; + case MHD_DAEMON_INFO_FIXED_SENTINEL: + default: + break; + } + return MHD_SC_INFO_TYPE_UNKNOWN; +} diff --git a/src/mhd2/daemon_logger.c b/src/mhd2/daemon_logger.c @@ -0,0 +1,53 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/daemon_logger.c + * @brief The implementation of the logger function + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#ifdef HAVE_LOG_FUNCTIONALITY +#include <stdarg.h> +#include "sys_null_macro.h" +#include "mhd_daemon.h" +#include "daemon_logger.h" + + +MHD_INTERNAL void +mhd_logger (struct MHD_Daemon *daemon, + enum MHD_StatusCode sc, + const char *fm, + ...) +{ + if (NULL != daemon->log_params.v_log_cb) + { + va_list vargs; + va_start (vargs, fm); + daemon->log_params.v_log_cb (daemon->log_params.v_log_cb_cls, + sc, fm, vargs); + va_end (vargs); + } +} + + +#endif /* ! HAVE_LOG_FUNCTIONALITY */ diff --git a/src/mhd2/daemon_logger.h b/src/mhd2/daemon_logger.h @@ -0,0 +1,133 @@ +/* + 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/daemon_logger.h + * @brief The declaration of the logger function and relevant macros + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DAEMON_LOGGER_H +#define MHD_DAEMON_LOGGER_H 1 + +#include "mhd_sys_options.h" + +#ifdef HAVE_LOG_FUNCTIONALITY + +#include "mhd_public_api.h" /* For enum MHD_StatusCode */ + +struct MHD_Daemon; /* Forward declaration */ + +/* Do not use this function directly, use wrapper macros below */ +/** + * The daemon logger function. + * @param daemon the daemon handle + * @param sc the status code of the event + * @param fm the format string ('printf()'-style) + * @ingroup logging + */ +MHD_INTERNAL void +mhd_logger (struct MHD_Daemon *daemon, + enum MHD_StatusCode sc, + const char *fm, + ...); + +/** + * Log a single message. + * + * The @a msg is a 'printf()' string, treated as format specifiers string. + * Any '%' symbols should be doubled ('%%') to avoid interpretation as a format + * specifier symbol. + */ +#define mhd_LOG_MSG(daemon,sc,msg) mhd_logger (daemon,sc,msg) + +/** + * Format message and log it + * + * Always use with #mhd_LOG_FMT() for the format string. + */ +#define mhd_LOG_PRINT mhd_logger + +/** + * The wrapper macro for the format string to be used for format parameter for + * the #mhd_LOG_FMT() macro + */ +#define mhd_LOG_FMT(format_string) format_string + +#else /* ! HAVE_LOG_FUNCTIONALITY */ + + +#ifdef HAVE_MACRO_VARIADIC + +/** + * Log a single message. + * + * The @a msg is a 'printf()' string, treated as format specifiers string. + * Any '%' symbols should be doubled ('%%') to avoid interpretation as a format + * specifier symbol. + */ +#define mhd_LOG_MSG(daemon,sc,msg) do { (void) daemon; } while (0) + +/** + * Format message and log it + * + * Always use with #mhd_LOG_FMT() for the format string. + */ +#define mhd_LOG_PRINT(daemon,sc,fm,...) do { (void) daemon; } while (0) + +#else /* ! HAVE_MACRO_VARIADIC */ + +#include "sys_base_types.h" /* For NULL */ + +/** + * Format message and log it + * + * Always use with #mhd_LOG_FMT() for the format string. + */ +MHD_static_inline_ void +mhd_LOG_PRINT (struct MHD_Daemon *daemon, + enum MHD_StatusCode sc, + const char *fm, + ...) +{ + (void) daemon; (void) sc; (void) fm; +} + + +/** + * Log a single message. + * + * The @a msg is a 'printf()' string, treated as format specifiers string. + * Any '%' symbols should be doubled ('%%') to avoid interpretation as a format + * specifier symbol. + */ +#define mhd_LOG_MSG(daemon,sc,msg) mhd_LOG_PRINT (daemon,sc,NULL) + +#endif /* ! HAVE_MACRO_VARIADIC */ + +/** + * The wrapper macro for the format string to be used for format parameter for + * the #mhd_LOG_FMT() macro + */ +#define mhd_LOG_FMT(format_string) NULL + +#endif /* ! HAVE_LOG_FUNCTIONALITY */ + +#endif /* ! MHD_DAEMON_LOGGER_H */ diff --git a/src/mhd2/daemon_logger_default.c b/src/mhd2/daemon_logger_default.c @@ -0,0 +1,57 @@ +/* + 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/daemon_logger_default.h + * @brief The declaration of the default logger function + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#ifdef HAVE_LOG_FUNCTIONALITY + +#include <stdio.h> + +#include "daemon_logger_default.h" +#include "mhd_assert.h" /* For NDEBUG macro */ + +MHD_INTERNAL void +mhd_logger_default (void *cls, + enum MHD_StatusCode sc, + const char *fm, + va_list ap) +{ + int res; + (void) cls; /* Not used by default logger */ + (void) sc; /* Not used by default logger */ + + res = vfprintf (stderr, fm, ap); + (void) res; /* The result of vfprintf() call is ignored */ + res = fprintf (stderr, "\n"); + (void) res; /* The result of vfprintf() call is ignored */ +#ifndef NDEBUG + res = fflush (stderr); + (void) res; /* The result of fflush() call is ignored */ +#endif /* ! NDEBUG */ +} + + +#endif /* ! HAVE_LOG_FUNCTIONALITY */ diff --git a/src/mhd2/daemon_logger_default.h b/src/mhd2/daemon_logger_default.h @@ -0,0 +1,60 @@ +/* + 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/daemon_logger_default.c + * @brief The implementation of the default logger function + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DAEMON_LOGGER_DEFAULT_H +#define MHD_DAEMON_LOGGER_DEFAULT_H 1 + +#include "mhd_sys_options.h" + +#ifdef HAVE_LOG_FUNCTIONALITY + +#include "mhd_public_api.h" /* For enum MHD_StatusCode */ + +#include <stdarg.h> + +/** + * Default logger function. + * @param cls the closure + * @param sc the status code of the event + * @param fm the format string (`printf()`-style) + * @param ap the arguments to @a fm + * @ingroup logging + */ +MHD_INTERNAL void +mhd_logger_default (void *cls, + enum MHD_StatusCode sc, + const char *fm, + va_list ap); + +#else /* ! HAVE_LOG_FUNCTIONALITY */ + +#include "sys_null_macro.h" + +#define mhd_logger_default NULL + +#endif /* ! MHD_DAEMON_LOGGER_DEFAULT_H */ + +#endif /* ! MHD_DAEMON_LOGGER_DEFAULT_H */ diff --git a/src/mhd2/daemon_options.h b/src/mhd2/daemon_options.h @@ -0,0 +1,290 @@ +/* This is generated code, it is still under LGPLv2.1+. + Do not edit directly! */ +/* *INDENT-OFF* */ +/** + * @file daemon_options.h + * @author daemon-options-generator.c + */ + +#ifndef MHD_DAEMON_OPTIONS_H +#define MHD_DAEMON_OPTIONS_H 1 + +#include "mhd_sys_options.h" +#include "mhd_public_api.h" + +struct DaemonOptions +{ + /** + * Value for #MHD_D_O_WORK_MODE. + * the object created by one of the next functions/macros: #MHD_WM_OPTION_EXTERNAL_PERIODIC(), #MHD_WM_OPTION_EXTERNAL_EVENT_LOOP_CB_LEVEL(), #MHD_WM_OPTION_EXTERNAL_EVENT_LOOP_CB_EDGE(), #MHD_WM_OPTION_EXTERNAL_SINGLE_FD_WATCH(), #MHD_WM_OPTION_WORKER_THREADS(), #MHD_WM_OPTION_THREAD_PER_CONNECTION() + */ + struct MHD_WorkModeWithParam work_mode; + + + /** + * Value for #MHD_D_O_POLL_SYSCALL. + * FIXME + */ + enum MHD_SockPollSyscall poll_syscall; + + + /** + * Value for #MHD_D_O_LOG_CALLBACK. + * the callback to use for logging, + * NULL to disable logging + */ + struct MHD_DaemonOptionValueLog log_callback; + + + /** + * Value for #MHD_D_O_BIND_PORT. + * the address family to use, + * the #MHD_AF_NONE to disable listen socket (the same effect as if this option is not used) + */ + struct MHD_DaemonOptionValueBind bind_port; + + + /** + * Value for #MHD_D_O_BIND_SA. + * the size of the socket address pointed by @a sa. + */ + struct MHD_DaemonOptionValueSA bind_sa; + + + /** + * Value for #MHD_D_O_LISTEN_SOCKET. + * the listen socket to use, ignored if set to #MHD_INVALID_SOCKET + */ + MHD_Socket listen_socket; + + + /** + * Value for #MHD_D_O_LISTEN_ADDR_REUSE. + * FIXME + */ + enum MHD_DaemonOptionBindType listen_addr_reuse; + + + /** + * Value for #MHD_D_O_TCP_FASTOPEN. + * the type use of of TCP FastOpen + */ + struct MHD_DaemonOptionValueTFO tcp_fastopen; + + + /** + * Value for #MHD_D_O_LISTEN_BACKLOG. + * FIXME + */ + unsigned int listen_backlog; + + + /** + * Value for #MHD_D_O_SIGPIPE_SUPPRESSED. + */ + enum MHD_Bool sigpipe_suppressed; + + + /** + * Value for #MHD_D_O_TLS. + * the TLS backend to use, + * #MHD_TLS_BACKEND_NONE for non-TLS (plain TCP) connections + */ + enum MHD_TlsBackend tls; + + + /** + * Value for #MHD_D_O_TLS_KEY_CERT. + * the private key loaded into memory (not a filename) + */ + struct MHD_DaemonOptionValueTlsCert tls_key_cert; + + + /** + * Value for #MHD_D_O_TLS_CLIENT_CA. + * the CA certificate in memory (not a filename) + */ + const char *tls_client_ca; + + + /** + * Value for #MHD_D_O_TLS_PSK_CALLBACK. + * the function to call to obtain pre-shared key + */ + struct MHD_DaemonOptionValueTlsPskCB tls_psk_callback; + + + /** + * Value for #MHD_D_O_NO_ALPN. + */ + enum MHD_Bool no_alpn; + + + /** + * Value for #MHD_D_O_DEFAULT_TIMEOUT. + * the in seconds, zero for no timeout + */ + unsigned int default_timeout; + + + /** + * Value for #MHD_D_O_GLOBAL_CONNECTION_LIMIT. + * FIXME + */ + unsigned int global_connection_limit; + + + /** + * Value for #MHD_D_O_PER_IP_LIMIT. + * FIXME + */ + unsigned int per_ip_limit; + + + /** + * Value for #MHD_D_O_ACCEPT_POLICY. + * the accept policy callback + */ + struct MHD_DaemonOptionValueAcceptPol accept_policy; + + + /** + * Value for #MHD_D_O_PROTOCOL_STRICT_LEVEL. + * the level of strictness + */ + struct MHD_DaemonOptionValueStrctLvl protocol_strict_level; + + + /** + * Value for #MHD_D_O_EARLY_URI_LOGGER. + * the early URI callback + */ + struct MHD_DaemonOptionValueUriCB early_uri_logger; + + + /** + * Value for #MHD_D_O_DISABLE_URI_QUERY_PLUS_AS_SPACE. + */ + enum MHD_Bool disable_uri_query_plus_as_space; + + + /** + * Value for #MHD_D_O_SUPPRESS_DATE_HEADER. + */ + enum MHD_Bool suppress_date_header; + + + /** + * Value for #MHD_D_O_ENABLE_SHOUTCAST. + */ + enum MHD_Bool enable_shoutcast; + + + /** + * Value for #MHD_D_O_CONN_MEMORY_LIMIT. + */ + size_t conn_memory_limit; + + + /** + * Value for #MHD_D_O_LARGE_POOL_SIZE. + */ + size_t large_pool_size; + + + /** + * Value for #MHD_D_O_STACK_SIZE. + */ + size_t stack_size; + + + /** + * Value for #MHD_D_O_FD_NUMBER_LIMIT. + * FIXME + */ + MHD_Socket fd_number_limit; + + + /** + * Value for #MHD_D_O_TURBO. + */ + enum MHD_Bool turbo; + + + /** + * Value for #MHD_D_O_DISABLE_THREAD_SAFETY. + */ + enum MHD_Bool disable_thread_safety; + + + /** + * Value for #MHD_D_O_DISALLOW_UPGRADE. + */ + enum MHD_Bool disallow_upgrade; + + + /** + * Value for #MHD_D_O_DISALLOW_SUSPEND_RESUME. + */ + enum MHD_Bool disallow_suspend_resume; + + + /** + * Value for #MHD_D_O_DAEMON_READY_CALLBACK. + * the pre-start callback + */ + struct MHD_DaemonOptionValueReadyCB daemon_ready_callback; + + + /** + * Value for #MHD_D_O_NOTIFY_CONNECTION. + * the callback for notifications + */ + struct MHD_DaemonOptionValueNotifConnCB notify_connection; + + + /** + * Value for #MHD_D_O_NOTIFY_STREAM. + * the callback for notifications + */ + struct MHD_DaemonOptionValueNotifStreamCB notify_stream; + + + /** + * Value for #MHD_D_O_RANDOM_ENTROPY. + * the size of the buffer + */ + struct MHD_DaemonOptionEntropySeed random_entropy; + + + /** + * Value for #MHD_D_O_DAUTH_MAP_SIZE. + * the size of the map array + */ + size_t dauth_map_size; + + + /** + * Value for #MHD_D_O_DAUTH_NONCE_BIND_TYPE. + * FIXME + */ + enum MHD_DaemonOptionValueDAuthBindNonce dauth_nonce_bind_type; + + + /** + * Value for #MHD_D_O_DAUTH_DEF_NONCE_TIMEOUT. + * FIXME + */ + unsigned int dauth_def_nonce_timeout; + + + /** + * Value for #MHD_D_O_DAUTH_DEF_MAX_NC. + * FIXME + */ + uint_fast32_t dauth_def_max_nc; + + +}; + +#endif /* ! MHD_DAEMON_OPTIONS_H 1 */ diff --git a/src/mhd2/daemon_set_options.c b/src/mhd2/daemon_set_options.c @@ -0,0 +1,208 @@ +/* This is generated code, it is still under LGPLv2.1+. + Do not edit directly! */ +/* *INDENT-OFF* */ +/** + * @file daemon_set_options.c + * @author daemon-options-generator.c + */ + +#include "mhd_sys_options.h" +#include "sys_bool_type.h" +#include "sys_base_types.h" +#include "sys_malloc.h" +#include <string.h> +#include "mhd_daemon.h" +#include "daemon_options.h" +#include "mhd_public_api.h" + +MHD_FN_PAR_NONNULL_ALL_ MHD_EXTERN_ +enum MHD_StatusCode +MHD_daemon_set_options ( + struct MHD_Daemon *daemon, + const struct MHD_DaemonOptionAndValue *options, + size_t options_max_num) +{ + struct DaemonOptions *restrict settings = daemon->settings; + enum MHD_StatusCode res = MHD_SC_OK; + size_t i; + + if (mhd_DAEMON_STATE_NOT_STARTED != daemon->state) + return MHD_SC_TOO_LATE; + + for (i = 0; i < options_max_num; i++) + { + const struct MHD_DaemonOptionAndValue *const option + = options + i; + switch (option->opt) + { + case MHD_D_O_END: + i = options_max_num - 1; + break; + case MHD_D_O_WORK_MODE: + settings->work_mode = option->val.work_mode; + continue; + case MHD_D_O_POLL_SYSCALL: + settings->poll_syscall = option->val.poll_syscall; + continue; + case MHD_D_O_LOG_CALLBACK: + /* Note: set directly to the daemon */ + daemon->log_params = option->val.log_callback; + continue; + case MHD_D_O_BIND_PORT: + settings->bind_port.v_af = option->val.bind_port.v_af; + settings->bind_port.v_port = option->val.bind_port.v_port; + continue; + case MHD_D_O_BIND_SA: + /* custom setter */ + if (0 != option->val.bind_sa.v_sa_len) + { + if (NULL != settings->bind_sa.v_sa) + free (settings->bind_sa.v_sa); + settings->bind_sa.v_sa = malloc (option->val.bind_sa.v_sa_len); + if (NULL == settings->bind_sa.v_sa) + return MHD_SC_DAEMON_MALLOC_FAILURE; + memcpy (settings->bind_sa.v_sa, option->val.bind_sa.v_sa, + option->val.bind_sa.v_sa_len); + settings->bind_sa.v_sa_len = option->val.bind_sa.v_sa_len; + settings->bind_sa.v_dual = option->val.bind_sa.v_dual; + } + continue; + case MHD_D_O_LISTEN_SOCKET: + settings->listen_socket = option->val.listen_socket; + continue; + case MHD_D_O_LISTEN_ADDR_REUSE: + settings->listen_addr_reuse = option->val.listen_addr_reuse; + continue; + case MHD_D_O_TCP_FASTOPEN: + settings->tcp_fastopen.v_option = option->val.tcp_fastopen.v_option; + settings->tcp_fastopen.v_queue_length = option->val.tcp_fastopen.v_queue_length; + continue; + case MHD_D_O_LISTEN_BACKLOG: + settings->listen_backlog = option->val.listen_backlog; + continue; + case MHD_D_O_SIGPIPE_SUPPRESSED: + settings->sigpipe_suppressed = option->val.sigpipe_suppressed; + continue; + case MHD_D_O_TLS: + settings->tls = option->val.tls; + continue; + case MHD_D_O_TLS_KEY_CERT: + settings->tls_key_cert.v_mem_key = option->val.tls_key_cert.v_mem_key; + settings->tls_key_cert.v_mem_cert = option->val.tls_key_cert.v_mem_cert; + settings->tls_key_cert.v_mem_pass = option->val.tls_key_cert.v_mem_pass; + continue; + case MHD_D_O_TLS_CLIENT_CA: + settings->tls_client_ca = option->val.tls_client_ca; + continue; + case MHD_D_O_TLS_PSK_CALLBACK: + settings->tls_psk_callback.v_psk_cb = option->val.tls_psk_callback.v_psk_cb; + settings->tls_psk_callback.v_psk_cb_cls = option->val.tls_psk_callback.v_psk_cb_cls; + continue; + case MHD_D_O_NO_ALPN: + settings->no_alpn = option->val.no_alpn; + continue; + case MHD_D_O_DEFAULT_TIMEOUT: + settings->default_timeout = option->val.default_timeout; + continue; + case MHD_D_O_GLOBAL_CONNECTION_LIMIT: + settings->global_connection_limit = option->val.global_connection_limit; + continue; + case MHD_D_O_PER_IP_LIMIT: + settings->per_ip_limit = option->val.per_ip_limit; + continue; + case MHD_D_O_ACCEPT_POLICY: + settings->accept_policy.v_apc = option->val.accept_policy.v_apc; + settings->accept_policy.v_apc_cls = option->val.accept_policy.v_apc_cls; + continue; + case MHD_D_O_PROTOCOL_STRICT_LEVEL: + settings->protocol_strict_level.v_sl = option->val.protocol_strict_level.v_sl; + settings->protocol_strict_level.v_how = option->val.protocol_strict_level.v_how; + continue; + case MHD_D_O_EARLY_URI_LOGGER: + settings->early_uri_logger.v_cb = option->val.early_uri_logger.v_cb; + settings->early_uri_logger.v_cls = option->val.early_uri_logger.v_cls; + continue; + case MHD_D_O_DISABLE_URI_QUERY_PLUS_AS_SPACE: + settings->disable_uri_query_plus_as_space = option->val.disable_uri_query_plus_as_space; + continue; + case MHD_D_O_SUPPRESS_DATE_HEADER: + settings->suppress_date_header = option->val.suppress_date_header; + continue; + case MHD_D_O_ENABLE_SHOUTCAST: + settings->enable_shoutcast = option->val.enable_shoutcast; + continue; + case MHD_D_O_CONN_MEMORY_LIMIT: + settings->conn_memory_limit = option->val.conn_memory_limit; + continue; + case MHD_D_O_LARGE_POOL_SIZE: + settings->large_pool_size = option->val.large_pool_size; + continue; + case MHD_D_O_STACK_SIZE: + settings->stack_size = option->val.stack_size; + continue; + case MHD_D_O_FD_NUMBER_LIMIT: + settings->fd_number_limit = option->val.fd_number_limit; + continue; + case MHD_D_O_TURBO: + settings->turbo = option->val.turbo; + continue; + case MHD_D_O_DISABLE_THREAD_SAFETY: + settings->disable_thread_safety = option->val.disable_thread_safety; + continue; + case MHD_D_O_DISALLOW_UPGRADE: + settings->disallow_upgrade = option->val.disallow_upgrade; + continue; + case MHD_D_O_DISALLOW_SUSPEND_RESUME: + settings->disallow_suspend_resume = option->val.disallow_suspend_resume; + continue; + case MHD_D_O_DAEMON_READY_CALLBACK: + settings->daemon_ready_callback.v_cb = option->val.daemon_ready_callback.v_cb; + settings->daemon_ready_callback.v_cb_cls = option->val.daemon_ready_callback.v_cb_cls; + continue; + case MHD_D_O_NOTIFY_CONNECTION: + settings->notify_connection.v_ncc = option->val.notify_connection.v_ncc; + settings->notify_connection.v_cls = option->val.notify_connection.v_cls; + continue; + case MHD_D_O_NOTIFY_STREAM: + settings->notify_stream.v_nsc = option->val.notify_stream.v_nsc; + settings->notify_stream.v_cls = option->val.notify_stream.v_cls; + continue; + case MHD_D_O_RANDOM_ENTROPY: + /* custom setter */ + /* The is not an easy for automatic generations */ + if (0 != option->val.random_entropy.v_buf_size) + { + if (NULL != settings->random_entropy.v_buf) + free (settings->random_entropy.v_buf); + settings->random_entropy.v_buf = + malloc (option->val.random_entropy.v_buf_size); + if (NULL == settings->random_entropy.v_buf) + return MHD_SC_DAEMON_MALLOC_FAILURE; + memcpy (settings->random_entropy.v_buf, + option->val.random_entropy.v_buf, + option->val.random_entropy.v_buf_size); + settings->random_entropy.v_buf_size = + option->val.random_entropy.v_buf_size; + } + continue; + case MHD_D_O_DAUTH_MAP_SIZE: + settings->dauth_map_size = option->val.dauth_map_size; + continue; + case MHD_D_O_DAUTH_NONCE_BIND_TYPE: + settings->dauth_nonce_bind_type = option->val.dauth_nonce_bind_type; + continue; + case MHD_D_O_DAUTH_DEF_NONCE_TIMEOUT: + settings->dauth_def_nonce_timeout = option->val.dauth_def_nonce_timeout; + continue; + case MHD_D_O_DAUTH_DEF_MAX_NC: + settings->dauth_def_max_nc = option->val.dauth_def_max_nc; + continue; + case MHD_D_O_SENTINEL: + default: /* for -Wswitch-default -Wswitch-enum */ + res = MHD_SC_OPTION_UNKNOWN; + i = options_max_num - 1; + break; + } + } + return res; +} diff --git a/src/mhd2/daemon_start.c b/src/mhd2/daemon_start.c @@ -0,0 +1,2868 @@ +/* + 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/daemon_start.c + * @brief The implementation of the MHD_daemon_start() + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "sys_malloc.h" + +#include <string.h> +#include "sys_sockets_types.h" +#include "sys_sockets_headers.h" +#include "mhd_sockets_macros.h" +#include "sys_ip_headers.h" + +#ifdef MHD_POSIX_SOCKETS +# include "sys_errno.h" +#endif +#ifdef MHD_USE_EPOLL +# include <sys/epoll.h> +#endif + +#ifdef MHD_POSIX_SOCKETS +# include <fcntl.h> +# ifdef MHD_USE_SELECT +# ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> /* For FD_SETSIZE */ +# else +# ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +# endif +# ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +# ifdef HAVE_UNISTD_H +# include <unistd.h> +# endif +# endif +# endif +#endif + +#include "mhd_limits.h" + +#include "mhd_daemon.h" +#include "daemon_options.h" + +#include "mhd_assert.h" +#include "mhd_sockets_funcs.h" +#include "daemon_logger.h" + +#ifdef MHD_USE_THREADS +# include "mhd_itc.h" +# include "mhd_threads.h" +# include "events_process.h" +# include "daemon_funcs.h" +#endif + +#include "mhd_public_api.h" + + +/** + * The default value for fastopen queue length (currently GNU/Linux only) + */ +#define MHD_TCP_FASTOPEN_DEF_QUEUE_LEN 64 + +/** + * Release any internally allocated pointers, then deallocate the settings. + * @param s the pointer to the settings to release + */ +static void +dsettings_release (struct DaemonOptions *s) +{ + /* Release starting from the last member */ + if (NULL != s->random_entropy.v_buf) + free (s->random_entropy.v_buf); + if (MHD_INVALID_SOCKET != s->listen_socket) + mhd_socket_close (s->listen_socket); + if (NULL != s->bind_sa.v_sa) + free (s->bind_sa.v_sa); + free (s); +} + + +/** + * Set the daemon work mode and perform some related checks. + * @param d the daemon object + * @param s the user settings + * @return MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_set_work_mode (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + switch (s->work_mode.mode) + { + case MHD_WM_EXTERNAL_PERIODIC: + d->wmode_int = mhd_WM_INT_INTERNAL_EVENTS_NO_THREADS; + break; + case MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL: + case MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE: + if (MHD_SPS_AUTO != s->poll_syscall) + { + mhd_LOG_MSG ( \ + d, MHD_SC_SYSCALL_WORK_MODE_COMBINATION_INVALID, \ + "The requested work mode is not compatible with setting " \ + "socket polling syscall."); + return MHD_SC_SYSCALL_WORK_MODE_COMBINATION_INVALID; + } + if (MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL == s->work_mode.mode) + d->wmode_int = mhd_WM_INT_EXTERNAL_EVENTS_LEVEL; + else + d->wmode_int = mhd_WM_INT_EXTERNAL_EVENTS_EDGE; + break; + case MHD_WM_EXTERNAL_SINGLE_FD_WATCH: + if ((MHD_SPS_AUTO != s->poll_syscall) && + (MHD_SPS_EPOLL != s->poll_syscall)) + { + mhd_LOG_MSG ( \ + d, MHD_SC_SYSCALL_WORK_MODE_COMBINATION_INVALID, \ + "The requested work mode MHD_WM_EXTERNAL_SINGLE_FD_WATCH " \ + "is not compatible with requested socket polling syscall."); + return MHD_SC_SYSCALL_WORK_MODE_COMBINATION_INVALID; + } +#ifndef MHD_USE_EPOLL + mhd_LOG_MSG ( \ + d, MHD_SC_FEATURE_DISABLED, \ + "The epoll is required for the requested work mode " \ + "MHD_WM_EXTERNAL_SINGLE_FD_WATCH, but not available on this " \ + "platform or MHD build."); + return MHD_SC_FEATURE_DISABLED; +#else + d->wmode_int = mhd_WM_INT_INTERNAL_EVENTS_NO_THREADS; +#endif + break; + case MHD_WM_THREAD_PER_CONNECTION: + if (MHD_SPS_EPOLL == s->poll_syscall) + { + mhd_LOG_MSG ( \ + d, MHD_SC_SYSCALL_WORK_MODE_COMBINATION_INVALID, \ + "The requested work mode MHD_WM_THREAD_PER_CONNECTION " \ + "is not compatible with 'epoll' sockets polling."); + return MHD_SC_SYSCALL_WORK_MODE_COMBINATION_INVALID; + } + /* Intentional fallthrough */ + case MHD_WM_WORKER_THREADS: +#ifndef MHD_USE_THREADS + mhd_LOG_MSG (d, MHD_SC_FEATURE_DISABLED, \ + "The internal threads modes are not supported by this " \ + "build of MHD."); + return MHD_SC_FEATURE_DISABLED; +#else /* MHD_USE_THREADS */ + if (MHD_WM_THREAD_PER_CONNECTION == s->work_mode.mode) + d->wmode_int = mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION; + else if (1 >= s->work_mode.params.num_worker_threads) /* && (MHD_WM_WORKER_THREADS == s->work_mode.mode) */ + d->wmode_int = mhd_WM_INT_INTERNAL_EVENTS_ONE_THREAD; + else + d->wmode_int = mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL; +#endif /* MHD_USE_THREADS */ + break; + default: + mhd_LOG_MSG (d, MHD_SC_CONFIGURATION_UNEXPECTED_WM, \ + "Wrong requested work mode."); + return MHD_SC_CONFIGURATION_UNEXPECTED_WM; + } + + return MHD_SC_OK; +} + + +union mhd_SockaddrAny +{ + struct sockaddr sa; + struct sockaddr_in sa_i4; +#ifdef HAVE_INET6 + struct sockaddr_in6 sa_i6; +#endif /* HAVE_INET6 */ + struct sockaddr_storage sa_stor; +}; + + +/** + * The type of the socket to create + */ +enum mhd_CreateSktType +{ + /** + * Unknown address family (could be IP or not IP) + */ + mhd_SKT_UNKNOWN = -4 + , + /** + * The socket is not IP. + */ + mhd_SKT_NON_IP = -2 + , + /** + * The socket is UNIX. + */ + mhd_SKT_UNIX = -1 + , + /** + * No socket + */ + mhd_SKT_NO_SOCKET = MHD_AF_NONE + , + /** + * IPv4 only + */ + mhd_SKT_IP_V4_ONLY = MHD_AF_INET4 + , + /** + * IPv6 only + */ + mhd_SKT_IP_V6_ONLY = MHD_AF_INET6 + , + /** + * IPv6 with dual stack enabled + */ + mhd_SKT_IP_DUAL_REQUIRED = MHD_AF_DUAL + , + /** + * Try IPv6 with dual stack then IPv4 + */ + mhd_SKT_IP_V4_WITH_V6_OPT = MHD_AF_DUAL_v6_OPTIONAL + , + /** + * IPv6 with optional dual stack + */ + mhd_SKT_IP_V6_WITH_V4_OPT = MHD_AF_DUAL_v4_OPTIONAL + , + /** + * Try IPv4 then IPv6 with optional dual stack + */ + mhd_SKT_IP_V4_WITH_FALLBACK = 16 +}; + +/** + * Create socket, bind to the address and start listening on the socket. + * + * The socket is assigned to the daemon as listening FD. + * @param d the daemon to use + * @param s the user settings + * @param v6_tried true if IPv6 has been tried already + * @param force_v6_any_dual true if IPv6 is forced with dual stack either + * enabled or not + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static enum MHD_StatusCode +create_bind_listen_stream_socket (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s, + bool v6_tried, + bool force_v6_any_dual) +{ + MHD_Socket sk; + enum mhd_CreateSktType sk_type; + bool sk_already_listening; + union mhd_SockaddrAny sa_all; + const struct sockaddr *p_use_sa; + socklen_t use_sa_size; + uint_least16_t sk_port; + bool is_non_block; + bool is_non_inhr; + enum MHD_StatusCode ret; + + sk = MHD_INVALID_SOCKET; + sk_type = mhd_SKT_NO_SOCKET; + sk_already_listening = false; + p_use_sa = NULL; + use_sa_size = 0; + sk_port = 0; + +#ifndef HAVE_INET6 + mhd_assert (! v6_tried); + mhd_assert (! force_v6_any_dual); +#endif + + if (MHD_INVALID_SOCKET != s->listen_socket) + { + mhd_assert (! v6_tried); + mhd_assert (! force_v6_any_dual); + /* Check for options conflicts */ + if (0 != s->bind_sa.v_sa_len) + { + mhd_LOG_MSG (d, MHD_SC_OPTIONS_CONFLICT, \ + "MHD_D_O_BIND_SA cannot be used together " \ + "with MHD_D_O_LISTEN_SOCKET"); + return MHD_SC_OPTIONS_CONFLICT; + } + else if (MHD_AF_NONE != s->bind_port.v_af) + { + mhd_LOG_MSG (d, MHD_SC_OPTIONS_CONFLICT, \ + "MHD_D_O_BIND_PORT cannot be used together " \ + "with MHD_D_O_LISTEN_SOCKET"); + return MHD_SC_OPTIONS_CONFLICT; + } + + /* No options conflicts */ + sk = s->listen_socket; + s->listen_socket = MHD_INVALID_SOCKET; /* Prevent closing with settings cleanup */ + sk_type = mhd_SKT_UNKNOWN; + sk_already_listening = true; + } + else if ((0 != s->bind_sa.v_sa_len) || (MHD_AF_NONE != s->bind_port.v_af)) + { + if (0 != s->bind_sa.v_sa_len) + { + mhd_assert (! v6_tried); + mhd_assert (! force_v6_any_dual); + + /* Check for options conflicts */ + if (MHD_AF_NONE != s->bind_port.v_af) + { + mhd_LOG_MSG (d, MHD_SC_OPTIONS_CONFLICT, \ + "MHD_D_O_BIND_SA cannot be used together " \ + "with MHD_D_O_BIND_PORT"); + return MHD_SC_OPTIONS_CONFLICT; + } + + /* No options conflicts */ + switch (s->bind_sa.v_sa->sa_family) + { + case AF_INET: + sk_type = mhd_SKT_IP_V4_ONLY; + if (sizeof(sa_all.sa_i4) > s->bind_sa.v_sa_len) + { + mhd_LOG_MSG (d, MHD_SC_CONFIGURATION_WRONG_SA_SIZE, \ + "The size of the provided sockaddr does not match " + "used address family"); + return MHD_SC_CONFIGURATION_WRONG_SA_SIZE; + } + memcpy (&(sa_all.sa_i4), s->bind_sa.v_sa, sizeof(sa_all.sa_i4)); + sk_port = (uint_least16_t) ntohs (sa_all.sa_i4.sin_port); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_all.sa_i4.sin_len = (socklen_t) sizeof(sa_all.sa_i4); +#endif + p_use_sa = (struct sockaddr *) &(sa_all.sa_i4); + use_sa_size = (socklen_t) sizeof(sa_all.sa_i4); + break; +#ifdef HAVE_INET6 + case AF_INET6: + sk_type = mhd_SKT_IP_V6_ONLY; + if (sizeof(sa_all.sa_i6) > s->bind_sa.v_sa_len) + { + mhd_LOG_MSG (d, MHD_SC_CONFIGURATION_WRONG_SA_SIZE, \ + "The size of the provided sockaddr does not match " + "used address family"); + return MHD_SC_CONFIGURATION_WRONG_SA_SIZE; + } + memcpy (&(sa_all.sa_i6), s->bind_sa.v_sa, s->bind_sa.v_sa_len); + sk_port = (uint_least16_t) ntohs (sa_all.sa_i6.sin6_port); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sa_all.sa_i6.sin6_len = (socklen_t) s->bind_sa.v_sa_len; +#endif + p_use_sa = (struct sockaddr *) &(sa_all.sa_i6); + use_sa_size = (socklen_t) sizeof(sa_all.sa_i6); + break; +#endif /* HAVE_INET6 */ +#ifdef MHD_AF_UNIX + case MHD_AF_UNIX: + sk_type = mhd_SKT_UNIX; + p_use_sa = NULL; /* To be set below */ + break; +#endif /* MHD_AF_UNIX */ + default: + sk_type = mhd_SKT_UNKNOWN; + p_use_sa = NULL; /* To be set below */ + } + + if (s->bind_sa.v_dual) + { + if (mhd_SKT_IP_V6_ONLY != sk_type) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_DUAL_STACK_NOT_SUITABLE, \ + "IP dual stack is not possible for provided sockaddr"); + } +#ifdef HAVE_INET6 + else + { +#ifdef IPV6_V6ONLY // TODO: detect constants declarations in configure + sk_type = mhd_SKT_IP_DUAL_REQUIRED; +#else /* ! IPV6_V6ONLY */ + mhd_LOG_MSG (d, \ + MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_NOT_SUPPORTED, \ + "IP dual stack is not supported by this platform or " \ + "by this MHD build"); +#endif /* ! IPV6_V6ONLY */ + } +#endif /* HAVE_INET6 */ + } + + if (NULL == p_use_sa) + { +#if defined(HAVE_STRUCT_SOCKADDR_SA_LEN) && \ + defined(HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN) + if ((((size_t) s->bind_sa.v_sa->sa_len) != s->bind_sa.v_sa_len) && + (sizeof(sa_all) >= s->bind_sa.v_sa_len)) + { + /* Fix embedded 'sa_len' member if possible */ + memcpy (&sa_all, s->bind_sa.v_sa, s->bind_sa.v_sa_len); + sa_all.sa_stor.ss_len = (socklen_t) s->bind_sa.v_sa_len; + p_use_sa = (const struct sockaddr *) &(sa_all.sa_stor); + } + else +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN && HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN */ + p_use_sa = s->bind_sa.v_sa; + use_sa_size = (socklen_t) s->bind_sa.v_sa_len; + } + } + else /* if (MHD_AF_NONE != s->bind_port.v_af) */ + { + /* No options conflicts */ + switch (s->bind_port.v_af) + { + case MHD_AF_NONE: + mhd_assert (0); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; + case MHD_AF_AUTO: +#ifdef HAVE_INET6 +#ifdef IPV6_V6ONLY // TODO: detect constants declarations in configure + if (force_v6_any_dual) + sk_type = mhd_SKT_IP_V6_WITH_V4_OPT; + else if (v6_tried) + sk_type = mhd_SKT_IP_V4_WITH_FALLBACK; + else + sk_type = mhd_SKT_IP_V4_WITH_V6_OPT; +#else /* ! IPV6_V6ONLY */ + mhd_assert (! v6_tried); + if (force_v6_any_dual) + sk_type = mhd_SKT_IP_V6_ONLY; + else + sk_type = mhd_SKT_IP_V4_WITH_FALLBACK; +#endif /* ! IPV6_V6ONLY */ +#else /* ! HAVE_INET6 */ + sk_type = mhd_SKT_IP_V4_ONLY; +#endif /* ! HAVE_INET6 */ + break; + case MHD_AF_INET4: + mhd_assert (! v6_tried); + mhd_assert (! force_v6_any_dual); + sk_type = mhd_SKT_IP_V4_ONLY; + break; + case MHD_AF_INET6: + mhd_assert (! v6_tried); + mhd_assert (! force_v6_any_dual); +#ifdef HAVE_INET6 + sk_type = mhd_SKT_IP_V6_ONLY; +#else /* ! HAVE_INET6 */ + mhd_LOG_MSG (d, MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD, \ + "IPv6 is not supported by this MHD build or " \ + "by this platform"); + return MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD; +#endif /* ! HAVE_INET6 */ + break; + case MHD_AF_DUAL: + mhd_assert (! v6_tried); + mhd_assert (! force_v6_any_dual); +#ifdef HAVE_INET6 +#ifdef IPV6_V6ONLY // TODO: detect constants declarations in configure + sk_type = mhd_SKT_IP_DUAL_REQUIRED; +#else /* ! IPV6_V6ONLY */ + mhd_LOG_MSG (d, + MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_NOT_SUPPORTED, \ + "IP dual stack is not supported by this platform or " \ + "by this MHD build"); + sk_type = mhd_SKT_IP_V6_ONLY; +#endif /* ! IPV6_V6ONLY */ +#else /* ! HAVE_INET6 */ + mhd_LOG_MSG (d, MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD, \ + "IPv6 is not supported by this MHD build or " \ + "by this platform"); + return MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD; +#endif /* ! HAVE_INET6 */ + break; + case MHD_AF_DUAL_v4_OPTIONAL: + mhd_assert (! v6_tried); + mhd_assert (! force_v6_any_dual); +#ifdef HAVE_INET6 +#ifdef IPV6_V6ONLY // TODO: detect constants declarations in configure + sk_type = mhd_SKT_IP_V6_WITH_V4_OPT; +#else /* ! IPV6_V6ONLY */ + sk_type = mhd_SKT_IP_V6_ONLY; +#endif /* ! IPV6_V6ONLY */ +#else /* ! HAVE_INET6 */ + mhd_LOG_MSG (d, MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD, \ + "IPv6 is not supported by this MHD build or " \ + "by this platform"); + return MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD; +#endif /* ! HAVE_INET6 */ + break; + case MHD_AF_DUAL_v6_OPTIONAL: + mhd_assert (! force_v6_any_dual); +#ifdef HAVE_INET6 +#ifdef IPV6_V6ONLY // TODO: detect constants declarations in configure + sk_type = (! v6_tried) ? + mhd_SKT_IP_V4_WITH_V6_OPT : mhd_SKT_IP_V4_ONLY; +#else /* ! IPV6_V6ONLY */ + mhd_assert (! v6_tried); + sk_type = mhd_SKT_IP_V4_ONLY; +#endif /* ! IPV6_V6ONLY */ +#else /* ! HAVE_INET6 */ + mhd_assert (! v6_tried); + sk_type = mhd_SKT_IP_V4_ONLY; +#endif /* ! HAVE_INET6 */ + break; + default: + mhd_LOG_MSG (d, MHD_SC_AF_NOT_SUPPORTED_BY_BUILD, \ + "Unknown address family specified"); + return MHD_SC_AF_NOT_SUPPORTED_BY_BUILD; + } + + mhd_assert (mhd_SKT_NO_SOCKET < sk_type); + + switch (sk_type) + { + case mhd_SKT_IP_V4_ONLY: + case mhd_SKT_IP_V4_WITH_FALLBACK: + /* Zeroing is not required, but may help on exotic platforms */ + memset (&(sa_all.sa_i4), 0, sizeof(sa_all.sa_i4)); + sa_all.sa_i4.sin_family = AF_INET; + sa_all.sa_i4.sin_port = htons (s->bind_port.v_port); + if (0 == INADDR_ANY) /* Optimised at compile time */ + sa_all.sa_i4.sin_addr.s_addr = INADDR_ANY; + else + sa_all.sa_i4.sin_addr.s_addr = htonl (INADDR_ANY); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_all.sa_i4.sin_len = sizeof (sa_all.sa_i4); +#endif + p_use_sa = (const struct sockaddr *) &(sa_all.sa_i4); + use_sa_size = (socklen_t) sizeof (sa_all.sa_i4); + break; + case mhd_SKT_IP_V6_ONLY: + case mhd_SKT_IP_DUAL_REQUIRED: + case mhd_SKT_IP_V4_WITH_V6_OPT: + case mhd_SKT_IP_V6_WITH_V4_OPT: +#ifdef HAVE_INET6 + if (1) + { +#ifdef IN6ADDR_ANY_INIT + static const struct in6_addr static_in6any = IN6ADDR_ANY_INIT; +#endif + /* Zeroing is required by POSIX */ + memset (&(sa_all.sa_i6), 0, sizeof(sa_all.sa_i6)); + sa_all.sa_i6.sin6_family = AF_INET6; + sa_all.sa_i6.sin6_port = htons (s->bind_port.v_port); +#ifdef IN6ADDR_ANY_INIT /* Optional assignment at the address is all zeros anyway */ + sa_all.sa_i6.sin6_addr = static_in6any; +#endif +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sa_all.sa_i6.sin6_len = sizeof (sa_all.sa_i6); +#endif + p_use_sa = (const struct sockaddr *) &(sa_all.sa_i6); + use_sa_size = (socklen_t) sizeof (sa_all.sa_i6); + } + break; +#endif /* HAVE_INET6 */ + case mhd_SKT_UNKNOWN: + case mhd_SKT_NON_IP: + case mhd_SKT_UNIX: + case mhd_SKT_NO_SOCKET: + default: + mhd_assert (0); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; + } + + sk_port = s->bind_port.v_port; + + } + } + else + { + /* No listen socket */ + d->net.listen.fd = MHD_INVALID_SOCKET; + d->net.listen.type = mhd_SOCKET_TYPE_UNKNOWN; + d->net.listen.non_block = false; + d->net.listen.port = 0; + + return MHD_SC_OK; + } + + mhd_assert (mhd_SKT_NO_SOCKET != sk_type); + mhd_assert ((NULL != p_use_sa) || sk_already_listening); + mhd_assert ((MHD_INVALID_SOCKET == sk) || sk_already_listening); + + if (MHD_INVALID_SOCKET == sk) + { + mhd_assert (NULL != p_use_sa); +#if defined(MHD_WINSOCK_SOCKETS) && defined(WSA_FLAG_NO_HANDLE_INHERIT) + /* May fail before Win7 SP1 */ + sk = WSASocketW (p_use_sa->sa_family, SOCK_STREAM, 0, + NULL, 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); + + if (MHD_INVALID_SOCKET == sk) +#endif /* MHD_WINSOCK_SOCKETS && WSA_FLAG_NO_HANDLE_INHERIT */ + sk = socket (p_use_sa->sa_family, + SOCK_STREAM | mhd_SOCK_NONBLOCK + | mhd_SOCK_CLOEXEC | mhd_SOCK_NOSIGPIPE, 0); + + if (MHD_INVALID_SOCKET == sk) + { + bool is_af_err = mhd_SCKT_LERR_IS_AF (); + + if (is_af_err) + mhd_LOG_MSG (d, MHD_SC_AF_NOT_AVAILABLE, \ + "The requested socket address family is rejected " \ + "by the OS"); + +#ifdef HAVE_INET6 + if (mhd_SKT_IP_V4_WITH_FALLBACK == sk_type) + return create_bind_listen_stream_socket (d, s, v6_tried, true); + if (mhd_SKT_IP_V4_WITH_V6_OPT == sk_type) + return create_bind_listen_stream_socket (d, s, true, false); +#endif /* HAVE_INET6 */ + + if (! is_af_err) + mhd_LOG_MSG (d, MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET, \ + "Failed to open listen socket"); + + return MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET; + } + is_non_block = (0 != mhd_SOCK_NONBLOCK); + is_non_inhr = (0 != mhd_SOCK_CLOEXEC); + } + else + { + is_non_block = false; /* Try to set non-block */ + is_non_inhr = false; /* Try to set non-inheritable */ + } + + /* The listen socket must be closed if error code returned + beyond this point */ + + ret = MHD_SC_OK; + + do + { /* The scope for automatic socket close for error returns */ + if (! mhd_FD_FITS_DAEMON (d,sk)) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_FD_OUTSIDE_OF_SET_RANGE, \ + "The listen FD value is higher than allowed"); + ret = MHD_SC_LISTEN_FD_OUTSIDE_OF_SET_RANGE; + break; + } + + if (! is_non_inhr) + { + if (! mhd_socket_noninheritable (sk)) + mhd_LOG_MSG (d, MHD_SC_LISTEN_SOCKET_NOINHERIT_FAILED, \ + "OS refused to make the listen socket non-inheritable"); + } + + if (! sk_already_listening) + { +#ifdef HAVE_INET6 +#ifdef IPV6_V6ONLY // TODO: detect constants declarations in configure + if ((mhd_SKT_IP_V6_ONLY == sk_type) || + (mhd_SKT_IP_DUAL_REQUIRED == sk_type) || + (mhd_SKT_IP_V4_WITH_V6_OPT == sk_type) || + (mhd_SKT_IP_V6_WITH_V4_OPT == sk_type) || + (mhd_SKT_UNKNOWN == sk_type)) + { + mhd_SCKT_OPT_BOOL no_dual_to_set; + bool use_dual; + + use_dual = ((mhd_SKT_IP_DUAL_REQUIRED == sk_type) || + (mhd_SKT_IP_V4_WITH_V6_OPT == sk_type) || + (mhd_SKT_IP_V6_WITH_V4_OPT == sk_type)); + no_dual_to_set = use_dual ? 0 : 1; + + if (0 != setsockopt (sk, IPPROTO_IPV6, IPV6_V6ONLY, + (void *) &no_dual_to_set, sizeof (no_dual_to_set))) + { + mhd_SCKT_OPT_BOOL no_dual_current; + socklen_t opt_size; + bool state_unknown; + bool state_match; + + no_dual_current = 0; + opt_size = sizeof(no_dual_current); + + /* Some platforms forbid setting this options, but allow + reading. */ + if ((0 != getsockopt (sk, IPPROTO_IPV6, IPV6_V6ONLY, + (void*) &no_dual_current, &opt_size)) + || (((socklen_t) sizeof(no_dual_current)) < opt_size)) + { + state_unknown = true; + state_match = false; + } + else + { + state_unknown = false; + state_match = ((! ! no_dual_current) == (! ! no_dual_to_set)); + } + + if (state_unknown || ! state_match) + { + if (mhd_SKT_IP_V4_WITH_V6_OPT == sk_type) + { + (void) mhd_socket_close (sk); + return create_bind_listen_stream_socket (d, s, true, false); + } + if (! state_unknown) + { + /* The dual-stack state is definitely wrong */ + if (mhd_SKT_IP_V6_ONLY == sk_type) + { + mhd_LOG_MSG ( \ + d, MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_REJECTED, \ + "Failed to disable IP dual-stack configuration " \ + "for the listen socket"); + ret = MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_REJECTED; + break; + } + else if (mhd_SKT_UNKNOWN != sk_type) + { + mhd_LOG_MSG ( \ + d, MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_REJECTED, \ + "Cannot enable IP dual-stack configuration " \ + "for the listen socket"); + if (mhd_SKT_IP_DUAL_REQUIRED == sk_type) + { + ret = MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_REJECTED; + break; + } + } + } + else + { + /* The dual-stack state is unknown */ + if (mhd_SKT_UNKNOWN != sk_type) + mhd_LOG_MSG ( + d, MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_UNKNOWN, \ + "Failed to set dual-stack (IPV6_ONLY) configuration " \ + "for the listen socket, using system defaults"); + } + } + } + } +#else /* ! IPV6_V6ONLY */ + mhd_assert (mhd_SKT_IP_DUAL_REQUIRED != sk_type); + mhd_assert (mhd_SKT_IP_V4_WITH_V6_OPT != sk_type); + mhd_assert (mhd_SKT_IP_V6_WITH_V4_OPT != sk_type); +#endif /* ! IPV6_V6ONLY */ +#endif /* HAVE_INET6 */ + + if (MHD_FOM_AUTO <= d->settings->tcp_fastopen.v_option) + { +#if defined(TCP_FASTOPEN) + int fo_param; +#ifdef __linux__ + /* The parameter is the queue length */ + fo_param = (int) d->settings->tcp_fastopen.v_queue_length; + if (0 == fo_param) + fo_param = MHD_TCP_FASTOPEN_DEF_QUEUE_LEN; +#else /* ! __linux__ */ + fo_param = 1; /* The parameter is on/off type of setting */ +#endif /* ! __linux__ */ + if (0 != setsockopt (sk, IPPROTO_TCP, TCP_FASTOPEN, + (const void *) &fo_param, + sizeof (fo_param))) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_FAST_OPEN_FAILURE, \ + "OS refused to enable TCP Fast Open on " \ + "the listen socket"); + if (MHD_FOM_AUTO < d->settings->tcp_fastopen.v_option) + { + ret = MHD_SC_LISTEN_FAST_OPEN_FAILURE; + break; + } + } +#else /* ! TCP_FASTOPEN */ + if (MHD_FOM_AUTO < d->settings->tcp_fastopen.v_option) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_FAST_OPEN_FAILURE, \ + "The OS does not support TCP Fast Open"); + ret = MHD_SC_LISTEN_FAST_OPEN_FAILURE; + break; + } +#endif + } + + if (MHD_D_OPTION_BIND_TYPE_NOT_SHARED >= d->settings->listen_addr_reuse) + { +#ifndef MHD_WINSOCK_SOCKETS +#ifdef SO_REUSEADDR + mhd_SCKT_OPT_BOOL on_val1 = 1; + if (0 != setsockopt (sk, SOL_SOCKET, SO_REUSEADDR, + (const void *) &on_val1, sizeof (on_val1))) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_PORT_REUSE_ENABLE_FAILED, \ + "OS refused to enable address reuse on " \ + "the listen socket"); + } +#else /* ! SO_REUSEADDR */ + mhd_LOG_MSG (d, MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED, \ + "The OS does not support address reuse for sockets"); +#endif /* ! SO_REUSEADDR */ +#endif /* ! MHD_WINSOCK_SOCKETS */ + if (MHD_D_OPTION_BIND_TYPE_NOT_SHARED > d->settings->listen_addr_reuse) + { +#if defined(SO_REUSEPORT) || defined(MHD_WINSOCK_SOCKETS) + mhd_SCKT_OPT_BOOL on_val2 = 1; + if (0 != setsockopt (sk, SOL_SOCKET, +#ifndef MHD_WINSOCK_SOCKETS + SO_REUSEPORT, +#else /* ! MHD_WINSOCK_SOCKETS */ + SO_REUSEADDR, /* On W32 it is the same as SO_REUSEPORT on other platforms */ +#endif /* ! MHD_WINSOCK_SOCKETS */ + (const void *) &on_val2, sizeof (on_val2))) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED, \ + "OS refused to enable address sharing " \ + "on the listen socket"); + ret = MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED; + break; + } +#else /* ! SO_REUSEADDR && ! MHD_WINSOCK_SOCKETS */ + mhd_LOG_MSG (d, MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED, \ + "The OS does not support address sharing for sockets"); + ret = MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED; + break; +#endif /* ! SO_REUSEADDR && ! MHD_WINSOCK_SOCKETS */ + } + } +#if defined(SO_EXCLUSIVEADDRUSE) || defined(SO_EXCLBIND) + else if (MHD_D_OPTION_BIND_TYPE_EXCLUSIVE <= + d->settings->listen_addr_reuse) + { + mhd_SCKT_OPT_BOOL on_val = 1; + if (0 != setsockopt (sk, SOL_SOCKET, +#ifdef SO_EXCLUSIVEADDRUSE + SO_EXCLUSIVEADDRUSE, +#else + SO_EXCLBIND, +#endif + (const void *) &on_val, sizeof (on_val))) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_ADDRESS_EXCLUSIVE_ENABLE_FAILED, \ + "OS refused to enable exclusive address use " \ + "on the listen socket"); + ret = MHD_SC_LISTEN_ADDRESS_EXCLUSIVE_ENABLE_FAILED; + break; + } + } +#endif /* SO_EXCLUSIVEADDRUSE || SO_EXCLBIND */ + + mhd_assert (NULL != p_use_sa); + mhd_assert (0 != use_sa_size); + if (0 != bind (sk, p_use_sa, use_sa_size)) + { +#ifdef HAVE_INET6 + if (mhd_SKT_IP_V4_WITH_FALLBACK == sk_type) + { + (void) mhd_socket_close (sk); + return create_bind_listen_stream_socket (d, s, v6_tried, true); + } + if (mhd_SKT_IP_V4_WITH_V6_OPT == sk_type) + { + (void) mhd_socket_close (sk); + return create_bind_listen_stream_socket (d, s, true, false); + } +#endif /* HAVE_INET6 */ + mhd_LOG_MSG (d, MHD_SC_LISTEN_SOCKET_BIND_FAILED, \ + "Failed to bind the listen socket"); + ret = MHD_SC_LISTEN_SOCKET_BIND_FAILED; + break; + } + + if (1) + { + int accept_queue_len; + accept_queue_len = (int) s->listen_backlog; + if (0 > accept_queue_len) + accept_queue_len = 0; + if (0 == accept_queue_len) + { +#ifdef SOMAXCONN + accept_queue_len = SOMAXCONN; +#else /* ! SOMAXCONN */ + accept_queue_len = 127; /* Should be the safe value */ +#endif /* ! SOMAXCONN */ + } + if (0 != listen (sk, accept_queue_len)) + { +#ifdef HAVE_INET6 + if (mhd_SKT_IP_V4_WITH_FALLBACK == sk_type) + { + (void) mhd_socket_close (sk); + return create_bind_listen_stream_socket (d, s, v6_tried, true); + } + if (mhd_SKT_IP_V4_WITH_V6_OPT == sk_type) + { + (void) mhd_socket_close (sk); + return create_bind_listen_stream_socket (d, s, true, false); + } +#endif /* HAVE_INET6 */ + mhd_LOG_MSG (d, MHD_SC_LISTEN_FAILURE, \ + "Failed to start listening on the listen socket"); + ret = MHD_SC_LISTEN_FAILURE; + break; + } + } + } + /* A valid listening socket is ready here */ + + if (! is_non_block) + { + is_non_block = mhd_socket_nonblocking (sk); + if (! is_non_block) + mhd_LOG_MSG (d, MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE, \ + "OS refused to make the listen socket non-blocking"); + } + + /* Set to the daemon only when the listening socket is fully ready */ + d->net.listen.fd = sk; + switch (sk_type) + { + case mhd_SKT_UNKNOWN: + d->net.listen.type = mhd_SOCKET_TYPE_UNKNOWN; + break; + case mhd_SKT_NON_IP: + d->net.listen.type = mhd_SOCKET_TYPE_NON_IP; + break; + case mhd_SKT_UNIX: + d->net.listen.type = mhd_SOCKET_TYPE_UNIX; + break; + case mhd_SKT_IP_V4_ONLY: + case mhd_SKT_IP_V6_ONLY: + case mhd_SKT_IP_DUAL_REQUIRED: + case mhd_SKT_IP_V4_WITH_V6_OPT: + case mhd_SKT_IP_V6_WITH_V4_OPT: + case mhd_SKT_IP_V4_WITH_FALLBACK: + d->net.listen.type = mhd_SOCKET_TYPE_IP; + break; + case mhd_SKT_NO_SOCKET: + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; + } + d->net.listen.non_block = is_non_block; + d->net.listen.port = sk_port; + + mhd_assert (ret == MHD_SC_OK); + + return MHD_SC_OK; + + } while (0); + + mhd_assert (MHD_SC_OK != ret); /* This should be only error returns here */ + mhd_assert (MHD_INVALID_SOCKET != sk); + (void) mhd_socket_close (sk); + return ret; +} + + +/** + * Detect and set the type and port of the listening socket + * @param d the daemon to use + */ +static MHD_FN_PAR_NONNULL_ (1) void +detect_listen_type_and_port (struct MHD_Daemon *restrict d) +{ + union mhd_SockaddrAny sa_all; + socklen_t sa_size; + enum mhd_SocketType declared_type; + + mhd_assert (MHD_INVALID_SOCKET != d->net.listen.fd); + mhd_assert (0 == d->net.listen.port); + memset (&sa_all, 0, sizeof(sa_all)); /* Actually not required */ + sa_size = (socklen_t) sizeof(sa_all); + + if (0 != getsockname (d->net.listen.fd, &(sa_all.sa), &sa_size)) + { + if (mhd_SOCKET_TYPE_IP == d->net.listen.type) + mhd_LOG_MSG (d, MHD_SC_LISTEN_PORT_DETECT_FAILURE, \ + "Failed to detect the port number on the listening socket"); + return; + } + + declared_type = d->net.listen.type; + if (0 == sa_size) + { +#ifndef __linux__ + /* Used on some non-Linux platforms */ + d->net.listen.type = mhd_SOCKET_TYPE_UNIX; + d->net.listen.port = 0; +#else /* ! __linux__ */ + (void) 0; +#endif /* ! __linux__ */ + } + else + { + switch (sa_all.sa.sa_family) + { + case AF_INET: + d->net.listen.type = mhd_SOCKET_TYPE_IP; + d->net.listen.port = (uint_least16_t) ntohs (sa_all.sa_i4.sin_port); + break; +#ifdef HAVE_INET6 + case AF_INET6: + d->net.listen.type = mhd_SOCKET_TYPE_IP; + d->net.listen.port = (uint_least16_t) ntohs (sa_all.sa_i6.sin6_port); + break; +#endif /* HAVE_INET6 */ +#ifdef MHD_AF_UNIX + case MHD_AF_UNIX: + d->net.listen.type = mhd_SOCKET_TYPE_UNIX; + d->net.listen.port = 0; + break; +#endif /* MHD_AF_UNIX */ + default: + d->net.listen.type = mhd_SOCKET_TYPE_UNKNOWN; + d->net.listen.port = 0; + break; + } + } + + if ((declared_type != d->net.listen.type) + && (mhd_SOCKET_TYPE_IP == declared_type)) + mhd_LOG_MSG (d, MHD_SC_UNEXPECTED_SOCKET_ERROR, \ + "The type of listen socket is detected as non-IP, while " \ + "the socket has been created as an IP socket"); +} + + +#ifdef MHD_USE_EPOLL + +/** + * Initialise daemon's epoll FD + */ +static MHD_FN_PAR_NONNULL_ (1) enum MHD_StatusCode +init_epoll (struct MHD_Daemon *restrict d) +{ + int e_fd; + mhd_assert (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != d->wmode_int); + mhd_assert ((mhd_POLL_TYPE_NOT_SET_YET == d->events.poll_type) || \ + ((mhd_POLL_TYPE_EPOLL == d->events.poll_type) && \ + (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int))); + mhd_assert ((! d->dbg.net_inited) || \ + (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int)); + mhd_assert ((mhd_POLL_TYPE_EPOLL != d->events.poll_type) || \ + (NULL == d->events.data.epoll.events)); + mhd_assert ((mhd_POLL_TYPE_EPOLL != d->events.poll_type) || \ + (MHD_INVALID_SOCKET == d->events.data.epoll.e_fd)); +#ifdef HAVE_EPOLL_CREATE1 + e_fd = epoll_create1 (EPOLL_CLOEXEC); +#else /* ! HAVE_EPOLL_CREATE1 */ + e_fd = epoll_create (128); /* The number is usually ignored */ + if (0 <= e_fd) + { + if (! mhd_socket_noninheritable (e_fd)) + mhd_LOG_MSG (d, MHD_SC_EPOLL_CTL_CONFIGURE_NOINHERIT_FAILED, \ + "Failed to make epoll control FD non-inheritable"); + } +#endif /* ! HAVE_EPOLL_CREATE1 */ + if (0 > e_fd) + { + mhd_LOG_MSG (d, MHD_SC_EPOLL_CTL_CREATE_FAILED, \ + "Failed to create epoll control FD"); + return MHD_SC_EPOLL_CTL_CREATE_FAILED; /* Failure exit point */ + } + + if (! mhd_FD_FITS_DAEMON (d, e_fd)) + { + mhd_LOG_MSG (d, MHD_SC_EPOLL_CTL_OUTSIDE_OF_SET_RANGE, \ + "The epoll control FD value is higher than allowed"); + (void) close (e_fd); + return MHD_SC_EPOLL_CTL_OUTSIDE_OF_SET_RANGE; /* Failure exit point */ + } + + d->events.poll_type = mhd_POLL_TYPE_EPOLL; + d->events.data.epoll.e_fd = e_fd; + d->events.data.epoll.events = NULL; /* Memory allocated during event and threads init */ + d->events.data.epoll.num_elements = 0; + return MHD_SC_OK; /* Success exit point */ +} + + +/** + * Deinitialise daemon's epoll FD + */ +MHD_FN_PAR_NONNULL_ (1) static void +deinit_epoll (struct MHD_Daemon *restrict d) +{ + mhd_assert (mhd_POLL_TYPE_EPOLL == d->events.poll_type); + /* With thread pool the epoll control FD could be migrated to the + * first worker daemon. */ + mhd_assert ((MHD_INVALID_SOCKET != d->events.data.epoll.e_fd) || \ + (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int)); + mhd_assert ((MHD_INVALID_SOCKET != d->events.data.epoll.e_fd) || \ + (mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY == d->threading.d_type)); + if (MHD_INVALID_SOCKET != d->events.data.epoll.e_fd) + close (d->events.data.epoll.e_fd); +} + + +#endif /* MHD_USE_EPOLL */ + +/** + * Choose sockets monitoring syscall and pre-initialise it + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) \ + MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_choose_and_preinit_events (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + enum mhd_IntPollType chosen_type; + + mhd_assert ((mhd_POLL_TYPE_NOT_SET_YET == d->events.poll_type) || \ + (mhd_WM_INT_EXTERNAL_EVENTS_EDGE == d->wmode_int) || \ + (mhd_WM_INT_EXTERNAL_EVENTS_LEVEL == d->wmode_int) || \ + (MHD_WM_EXTERNAL_SINGLE_FD_WATCH == s->work_mode.mode)); + mhd_assert ((mhd_POLL_TYPE_NOT_SET_YET == d->events.poll_type) || \ + (d->events.poll_type == (enum mhd_IntPollType) s->poll_syscall) \ + || ((MHD_SPS_AUTO == s->poll_syscall) && \ + ((mhd_POLL_TYPE_EXT == d->events.poll_type) || \ + (mhd_POLL_TYPE_EPOLL == d->events.poll_type)))); + + /* Check whether the provided parameter is in the range of expected values */ + switch (s->poll_syscall) + { + case MHD_SPS_AUTO: + chosen_type = mhd_POLL_TYPE_NOT_SET_YET; + break; + case MHD_SPS_SELECT: + mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); +#ifndef MHD_USE_SELECT + mhd_LOG_MSG (d, MHD_SC_SELECT_SYSCALL_NOT_AVAILABLE, \ + "'select()' is not supported by the platform or " \ + "this MHD build"); + return MHD_SC_SELECT_SYSCALL_NOT_AVAILABLE; +#else /* MHD_USE_SELECT */ + chosen_type = mhd_POLL_TYPE_SELECT; +#endif /* MHD_USE_SELECT */ + break; + case MHD_SPS_POLL: + mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); +#ifndef MHD_USE_POLL + mhd_LOG_MSG (d, MHD_SC_POLL_SYSCALL_NOT_AVAILABLE, \ + "'poll()' is not supported by the platform or " \ + "this MHD build"); + return MHD_SC_POLL_SYSCALL_NOT_AVAILABLE; +#else /* MHD_USE_POLL */ + chosen_type = mhd_POLL_TYPE_POLL; +#endif /* MHD_USE_POLL */ + break; + case MHD_SPS_EPOLL: + mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); +#ifndef MHD_USE_EPOLL + mhd_LOG_MSG (d, MHD_SC_EPOLL_SYSCALL_NOT_AVAILABLE, \ + "'epoll' is not supported by the platform or " \ + "this MHD build"); + return MHD_SC_EPOLL_SYSCALL_NOT_AVAILABLE; +#else /* MHD_USE_EPOLL */ + chosen_type = mhd_POLL_TYPE_EPOLL; +#endif /* MHD_USE_EPOLL */ + break; + default: + mhd_LOG_MSG (d, MHD_SC_CONFIGURATION_UNEXPECTED_SPS, + "Wrong socket polling syscall specified"); + return MHD_SC_CONFIGURATION_UNEXPECTED_SPS; + } + + mhd_assert (mhd_POLL_TYPE_EXT != chosen_type); + + if (mhd_POLL_TYPE_NOT_SET_YET == chosen_type) + { + if (mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)) + chosen_type = mhd_POLL_TYPE_EXT; +#ifdef MHD_USE_EPOLL + else if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != d->wmode_int) + chosen_type = mhd_POLL_TYPE_EPOLL; /* with possible fallback */ +#endif + else + { +#if defined(MHD_USE_POLL) + chosen_type = mhd_POLL_TYPE_POLL; +#elif defined(MHD_USE_SELECT) + chosen_type = mhd_POLL_TYPE_SELECT; +#else + (void) 0; /* Do nothing. Mute compiler warning */ +#endif + } + } + + /* Try 'epoll' if possible */ +#ifdef MHD_USE_EPOLL + if (mhd_POLL_TYPE_EPOLL == chosen_type) + { + enum MHD_StatusCode epoll_res; + + mhd_assert (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != \ + d->wmode_int); + epoll_res = init_epoll (d); + + if (MHD_SC_OK != epoll_res) + { + if ((MHD_SPS_EPOLL == s->poll_syscall) || + (MHD_WM_EXTERNAL_SINGLE_FD_WATCH == s->work_mode.mode)) + return epoll_res; /* Cannot init epoll, but epoll is required */ + chosen_type = mhd_POLL_TYPE_NOT_SET_YET; /* Choose again */ + } + } + mhd_assert ((mhd_POLL_TYPE_EPOLL != d->events.poll_type) || \ + (0 < d->events.data.epoll.e_fd)); +#endif /* MHD_USE_EPOLL */ + + if (mhd_POLL_TYPE_NOT_SET_YET == chosen_type) + { +#if defined(MHD_USE_POLL) + chosen_type = mhd_POLL_TYPE_POLL; +#elif defined(MHD_USE_SELECT) + chosen_type = mhd_POLL_TYPE_SELECT; +#else + mhd_LOG_MSG (d, MHD_SC_FEATURE_DISABLED, \ + "All suitable internal sockets polling technologies are " \ + "disabled in this MHD build"); + return MHD_SC_FEATURE_DISABLED; +#endif + } + + switch (chosen_type) + { + case mhd_POLL_TYPE_EXT: + mhd_assert ((MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL == s->work_mode.mode) || \ + (MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE != s->work_mode.mode)); + mhd_assert (mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + mhd_assert (MHD_WM_EXTERNAL_SINGLE_FD_WATCH != s->work_mode.mode); + d->events.poll_type = mhd_POLL_TYPE_EXT; + d->events.data.ext.cb = + s->work_mode.params.v_external_event_loop_cb.reg_cb; + d->events.data.ext.cls = + s->work_mode.params.v_external_event_loop_cb.reg_cb_cls; + break; +#ifdef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: + mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + mhd_assert (MHD_WM_EXTERNAL_SINGLE_FD_WATCH != s->work_mode.mode); + d->events.poll_type = mhd_POLL_TYPE_SELECT; + d->events.data.select.rfds = NULL; /* Memory allocated during event and threads init */ + d->events.data.select.wfds = NULL; /* Memory allocated during event and threads init */ + d->events.data.select.efds = NULL; /* Memory allocated during event and threads init */ + break; +#endif /* MHD_USE_SELECT */ +#ifdef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: + mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + mhd_assert (MHD_WM_EXTERNAL_SINGLE_FD_WATCH != s->work_mode.mode); + d->events.poll_type = mhd_POLL_TYPE_POLL; + d->events.data.poll.fds = NULL; /* Memory allocated during event and threads init */ + d->events.data.poll.rel = NULL; /* Memory allocated during event and threads init */ + break; +#endif /* MHD_USE_POLL */ +#ifdef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: + mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + /* Pre-initialised by init_epoll() */ + mhd_assert (mhd_POLL_TYPE_EPOLL == d->events.poll_type); + mhd_assert (0 <= d->events.data.epoll.e_fd); + mhd_assert (NULL == d->events.data.epoll.events); + break; +#endif /* MHD_USE_EPOLL */ +#ifndef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: +#endif /* ! MHD_USE_SELECT */ +#ifndef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: +#endif /* ! MHD_USE_POLL */ +#ifndef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: +#endif /* ! MHD_USE_EPOLL */ + case mhd_POLL_TYPE_NOT_SET_YET: + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; + break; + } + return MHD_SC_OK; +} + + +/** + * Initialise network/sockets for the daemon. + * Also choose events mode / sockets polling syscall. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) \ + MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_init_net (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + enum MHD_StatusCode ret; + + mhd_assert (! d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); +#ifdef MHD_POSIX_SOCKETS + d->net.cfg.max_fd_num = s->fd_number_limit; +#endif /* MHD_POSIX_SOCKETS */ + + ret = daemon_choose_and_preinit_events (d, s); + if (MHD_SC_OK != ret) + return ret; + + mhd_assert (mhd_POLL_TYPE_NOT_SET_YET != d->events.poll_type); + + /* No direct return of error codes is allowed beyond this point. + Deinit/cleanup must be performed before return of any error. */ + +#if defined(MHD_POSIX_SOCKETS) && defined(MHD_USE_SELECT) + if (mhd_POLL_TYPE_SELECT == d->events.poll_type) + { + if ((MHD_INVALID_SOCKET == d->net.cfg.max_fd_num) || + (FD_SETSIZE < d->net.cfg.max_fd_num)) + d->net.cfg.max_fd_num = FD_SETSIZE; + } +#endif /* MHD_POSIX_SOCKETS && MHD_USE_SELECT */ + + if (MHD_SC_OK == ret) + { + ret = create_bind_listen_stream_socket (d, s, false, false); + + if (MHD_SC_OK == ret) + { + if ((MHD_INVALID_SOCKET != d->net.listen.fd) + && ! d->net.listen.non_block + && ((mhd_WM_INT_EXTERNAL_EVENTS_EDGE == d->wmode_int) || + (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int))) + { + mhd_LOG_MSG (d, MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE, \ + "The selected daemon work mode requires listening socket " + "in non-blocking mode"); + ret = MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE; + } + + if (MHD_SC_OK == ret) + { + if ((MHD_INVALID_SOCKET != d->net.listen.fd) && + ((0 == d->net.listen.port) || + (mhd_SOCKET_TYPE_UNKNOWN == d->net.listen.type))) + detect_listen_type_and_port (d); + +#ifndef NDEBUG + d->dbg.net_inited = true; +#endif + return MHD_SC_OK; /* Success exit point */ + } + + /* Below is a cleanup path */ + if (MHD_INVALID_SOCKET != d->net.listen.fd) + mhd_socket_close (d->net.listen.fd); + } + } + +#ifdef MHD_USE_EPOLL + if ((mhd_POLL_TYPE_EPOLL == d->events.poll_type)) + close (d->events.data.epoll.e_fd); +#endif /* MHD_USE_EPOLL */ + + mhd_assert (MHD_SC_OK != ret); + + return ret; +} + + +/** + * Deinitialise daemon's network data + * @param d the daemon object + */ +MHD_FN_PAR_NONNULL_ (1) static void +daemon_deinit_net (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); + mhd_assert (mhd_POLL_TYPE_NOT_SET_YET != d->events.poll_type); +#ifdef MHD_USE_EPOLL + if (mhd_POLL_TYPE_EPOLL == d->events.poll_type) + deinit_epoll (d); +#endif /* MHD_USE_EPOLL */ + if (MHD_INVALID_SOCKET != d->net.listen.fd) + mhd_socket_close (d->net.listen.fd); + +#ifndef NDEBUG + d->dbg.net_deinited = true; +#endif +} + + +#if 0 +void +dauth_init (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + mhd_assert ((NULL == s->random_entropy.v_buf) || \ + (0 != s->random_entropy.v_buf_size)); + mhd_assert ((0 == s->random_entropy.v_buf_size) || \ + (NULL != s->random_entropy.v_buf)); +} + + +#endif + +/** + * Initialise large buffer tracking. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) \ + MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_init_large_buf (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + mhd_assert (! mhd_D_HAS_MASTER (d)); + d->req_cfg.large_buf.space_left = s->large_pool_size; + if (0 == d->req_cfg.large_buf.space_left) // TODO: USE SETTINGS! + d->req_cfg.large_buf.space_left = 1024 * 1024U; // TODO: USE SETTINGS! + if (! mhd_mutex_init_short (&(d->req_cfg.large_buf.lock))) + { + mhd_LOG_MSG (d, MHD_SC_MUTEX_INIT_FAILURE, \ + "Failed to initialise mutex for the global large buffer."); + return MHD_SC_MUTEX_INIT_FAILURE; + } + return MHD_SC_OK; +} + + +/** + * Initialise large buffer tracking. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) void +daemon_deinit_large_buf (struct MHD_Daemon *restrict d) +{ + mhd_mutex_destroy_chk (&(d->req_cfg.large_buf.lock)); +} + + +/** + * Finish initialisation of events processing + * @param d the daemon object + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +allocate_events (struct MHD_Daemon *restrict d) +{ +#if defined(MHD_USE_POLL) || defined(MHD_USE_EPOLL) + /** + * The number of elements to be monitored by sockets polling function + */ + unsigned int num_elements; + num_elements = 0; +#ifdef MHD_USE_THREADS + ++num_elements; /* For the ITC */ +#endif + if (MHD_INVALID_SOCKET != d->net.listen.fd) + ++num_elements; /* For the listening socket */ + if (! mhd_D_HAS_THR_PER_CONN (d)) + num_elements += d->conns.cfg.count_limit; +#endif /* MHD_USE_POLL || MHD_USE_EPOLL */ + + mhd_assert (0 != d->conns.cfg.count_limit); + mhd_assert (mhd_D_TYPE_HAS_EVENTS_PROCESSING (d->threading.d_type)); + + mhd_DLINKEDL_INIT_LIST (&(d->events),proc_ready); + + switch (d->events.poll_type) + { + case mhd_POLL_TYPE_EXT: + mhd_assert (NULL != d->events.data.ext.cb); +#ifndef NDEBUG + d->dbg.events_allocated = true; +#endif + return MHD_SC_OK; /* Success exit point */ + break; +#ifdef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: + /* The pointers have been set to NULL during pre-initialisations of the events */ + mhd_assert (NULL == d->events.data.select.rfds); + mhd_assert (NULL == d->events.data.select.wfds); + mhd_assert (NULL == d->events.data.select.efds); + d->events.data.select.rfds = (fd_set *) malloc (sizeof(fd_set)); + if (NULL != d->events.data.select.rfds) + { + d->events.data.select.wfds = (fd_set *) malloc (sizeof(fd_set)); + if (NULL != d->events.data.select.wfds) + { + d->events.data.select.efds = (fd_set *) malloc (sizeof(fd_set)); + if (NULL != d->events.data.select.efds) + { +#ifndef NDEBUG + d->dbg.num_events_elements = FD_SETSIZE; + d->dbg.events_allocated = true; +#endif + return MHD_SC_OK; /* Success exit point */ + } + + free (d->events.data.select.wfds); + } + free (d->events.data.select.rfds); + } + mhd_LOG_MSG (d, MHD_SC_FD_SET_MEMORY_ALLOCATE_FAILURE, \ + "Failed to allocate memory for fd_sets for the daemon"); + return MHD_SC_FD_SET_MEMORY_ALLOCATE_FAILURE; + break; +#endif /* MHD_USE_SELECT */ +#ifdef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: + /* The pointers have been set to NULL during pre-initialisations of the events */ + mhd_assert (NULL == d->events.data.poll.fds); + mhd_assert (NULL == d->events.data.poll.rel); + if ((num_elements > d->conns.cfg.count_limit) /* Check for value overflow */ + || (mhd_D_HAS_THR_PER_CONN (d))) + { + d->events.data.poll.fds = + (struct pollfd *) malloc (sizeof(struct pollfd) * num_elements); + if (NULL != d->events.data.poll.fds) + { + d->events.data.poll.rel = + (union mhd_SocketRelation *) malloc (sizeof(union mhd_SocketRelation) + * num_elements); + if (NULL != d->events.data.poll.rel) + { +#ifndef NDEBUG + d->dbg.num_events_elements = num_elements; + d->dbg.events_allocated = true; +#endif + return MHD_SC_OK; /* Success exit point */ + } + free (d->events.data.poll.fds); + } + } + mhd_LOG_MSG (d, MHD_SC_POLL_FDS_MEMORY_ALLOCATE_FAILURE, \ + "Failed to allocate memory for poll fds for the daemon"); + return MHD_SC_POLL_FDS_MEMORY_ALLOCATE_FAILURE; + break; +#endif /* MHD_USE_POLL */ +#ifdef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: + mhd_assert (! mhd_D_HAS_THR_PER_CONN (d)); + /* The event FD has been created during pre-initialisations of the events */ + mhd_assert (MHD_INVALID_SOCKET != d->events.data.epoll.e_fd); + /* The pointer has been set to NULL during pre-initialisations of the events */ + mhd_assert (NULL == d->events.data.epoll.events); + mhd_assert (0 == d->events.data.epoll.num_elements); + if ((num_elements > d->conns.cfg.count_limit) /* Check for value overflow */ + || (mhd_D_HAS_THR_PER_CONN (d))) + { + const unsigned int upper_limit = (sizeof(void*) >= 8) ? 4096 : 1024; + + /* Trade neglectable performance penalty for memory saving */ + /* Very large amount of new events processed in batches */ + if (num_elements > upper_limit) + num_elements = upper_limit; + + d->events.data.epoll.events = + (struct epoll_event *) malloc (sizeof(struct epoll_event) + * num_elements); + if (NULL != d->events.data.epoll.events) + { + d->events.data.epoll.num_elements = num_elements; +#ifndef NDEBUG + d->dbg.num_events_elements = num_elements; + d->dbg.events_allocated = true; +#endif + return MHD_SC_OK; /* Success exit point */ + } + } + mhd_LOG_MSG (d, MHD_SC_EPOLL_EVENTS_MEMORY_ALLOCATE_FAILURE, \ + "Failed to allocate memory for epoll events for the daemon"); + return MHD_SC_EPOLL_EVENTS_MEMORY_ALLOCATE_FAILURE; + break; +#endif /* MHD_USE_EPOLL */ +#ifndef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: +#endif /* ! MHD_USE_SELECT */ +#ifndef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: +#endif /* ! MHD_USE_POLL */ +#ifndef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: +#endif /* ! MHD_USE_EPOLL */ + case mhd_POLL_TYPE_NOT_SET_YET: + default: + mhd_assert (0 && "Impossible value"); + } + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; +} + + +/** + * Deallocate events data + * @param d the daemon object + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) void +deallocate_events (struct MHD_Daemon *restrict d) +{ + mhd_assert (0 != d->conns.cfg.count_limit); + mhd_assert (mhd_D_TYPE_HAS_EVENTS_PROCESSING (d->threading.d_type)); + if (mhd_POLL_TYPE_NOT_SET_YET == d->events.poll_type) + { + mhd_assert (0 && "Wrong workflow"); + MHD_UNREACHABLE_; + return; + } +#ifdef MHD_USE_SELECT + else if (mhd_POLL_TYPE_SELECT == d->events.poll_type) + { + mhd_assert (NULL != d->events.data.select.efds); + mhd_assert (NULL != d->events.data.select.wfds); + mhd_assert (NULL != d->events.data.select.rfds); + free (d->events.data.select.efds); + free (d->events.data.select.wfds); + free (d->events.data.select.rfds); + } +#endif /* MHD_USE_SELECT */ +#ifdef MHD_USE_POLL + else if (mhd_POLL_TYPE_POLL == d->events.poll_type) + { + mhd_assert (NULL != d->events.data.poll.rel); + mhd_assert (NULL != d->events.data.poll.fds); + free (d->events.data.poll.rel); + free (d->events.data.poll.fds); + } +#endif /* MHD_USE_POLL */ +#ifdef MHD_USE_EPOLL + else if (mhd_POLL_TYPE_EPOLL == d->events.poll_type) + { + mhd_assert (0 != d->events.data.epoll.num_elements); + mhd_assert (NULL != d->events.data.epoll.events); + free (d->events.data.epoll.events); + } +#endif /* MHD_USE_EPOLL */ +#ifndef NDEBUG + d->dbg.events_allocated = false; +#endif + return; +} + + +/** + * Initialise daemon's ITC + * @param d the daemon object + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +init_itc (struct MHD_Daemon *restrict d) +{ + mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); + mhd_assert (mhd_D_TYPE_HAS_EVENTS_PROCESSING (d->threading.d_type)); +#ifdef MHD_USE_THREADS + // TODO: add and process "thread unsafe" daemon's option + if (! mhd_itc_init (&(d->threading.itc))) + { +#if defined(MHD_ITC_EVENTFD_) + mhd_LOG_MSG ( \ + d, MHD_SC_ITC_INITIALIZATION_FAILED, \ + "Failed to initialise eventFD for inter-thread communication"); +#elif defined(MHD_ITC_PIPE_) + mhd_LOG_MSG ( \ + d, MHD_SC_ITC_INITIALIZATION_FAILED, \ + "Failed to create a pipe for inter-thread communication"); +#elif defined(MHD_ITC_SOCKETPAIR_) + mhd_LOG_MSG ( \ + d, MHD_SC_ITC_INITIALIZATION_FAILED, \ + "Failed to create a socketpair for inter-thread communication"); +#else +#warning Missing expicit handling of the ITC type + mhd_LOG_MSG ( \ + d, MHD_SC_ITC_INITIALIZATION_FAILED, \ + "Failed to initialise inter-thread communication"); +#endif + return MHD_SC_ITC_INITIALIZATION_FAILED; + } + if (! mhd_FD_FITS_DAEMON (d,mhd_itc_r_fd (d->threading.itc))) + { + mhd_LOG_MSG (d, MHD_SC_ITC_FD_OUTSIDE_OF_SET_RANGE, \ + "The inter-thread communication FD value is " \ + "higher than allowed"); + (void) mhd_itc_destroy (d->threading.itc); + mhd_itc_set_invalid (&(d->threading.itc)); + return MHD_SC_ITC_FD_OUTSIDE_OF_SET_RANGE; + } +#endif /* MHD_USE_THREADS */ + return MHD_SC_OK; +} + + +/** + * Deallocate events data + * @param d the daemon object + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) void +deinit_itc (struct MHD_Daemon *restrict d) +{ + mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); + mhd_assert (mhd_D_TYPE_HAS_EVENTS_PROCESSING (d->threading.d_type)); +#ifdef MHD_USE_THREADS + // TODO: add and process "thread unsafe" daemon's option + mhd_assert (! mhd_ITC_IS_INVALID (d->threading.itc)); + (void) mhd_itc_destroy (d->threading.itc); +#endif /* MHD_USE_THREADS */ +} + + +/** + * The final part of events initialisation: pre-add ITC and listening FD to + * the monitored items (if supported by monitoring syscall). + * @param d the daemon object + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +add_itc_and_listen_to_monitoring (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); + mhd_assert (d->dbg.events_allocated); + mhd_assert (! d->dbg.events_fully_inited); + mhd_assert (mhd_D_TYPE_HAS_EVENTS_PROCESSING (d->threading.d_type)); +#ifdef MHD_USE_THREADS + mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); +#endif + + switch (d->events.poll_type) + { + case mhd_POLL_TYPE_EXT: + mhd_assert (NULL != d->events.data.ext.cb); + /* Nothing to do with the external events */ + // FIXME: Register the ITC and the listening NOW? + return MHD_SC_OK; + break; +#ifdef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: + mhd_assert (NULL != d->events.data.select.rfds); + mhd_assert (NULL != d->events.data.select.wfds); + mhd_assert (NULL != d->events.data.select.efds); + /* Nothing to do when using 'select()' */ + return MHD_SC_OK; + break; +#endif /* MHD_USE_SELECT */ +#ifdef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: + mhd_assert (NULL != d->events.data.poll.fds); + mhd_assert (NULL != d->events.data.poll.rel); + if (1) + { + unsigned int i; + i = 0; +#ifdef MHD_USE_THREADS + d->events.data.poll.fds[i].fd = mhd_itc_r_fd (d->threading.itc); + d->events.data.poll.fds[i].events = POLLIN; + d->events.data.poll.rel[i].fd_id = mhd_SOCKET_REL_MARKER_ITC; + ++i; +#endif + if (MHD_INVALID_SOCKET != d->net.listen.fd) + { + d->events.data.poll.fds[i].fd = d->net.listen.fd; + d->events.data.poll.fds[i].events = POLLIN; + d->events.data.poll.rel[i].fd_id = mhd_SOCKET_REL_MARKER_LISTEN; + } + } + return MHD_SC_OK; + break; +#endif /* MHD_USE_POLL */ +#ifdef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: + mhd_assert (MHD_INVALID_SOCKET != d->events.data.epoll.e_fd); + mhd_assert (NULL != d->events.data.epoll.events); + mhd_assert (0 < d->events.data.epoll.num_elements); + if (1) + { + struct epoll_event reg_event; +#ifdef MHD_USE_THREADS + reg_event.events = EPOLLIN; + reg_event.data.u64 = (uint64_t) mhd_SOCKET_REL_MARKER_ITC; /* uint64_t is used in the epoll header */ + if (0 != epoll_ctl (d->events.data.epoll.e_fd, EPOLL_CTL_ADD, + mhd_itc_r_fd (d->threading.itc), &reg_event)) + { + mhd_LOG_MSG (d, MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE, \ + "Failed to add ITC fd to the epoll monitoring."); + return MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE; + } +#endif + if (MHD_INVALID_SOCKET != d->net.listen.fd) + { + reg_event.events = EPOLLIN; + reg_event.data.u64 = (uint64_t) mhd_SOCKET_REL_MARKER_LISTEN; /* uint64_t is used in the epoll header */ + if (0 != epoll_ctl (d->events.data.epoll.e_fd, EPOLL_CTL_ADD, + d->net.listen.fd, &reg_event)) + { + mhd_LOG_MSG (d, MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE, \ + "Failed to add listening fd to the epoll monitoring."); + return MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE; + } + } + } + return MHD_SC_OK; + break; +#endif /* MHD_USE_EPOLL */ +#ifndef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: +#endif /* ! MHD_USE_SELECT */ +#ifndef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: +#endif /* ! MHD_USE_POLL */ +#ifndef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: +#endif /* ! MHD_USE_EPOLL */ + case mhd_POLL_TYPE_NOT_SET_YET: + default: + mhd_assert (0 && "Impossible value"); + } + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; +} + + +/** + * Initialise daemon connections' data. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +init_individual_conns (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (0 != d->conns.cfg.count_limit); + + mhd_DLINKEDL_INIT_LIST (&(d->conns),all_conn); + mhd_DLINKEDL_INIT_LIST (&(d->conns),def_timeout); + mhd_DLINKEDL_INIT_LIST (&(d->conns),to_clean); + d->conns.count = 0; + d->conns.block_new = false; + + d->conns.cfg.mem_pool_size = s->conn_memory_limit; + if (0 == d->conns.cfg.mem_pool_size) + d->conns.cfg.mem_pool_size = 32 * 1024; + else if (256 > d->conns.cfg.mem_pool_size) + d->conns.cfg.mem_pool_size = 256; + +#ifndef NDEBUG + d->dbg.connections_inited = true; +#endif + return MHD_SC_OK; +} + + +/** + * Prepare daemon-local (worker daemon for thread pool mode) threading data + * and finish events initialising. + * To be used only with non-master daemons. + * Do not start the thread even if configured for the internal threads. + * @param d the daemon object + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +init_individual_thread_data_events_conns (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + enum MHD_StatusCode res; + mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); + mhd_assert (mhd_D_TYPE_HAS_EVENTS_PROCESSING (d->threading.d_type)); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (! d->dbg.connections_inited); + + res = allocate_events (d); + if (MHD_SC_OK != res) + return res; + + res = init_itc (d); + if (MHD_SC_OK == res) + { + res = add_itc_and_listen_to_monitoring (d); + + if (MHD_SC_OK == res) + { +#ifndef NDEBUG + d->dbg.events_fully_inited = true; +#endif +#ifdef MHD_USE_THREADS + mhd_thread_handle_ID_set_invalid (&(d->threading.tid)); + d->threading.stop_requested = false; +#endif /* MHD_USE_THREADS */ +#ifndef NDEBUG + d->dbg.threading_inited = true; +#endif + + res = init_individual_conns (d, s); + if (MHD_SC_OK == res) + return MHD_SC_OK; + } + deinit_itc (d); + } + deallocate_events (d); + mhd_assert (MHD_SC_OK != res); + return res; +} + + +/** + * Deinit daemon-local (worker daemon for thread pool mode) threading data + * and deallocate events. + * To be used only with non-master daemons. + * Do not start the thread even if configured for the internal threads. + * @param d the daemon object + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) void +deinit_individual_thread_data_events_conns (struct MHD_Daemon *restrict d) +{ + deinit_itc (d); + deallocate_events (d); + mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn)); + mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->events),proc_ready)); +#ifndef NDEBUG + d->dbg.events_fully_inited = false; +#endif +} + + +/** + * Set the maximum number of handled connections for the daemon. + * Works only for global limit, does not work for the worker daemon. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +set_connections_total_limits (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + unsigned int limit_by_conf; + unsigned int limit_by_num; + unsigned int limit_by_select; + unsigned int resulting_limit; + bool error_by_fd_setsize; + unsigned int num_worker_daemons; + + mhd_assert (! mhd_D_HAS_MASTER (d)); + mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); + + if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int) + { + mhd_assert (MHD_WM_WORKER_THREADS == s->work_mode.mode); + if ((0 != s->global_connection_limit) && + (0 != s->work_mode.params.num_worker_threads) && + (s->global_connection_limit < s->work_mode.params.num_worker_threads)) + { + mhd_LOG_MSG ( \ + d, MHD_SC_CONFIGURATION_CONN_LIMIT_TOO_SMALL, \ + "The limit specified by MHD_D_O_GLOBAL_CONNECTION_LIMIT is smaller " \ + "then the number of worker threads."); + return MHD_SC_CONFIGURATION_CONN_LIMIT_TOO_SMALL; + } + } + num_worker_daemons = 1; +#ifdef MHD_USE_THREADS + if (mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)) + num_worker_daemons = s->work_mode.params.num_worker_threads; +#endif /* MHD_USE_THREADS */ + + limit_by_conf = s->global_connection_limit; + limit_by_num = UINT_MAX; + limit_by_select = UINT_MAX; + + error_by_fd_setsize = false; +#ifdef MHD_POSIX_SOCKETS + if (1) + { + limit_by_num = (unsigned int) d->net.cfg.max_fd_num; + if (0 != limit_by_num) + { + /* Find the upper limit. + The real limit is lower, as any other process FDs will use the slots + in the allowed numbers range */ + limit_by_num -= 3; /* The numbers zero, one and two are used typically */ +#ifdef MHD_USE_THREADS + limit_by_num -= mhd_ITC_NUM_FDS * num_worker_daemons; +#endif /* MHD_USE_THREADS */ + if (MHD_INVALID_SOCKET != d->net.listen.fd) + --limit_by_num; /* One FD is used for the listening socket */ + if ((num_worker_daemons > limit_by_num) || + (limit_by_num > (unsigned int) d->net.cfg.max_fd_num) /* Underflow */) + { + if (d->net.cfg.max_fd_num == s->fd_number_limit) + { + mhd_LOG_MSG ( \ + d, MHD_SC_MAX_FD_NUMBER_LIMIT_TOO_STRICT, \ + "The limit specified by MHD_D_O_FD_NUMBER_LIMIT is too strict " \ + "for this daemon settings."); + return MHD_SC_MAX_FD_NUMBER_LIMIT_TOO_STRICT; + } + else + { + mhd_assert (mhd_POLL_TYPE_SELECT == d->events.poll_type); + error_by_fd_setsize = true; + } + } + } + else + limit_by_num = (unsigned int) INT_MAX; + } +#elif defined(MHD_WINSOCK_SOCKETS) + if (1) + { +#ifdef MHD_USE_SELECT + if ((mhd_DAEMON_TYPE_SINGLE == d->threading.d_type) && + (mhd_POLL_TYPE_SELECT == d->events.poll_type)) + { + /* W32 limits the total number (count) of sockets used for select() */ + unsigned int limit_per_worker; + + limit_per_worker = FD_SETSIZE; + if (MHD_INVALID_SOCKET != d->net.listen.fd) + --limit_per_worker; /* The slot for the listening socket */ +#ifdef MHD_USE_THREADS + --limit_per_worker; /* The slot for the ITC */ +#endif /* MHD_USE_THREADS */ + if ((0 == limit_per_worker) || (limit_per_worker > FD_SETSIZE)) + error_by_fd_setsize = true; + else + { + limit_by_select = limit_per_worker * num_worker_daemons; + if (limit_by_select / limit_per_worker != num_worker_daemons) + limit_by_select = UINT_MAX; + } + } +#endif /* MHD_USE_SELECT */ + (void) 0; /* Mute compiler warning */ + } +#endif /* MHD_POSIX_SOCKETS */ + if (error_by_fd_setsize) + { + mhd_LOG_MSG ( \ + d, MHD_SC_SYS_FD_SETSIZE_TOO_STRICT, \ + "The FD_SETSIZE is too strict to run daemon with the polling " \ + "by select() and with the specified number of workers."); + return MHD_SC_SYS_FD_SETSIZE_TOO_STRICT; + } + + if (0 != limit_by_conf) + { + /* The number has bet set explicitly */ + resulting_limit = limit_by_conf; + } + else + { + /* No user configuration provided */ + unsigned int suggested_limit; +#ifndef MHD_WINSOCK_SOCKETS +#define TYPICAL_NOFILES_LIMIT (1024) /* The usual limit for the number of open FDs */ + suggested_limit = TYPICAL_NOFILES_LIMIT; + suggested_limit -= 3; /* The numbers zero, one and two are used typically */ +#ifdef MHD_USE_THREADS + suggested_limit -= mhd_ITC_NUM_FDS * num_worker_daemons; +#endif /* MHD_USE_THREADS */ + if (MHD_INVALID_SOCKET != d->net.listen.fd) + --suggested_limit; /* One FD is used for the listening socket */ + if (suggested_limit > TYPICAL_NOFILES_LIMIT) + suggested_limit = 0; /* Overflow */ +#else /* MHD_WINSOCK_SOCKETS */ +#ifdef _WIN64 + suggested_limit = 2048; +#else + suggested_limit = 1024; +#endif +#endif /* MHD_WINSOCK_SOCKETS */ + if (suggested_limit < num_worker_daemons) + { + /* Use at least one connection for every worker daemon and + let the system to restrict the new connections if they are above + the system limits. */ + suggested_limit = num_worker_daemons; + } + resulting_limit = suggested_limit; + } + if (resulting_limit > limit_by_num) + resulting_limit = limit_by_num; + + if (resulting_limit > limit_by_select) + resulting_limit = limit_by_select; + + mhd_assert (resulting_limit >= num_worker_daemons); + d->conns.cfg.count_limit = resulting_limit; + + return MHD_SC_OK; +} + + +/** + * Set correct daemon threading type. + * Set the number of workers for thread pool type. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +MHD_FN_PAR_NONNULL_ (1) \ + MHD_FN_MUST_CHECK_RESULT_ static inline enum MHD_StatusCode +set_d_threading_type (struct MHD_Daemon *restrict d) +{ + switch (d->wmode_int) + { + case mhd_WM_INT_EXTERNAL_EVENTS_EDGE: + case mhd_WM_INT_EXTERNAL_EVENTS_LEVEL: + mhd_assert (! mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT == d->events.poll_type); + mhd_assert (NULL != d->events.data.ext.cb); +#ifdef MHD_USE_THREADS + d->threading.d_type = mhd_DAEMON_TYPE_SINGLE; +#endif /* MHD_USE_THREADS */ + return MHD_SC_OK; + case mhd_WM_INT_INTERNAL_EVENTS_NO_THREADS: + mhd_assert (! mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT != d->events.poll_type); +#ifdef MHD_USE_THREADS + d->threading.d_type = mhd_DAEMON_TYPE_SINGLE; +#endif /* MHD_USE_THREADS */ + return MHD_SC_OK; +#ifdef MHD_USE_THREADS + case mhd_WM_INT_INTERNAL_EVENTS_ONE_THREAD: + mhd_assert (mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT != d->events.poll_type); + d->threading.d_type = mhd_DAEMON_TYPE_SINGLE; + return MHD_SC_OK; + case mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION: + mhd_assert (mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT != d->events.poll_type); + mhd_assert (mhd_POLL_TYPE_EPOLL != d->events.poll_type); + d->threading.d_type = mhd_DAEMON_TYPE_LISTEN_ONLY; + return MHD_SC_OK; + case mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL: + mhd_assert (mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT != d->events.poll_type); + d->threading.d_type = mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY; + return MHD_SC_OK; +#else /* ! MHD_USE_THREADS */ + case mhd_WM_INT_INTERNAL_EVENTS_ONE_THREAD: + case mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION: + case mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL: +#endif /* ! MHD_USE_THREADS */ + default: + mhd_assert (0 && "Impossible value"); + } + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; +} + + +#ifdef MHD_USE_THREADS + +/** + * De-initialise workers pool, including workers daemons. + * The threads must be not running. + * @param d the daemon object + * @param num_workers the number of workers to deinit + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) void +deinit_workers_pool (struct MHD_Daemon *restrict d, + unsigned int num_workers) +{ + unsigned int i; + mhd_assert (mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (NULL != d->threading.hier.pool.workers); + mhd_assert ((2 <= d->threading.hier.pool.num) || \ + (mhd_DAEMON_STATE_STARTING == d->state)); + mhd_assert ((num_workers == d->threading.hier.pool.num) || \ + (mhd_DAEMON_STATE_STARTING == d->state)); + mhd_assert ((mhd_DAEMON_STATE_STOPPING == d->state) || \ + (mhd_DAEMON_STATE_STARTING == d->state)); + + /* Deinitialise in reverse order */ + for (i = num_workers - 1; num_workers > i; --i) + { /* Note: loop exits after underflow of 'i' */ + struct MHD_Daemon *const worker = d->threading.hier.pool.workers + i; + deinit_individual_thread_data_events_conns (worker); +#ifdef MHD_USE_EPOLL + if (mhd_POLL_TYPE_EPOLL == worker->events.poll_type) + deinit_epoll (worker); +#endif /* MHD_USE_EPOLL */ + } + free (d->threading.hier.pool.workers); +#ifndef NDEBUG + d->dbg.thread_pool_inited = false; +#endif +} + + +/** + * Nullify worker daemon member that should be set only in master daemon + * @param d + */ +static MHD_FN_PAR_NONNULL_ (1) void +reset_master_only_areas (struct MHD_Daemon *restrict d) +{ + /* Not needed. It is initialised later */ + /* memset (&(d->req_cfg.large_buf), 0, sizeof(d->req_cfg.large_buf)); */ + (void) d; +} + + +/** + * Initialise workers pool, including workers daemons. + * Do not start the threads. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +init_workers_pool (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + enum MHD_StatusCode res; + size_t workers_pool_size; + unsigned int conn_per_daemon; + unsigned int num_workers; + unsigned int conn_remainder; + unsigned int i; + + mhd_assert (d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); + mhd_assert (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int); + mhd_assert (mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (mhd_POLL_TYPE_NOT_SET_YET < d->events.poll_type); + mhd_assert (1 < s->work_mode.params.num_worker_threads); + mhd_assert (0 != d->conns.cfg.count_limit); + mhd_assert (s->work_mode.params.num_worker_threads <= \ + d->conns.cfg.count_limit); + mhd_assert (! d->dbg.thread_pool_inited); + + num_workers = s->work_mode.params.num_worker_threads; + workers_pool_size = + (sizeof(struct MHD_Daemon) * num_workers); + if (workers_pool_size / num_workers != sizeof(struct MHD_Daemon)) + { /* Overflow */ + mhd_LOG_MSG ( \ + d, MHD_SC_THREAD_POOL_MALLOC_FAILURE, \ + "The size of the thread pool is too large."); + return MHD_SC_THREAD_POOL_MALLOC_FAILURE; + } + +#ifndef NDEBUG + mhd_itc_set_invalid (&(d->threading.itc)); + mhd_thread_handle_ID_set_invalid (&(d->threading.tid)); +#endif + + d->threading.hier.pool.workers = malloc (workers_pool_size); + if (NULL == d->threading.hier.pool.workers) + { + mhd_LOG_MSG ( \ + d, MHD_SC_THREAD_POOL_MALLOC_FAILURE, \ + "Failed to allocate memory for the thread pool."); + return MHD_SC_THREAD_POOL_MALLOC_FAILURE; + } + + conn_per_daemon = d->conns.cfg.count_limit / num_workers; + conn_remainder = d->conns.cfg.count_limit % num_workers; + res = MHD_SC_OK; + for (i = 0; num_workers > i; ++i) + { + struct MHD_Daemon *restrict const worker = + d->threading.hier.pool.workers + i; + memcpy (worker, d, sizeof(struct MHD_Daemon)); + reset_master_only_areas (worker); + + worker->threading.d_type = mhd_DAEMON_TYPE_WORKER; + worker->threading.hier.master = d; + worker->conns.cfg.count_limit = conn_per_daemon; + if (conn_remainder > i) + worker->conns.cfg.count_limit++; /* Distribute the reminder */ +#ifdef MHD_USE_EPOLL + if (mhd_POLL_TYPE_EPOLL == worker->events.poll_type) + { + if (0 == i) + { + mhd_assert (0 <= d->events.data.epoll.e_fd); + /* Move epoll control FD from the master daemon to the first worker */ + /* The FD has been copied by memcpy(). Clean-up the master daemon. */ + d->events.data.epoll.e_fd = MHD_INVALID_SOCKET; + } + else + res = init_epoll (worker); + } +#endif /* MHD_USE_EPOLL */ + if (MHD_SC_OK == res) + { + res = init_individual_thread_data_events_conns (worker, s); + if (MHD_SC_OK == res) + continue; /* Process the next worker */ + +#ifdef MHD_USE_EPOLL + if (mhd_POLL_TYPE_EPOLL == worker->events.poll_type) + deinit_epoll (worker); +#endif /* MHD_USE_EPOLL */ + + /* Below is the clean-up of the current slot */ + } + free (worker); + break; + } + if (num_workers == i) + { + mhd_assert (MHD_SC_OK == res); +#ifndef NDEBUG + d->dbg.thread_pool_inited = true; + d->dbg.threading_inited = true; +#endif + d->threading.hier.pool.num = num_workers; + return MHD_SC_OK; + } + + /* Below is a clean-up */ + + mhd_assert (MHD_SC_OK != res); + deinit_workers_pool (d, i); + return res; +} + + +#endif /* MHD_USE_THREADS */ + +/** + * Initialise threading and inter-thread communications. + * Also finish initialisation of events processing and initialise daemon's + * connection data. + * Do not start the thread even if configured for the internal threads. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_init_threading_and_conn (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + enum MHD_StatusCode res; + + mhd_assert (d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); + mhd_assert (mhd_POLL_TYPE_NOT_SET_YET != d->events.poll_type); + + res = set_d_threading_type (d); + if (MHD_SC_OK != res) + return res; + + res = set_connections_total_limits (d, s); + if (MHD_SC_OK != res) + return res; + + d->threading.cfg.stack_size = s->stack_size; + + if (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)) + res = init_individual_thread_data_events_conns (d, s); + else + { +#ifdef MHD_USE_THREADS + res = init_workers_pool (d, s); +#else /* ! MHD_USE_THREADS */ + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; +#endif /* ! MHD_USE_THREADS */ + } + if (MHD_SC_OK == res) + { + mhd_assert (d->dbg.events_allocated || \ + mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type) || \ + ! d->dbg.events_allocated); + mhd_assert (! d->dbg.thread_pool_inited || \ + mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type) || \ + d->dbg.thread_pool_inited); + mhd_assert (! mhd_D_TYPE_IS_INTERNAL_ONLY (d->threading.d_type)); + mhd_assert (! d->dbg.events_allocated || d->dbg.connections_inited); + mhd_assert (! d->dbg.connections_inited || d->dbg.events_allocated); + } + return res; +} + + +/** + * De-initialise threading and inter-thread communications. + * Also deallocate events and de-initialise daemon's connection data. + * No daemon-manged threads should be running. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) void +daemon_deinit_threading_and_conn (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); + mhd_assert (d->dbg.threading_inited); + mhd_assert (! mhd_D_TYPE_IS_INTERNAL_ONLY (d->threading.d_type)); + if (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)) + { + mhd_assert (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL != d->wmode_int); + mhd_assert (d->dbg.connections_inited); + mhd_assert (d->dbg.events_allocated); + mhd_assert (! d->dbg.thread_pool_inited); + deinit_individual_thread_data_events_conns (d); + } + else + { +#ifdef MHD_USE_THREADS + mhd_assert (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int); + mhd_assert (! d->dbg.connections_inited); + mhd_assert (! d->dbg.events_allocated); + mhd_assert (d->dbg.thread_pool_inited); + deinit_workers_pool (d, d->threading.hier.pool.num); +#else /* ! MHD_USE_THREADS */ + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + (void) 0; +#endif /* ! MHD_USE_THREADS */ + } +} + + +#ifdef MHD_USE_THREADS + +/** + * Start the daemon individual single thread. + * Works both for single thread daemons and for worker daemon for thread + * pool mode. + * Must be called only for daemons with internal threads. + * @param d the daemon object, must be completely initialised + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +start_individual_daemon_thread (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.threading_inited); + mhd_assert (mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (! mhd_thread_handle_ID_is_valid_handle (d->threading.tid)); + + if (mhd_DAEMON_TYPE_SINGLE == d->threading.d_type) + { + if (! mhd_create_named_thread ( \ + &(d->threading.tid), "MHD-single", \ + d->threading.cfg.stack_size, \ + &mhd_worker_all_events, \ + (void*) d)) + { + mhd_LOG_MSG (d, MHD_SC_THREAD_MAIN_LAUNCH_FAILURE, \ + "Failed to start daemon main thread."); + return MHD_SC_THREAD_MAIN_LAUNCH_FAILURE; + } + } + else if (mhd_DAEMON_TYPE_WORKER == d->threading.d_type) + { + if (! mhd_create_named_thread ( \ + &(d->threading.tid), "MHD-worker", \ + d->threading.cfg.stack_size, \ + &mhd_worker_all_events, \ + (void*) d)) + { + mhd_LOG_MSG (d, MHD_SC_THREAD_WORKER_LAUNCH_FAILURE, \ + "Failed to start daemon worker thread."); + return MHD_SC_THREAD_WORKER_LAUNCH_FAILURE; + } + } + else if (mhd_DAEMON_TYPE_LISTEN_ONLY == d->threading.d_type) + { + if (! mhd_create_named_thread ( \ + &(d->threading.tid), "MHD-listen", \ + d->threading.cfg.stack_size, \ + &mhd_worker_listening_only, \ + (void*) d)) + { + mhd_LOG_MSG (d, MHD_SC_THREAD_LISTENING_LAUNCH_FAILURE, \ + "Failed to start daemon listening thread."); + return MHD_SC_THREAD_LISTENING_LAUNCH_FAILURE; + } + } + else + { + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; + } + mhd_assert (mhd_thread_handle_ID_is_valid_handle (d->threading.tid)); + return MHD_SC_OK; +} + + +/** + * Stop the daemon individual single thread. + * Works both for single thread daemons and for worker daemon for thread + * pool mode. + * Must be called only for daemons with internal threads. + * @param d the daemon object, must be completely initialised + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +MHD_FN_PAR_NONNULL_ (1) static void +stop_individual_daemon_thread (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.threading_inited); + mhd_assert (mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert ((mhd_DAEMON_STATE_STOPPING == d->state) || \ + (mhd_DAEMON_STATE_STARTING == d->state)); + mhd_assert (mhd_thread_handle_ID_is_valid_handle (d->threading.tid)); + + d->threading.stop_requested = true; + + mhd_daemon_trigger_itc (d); + if (! mhd_thread_handle_ID_join_thread (d->threading.tid)) + { + mhd_LOG_MSG (d, MHD_SC_DAEMON_THREAD_STOP_ERROR, \ + "Failed to stop daemon main thread."); + } +} + + +/** + * Stop all worker threads in the thread pool. + * Must be called only for master daemons with thread pool. + * @param d the daemon object, the workers threads must be running + * @param num_workers the number of threads to stop + */ +static MHD_FN_PAR_NONNULL_ (1) void +stop_worker_pool_threads (struct MHD_Daemon *restrict d, + unsigned int num_workers) +{ + unsigned int i; + mhd_assert (mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (NULL != d->threading.hier.pool.workers); + mhd_assert (0 != d->threading.hier.pool.num); + mhd_assert (d->dbg.thread_pool_inited); + mhd_assert (2 <= d->threading.hier.pool.num); + mhd_assert ((num_workers == d->threading.hier.pool.num) || \ + (mhd_DAEMON_STATE_STARTING == d->state)); + mhd_assert ((mhd_DAEMON_STATE_STOPPING == d->state) || \ + (mhd_DAEMON_STATE_STARTING == d->state)); + + /* Process all the threads in the reverse order */ + + /* Trigger all threads */ + for (i = num_workers - 1; num_workers > i; --i) + { /* Note: loop exits after underflow of 'i' */ + d->threading.hier.pool.workers[i].threading.stop_requested = true; + mhd_assert (mhd_ITC_IS_VALID ( \ + d->threading.hier.pool.workers[i].threading.itc)); + mhd_daemon_trigger_itc (d->threading.hier.pool.workers + i); + } + + /* Collect all threads */ + for (i = num_workers - 1; num_workers > i; --i) + { /* Note: loop exits after underflow of 'i' */ + struct MHD_Daemon *const restrict worker = + d->threading.hier.pool.workers + i; + mhd_assert (mhd_thread_handle_ID_is_valid_handle (worker->threading.tid)); + if (! mhd_thread_handle_ID_join_thread (worker->threading.tid)) + { + mhd_LOG_MSG (d, MHD_SC_DAEMON_THREAD_STOP_ERROR, \ + "Failed to stop a worker thread."); + } + } +} + + +/** + * Start the workers pool threads. + * Must be called only for master daemons with thread pool. + * @param d the daemon object, must be completely initialised + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +start_worker_pool_threads (struct MHD_Daemon *restrict d) +{ + enum MHD_StatusCode res; + unsigned int i; + + mhd_assert (d->dbg.threading_inited); + mhd_assert (mhd_WM_INT_HAS_THREADS (d->wmode_int)); + mhd_assert (mhd_D_TYPE_IS_VALID (d->threading.d_type)); + mhd_assert (mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (d->dbg.thread_pool_inited); + mhd_assert (2 <= d->threading.hier.pool.num); + + res = MHD_SC_OK; + + for (i = 0; d->threading.hier.pool.num > i; ++i) + { + res = start_individual_daemon_thread (d->threading.hier.pool.workers + i); + if (MHD_SC_OK != res) + break; + } + if (d->threading.hier.pool.num == i) + { + mhd_assert (MHD_SC_OK == res); + return MHD_SC_OK; + } + + stop_worker_pool_threads (d, i); + mhd_assert (MHD_SC_OK != res); + return res; +} + + +#endif /* MHD_USE_THREADS */ + +/** + * Start the daemon internal threads, if the daemon configured to use them. + * @param d the daemon object, must be completely initialised + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_start_threads (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); + mhd_assert (d->dbg.threading_inited); + mhd_assert (! mhd_D_TYPE_IS_INTERNAL_ONLY (d->threading.d_type)); + if (mhd_WM_INT_HAS_THREADS (d->wmode_int)) + { +#ifdef MHD_USE_THREADS + if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL != d->wmode_int) + { + mhd_assert (d->dbg.threading_inited); + mhd_assert (mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY != d->threading.d_type); + return start_individual_daemon_thread (d); + } + else + { + mhd_assert (d->dbg.thread_pool_inited); + mhd_assert (mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY == d->threading.d_type); + return start_worker_pool_threads (d); + } +#else /* ! MHD_USE_THREADS */ + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; +#endif /* ! MHD_USE_THREADS */ + } + return MHD_SC_OK; +} + + +/** + * Stop the daemon internal threads, if the daemon configured to use them. + * @param d the daemon object, the threads (if any) must be started + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) void +daemon_stop_threads (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.net_inited); + mhd_assert (! d->dbg.net_deinited); + mhd_assert (d->dbg.threading_inited); + if (mhd_WM_INT_HAS_THREADS (d->wmode_int)) + { +#ifdef MHD_USE_THREADS + if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL != d->wmode_int) + { + mhd_assert (d->dbg.threading_inited); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + stop_individual_daemon_thread (d); + return; + } + else + { + mhd_assert (d->dbg.thread_pool_inited); + mhd_assert (mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + stop_worker_pool_threads (d, d->threading.hier.pool.num); + return; + } +#else /* ! MHD_USE_THREADS */ + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + return MHD_SC_INTERNAL_ERROR; +#endif /* ! MHD_USE_THREADS */ + } +} + + +/** + * Internal daemon initialisation function. + * This function calls all required initialisation stages one-by-one. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_start_internal (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + enum MHD_StatusCode res; + + res = daemon_set_work_mode (d, s); + if (MHD_SC_OK != res) + return res; + + res = daemon_init_net (d, s); + if (MHD_SC_OK != res) + return res; + + + // TODO: Other init + + res = daemon_init_threading_and_conn (d, s); + if (MHD_SC_OK == res) + { + mhd_assert (d->dbg.net_inited); + mhd_assert (d->dbg.threading_inited); + mhd_assert (! mhd_D_TYPE_IS_INTERNAL_ONLY (d->threading.d_type)); + + res = daemon_init_large_buf (d, s); + if (MHD_SC_OK == res) + { + res = daemon_start_threads (d); + if (MHD_SC_OK == res) + { + return MHD_SC_OK; + } + + /* Below is a clean-up path */ + daemon_deinit_large_buf (d); + } + daemon_deinit_threading_and_conn (d); + } + + + daemon_deinit_net (d); + mhd_assert (MHD_SC_OK != res); + return res; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +MHD_daemon_start (struct MHD_Daemon *daemon) +{ + struct MHD_Daemon *const d = daemon; /* a short alias */ + struct DaemonOptions *const s = daemon->settings; /* a short alias */ + enum MHD_StatusCode res; + + if (mhd_DAEMON_STATE_NOT_STARTED != daemon->state) + return MHD_SC_TOO_LATE; + + mhd_assert (NULL != s); + + d->state = mhd_DAEMON_STATE_STARTING; + res = daemon_start_internal (d, s); + + d->settings = NULL; + dsettings_release (s); + + d->state = + (MHD_SC_OK == res) ? mhd_DAEMON_STATE_STARTED : mhd_DAEMON_STATE_FAILED; + + return res; +} + + +MHD_EXTERN_ MHD_FN_PAR_NONNULL_ALL_ void +MHD_daemon_destroy (struct MHD_Daemon *daemon) +{ + bool not_yet_started = (mhd_DAEMON_STATE_NOT_STARTED == daemon->state); + bool has_failed = (mhd_DAEMON_STATE_FAILED == daemon->state); + mhd_assert (mhd_DAEMON_STATE_STOPPING > daemon->state); + mhd_assert (mhd_DAEMON_STATE_STARTING != daemon->state); + + daemon->state = mhd_DAEMON_STATE_STOPPING; + if (not_yet_started) + { + mhd_assert (NULL != daemon->settings); + dsettings_release (daemon->settings); + return; + } + else if (! has_failed) + { + mhd_assert (NULL == daemon->settings); + mhd_assert (daemon->dbg.threading_inited); + + daemon_stop_threads (daemon); + + daemon_deinit_threading_and_conn (daemon); + + daemon_deinit_large_buf (daemon); + + daemon_deinit_net (daemon); + } + daemon->state = mhd_DAEMON_STATE_STOPPED; /* Useful only for debugging */ + + free (daemon); +} diff --git a/src/mhd2/dcc_action.c b/src/mhd2/dcc_action.c @@ -0,0 +1,150 @@ +/* + 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/dcc_action.c + * @brief The definition of the MHD_DCC_action_*() + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include <string.h> + +#include "mhd_dcc_action.h" +#include "mhd_response.h" +#include "mhd_reply.h" +#include "mhd_connection.h" + +#include "mhd_public_api.h" + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_CSTR_ (4) const struct MHD_DynamicContentCreatorAction * +MHD_DCC_action_continue_zc ( + struct MHD_DynamicContentCreatorContext *ctx, + size_t data_size, + const struct MHD_DynContentZCIoVec *iov_data, + const char *MHD_RESTRICT chunk_ext) +{ + struct MHD_DynamicContentCreatorAction *ret; + + ret = NULL; + do + { + if ((&(ctx->connection->rp.app_act_ctx)) != ctx) + break; + if (mhd_DCC_ACTION_NO_ACTION != ctx->connection->rp.app_act.act) + break; /* The action already has been created */ + if (NULL != iov_data) + { + mhd_assert (0 && "Not implemented yet"); + break; + } + if (0 == data_size) + { /* The total size must be non-zero */ + if (NULL == iov_data) + break; + mhd_assert (0 && "Not implemented yet"); + (void) iov_data->iov_count; + // TODO: add check for iov data total size + break; + } + if (NULL != chunk_ext) + { + if (ctx->connection->rp.props.chunked) + { + mhd_assert (0 && "Not implemented yet"); + // TODO: copy 'chunk_ext' directly to the output buffer + break; + } + } + ret = &(ctx->connection->rp.app_act); + ret->act = mhd_DCC_ACTION_CONTINUE; + ret->data.cntnue.buf_data_size = data_size; + ret->data.cntnue.iov_data = iov_data; + } while (0); + + if (NULL == ret) + { + /* Call application clean-up */ + if ((NULL != iov_data) && (NULL != iov_data->iov_fcb)) + iov_data->iov_fcb (iov_data->iov_fcb_cls); + } + + return ret; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +const struct MHD_DynamicContentCreatorAction * +MHD_DCC_action_finish_with_footer ( + struct MHD_DynamicContentCreatorContext *ctx, + size_t num_footers, + const struct MHD_NameValueCStr *MHD_RESTRICT footers) +{ + struct MHD_DynamicContentCreatorAction *ret; + + if ((&(ctx->connection->rp.app_act_ctx)) != ctx) + return NULL; + if (mhd_DCC_ACTION_NO_ACTION != ctx->connection->rp.app_act.act) + return NULL; /* The action already has been created */ + if ((0 != num_footers) && (NULL == footers)) + return NULL; + + if (MHD_SIZE_UNKNOWN != ctx->connection->rp.response->cntn_size) + { + mhd_assert (ctx->connection->rp.rsp_cntn_read_pos < + ctx->connection->rp.response->cntn_size); + return NULL; + } + + if (0 != num_footers) + { + mhd_assert (0 && "Not implemented yet"); + // TODO: build response footer and use footers directly + return NULL; + } + + ret = &(ctx->connection->rp.app_act); + ret->act = mhd_DCC_ACTION_FINISH; + + return ret; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_RETURNS_NONNULL_ const struct MHD_DynamicContentCreatorAction * +MHD_DCC_action_suspend (struct MHD_DynamicContentCreatorContext *ctx) +{ + struct MHD_DynamicContentCreatorAction *ret; + + if ((&(ctx->connection->rp.app_act_ctx)) != ctx) + return NULL; + if (mhd_DCC_ACTION_NO_ACTION != ctx->connection->rp.app_act.act) + return NULL; /* The action already has been created */ + + ret = &(ctx->connection->rp.app_act); + ret->act = mhd_DCC_ACTION_SUSPEND; + + return ret; +} diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c @@ -0,0 +1,1104 @@ +/* + 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/events_process.c + * @brief The implementation of events processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "events_process.h" + +#include "mhd_socket_type.h" +#include "sys_poll.h" +#include "sys_select.h" +#ifdef MHD_USE_EPOLL +# include <sys/epoll.h> +#endif +#ifdef MHD_POSIX_SOCKETS +# include "sys_errno.h" +#endif + +#include "mhd_itc.h" + +#include "mhd_panic.h" + +#include "mhd_sockets_macros.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" + +#include "conn_mark_ready.h" +#include "daemon_logger.h" +#include "daemon_add_conn.h" +#include "daemon_funcs.h" +#include "conn_data_process.h" +#include "stream_funcs.h" + +#include "mhd_public_api.h" + +static int +get_max_wait (struct MHD_Daemon *restrict d) +{ + bool zero_wait = d->events.zero_wait; + if (d->events.act_req.accept && d->conns.block_new) + d->events.act_req.accept = false; + + d->events.zero_wait = false; /* Reset as this pending data will be processed */ + if (d->events.act_req.accept) + return 0; + if (zero_wait) + return 0; + if (NULL != mhd_DLINKEDL_GET_FIRST (&(d->events), proc_ready)) + return 0; + + return INT_MAX; // TODO: calculate correct timeout value +} + + +MHD_FN_PAR_NONNULL_ (1) static void +update_conn_net_status (struct MHD_Daemon *restrict d, + struct MHD_Connection *restrict c, + bool recv_ready, + bool send_ready, + bool err_state) +{ + enum mhd_SocketNetState sk_state; + + mhd_assert (d == c->daemon); + + sk_state = mhd_SOCKET_NET_STATE_NOTHING; + if (recv_ready) + sk_state = (enum mhd_SocketNetState) + (sk_state | (unsigned int) mhd_SOCKET_NET_STATE_RECV_READY); + if (send_ready) + sk_state = (enum mhd_SocketNetState) + (sk_state | (unsigned int) mhd_SOCKET_NET_STATE_SEND_READY); + if (err_state) + sk_state = (enum mhd_SocketNetState) + (sk_state | (unsigned int) mhd_SOCKET_NET_STATE_ERROR_READY); + c->sk_ready = sk_state; + + if ((0 != + (((unsigned int) c->sk_ready) & ((unsigned int) c->event_loop_info) + & (MHD_EVENT_LOOP_INFO_READ | MHD_EVENT_LOOP_INFO_WRITE))) + || err_state) + mhd_conn_mark_ready (c, d); + else + mhd_conn_mark_unready (c, d); +} + + +/** + * Accept new connections on the daemon + * @param d the daemon to use + * @return true if all incoming connections has been accepted, + * false if some connection may still wait to be accepted + */ +MHD_FN_PAR_NONNULL_ (1) static bool +daemon_accept_new_conns (struct MHD_Daemon *restrict d) +{ + unsigned int num_to_accept; + mhd_assert (MHD_INVALID_SOCKET != d->net.listen.fd); + mhd_assert (! d->conns.block_new); + mhd_assert (d->conns.count < d->conns.cfg.count_limit); + mhd_assert (! mhd_D_HAS_WORKERS (d)); + + if (! d->net.listen.non_block) + num_to_accept = 1; /* listen socket is blocking, only one connection can be processed */ + else + { + const unsigned int slots_left = d->conns.cfg.count_limit - d->conns.count; + if (! mhd_D_HAS_MASTER (d)) + { + /* Fill up to one quarter of allowed limit in one turn */ + num_to_accept = d->conns.cfg.count_limit / 4; + /* Limit to a reasonable number */ + if (((sizeof(void *) > 4) ? 4096 : 1024) < num_to_accept) + num_to_accept = ((sizeof(void *) > 4) ? 4096 : 1024); + if (slots_left < num_to_accept) + num_to_accept = slots_left; + } +#ifdef MHD_USE_THREADS + else + { + /* Has workers thread pool. Care must be taken to evenly distribute + new connections in the workers pool. + At the same time, the burst of new connections should be handled as + quick as possible. */ + const unsigned int num_conn = d->conns.count; + const unsigned int limit = d->conns.cfg.count_limit; + const unsigned int num_workers = + d->threading.hier.master->threading.hier.pool.num; + if (num_conn < limit / 16) + { + num_to_accept = num_conn / num_workers; + if (8 > num_to_accept) + { + if (8 > slots_left / 16) + num_to_accept = slots_left / 16; + else + num_to_accept = 8; + } + if (64 < num_to_accept) + num_to_accept = 64; + } + else if (num_conn < limit / 8) + { + num_to_accept = num_conn * 2 / num_workers; + if (8 > num_to_accept) + { + if (8 > slots_left / 8) + num_to_accept = slots_left / 8; + else + num_to_accept = 8; + } + if (128 < num_to_accept) + num_to_accept = 128; + } + else if (num_conn < limit / 4) + { + num_to_accept = num_conn * 4 / num_workers; + if (8 > num_to_accept) + num_to_accept = 8; + if (slots_left / 4 < num_to_accept) + num_to_accept = slots_left / 4; + if (256 < num_to_accept) + num_to_accept = 256; + } + else if (num_conn < limit / 2) + { + num_to_accept = num_conn * 8 / num_workers; + if (16 > num_to_accept) + num_to_accept = 16; + if (slots_left / 4 < num_to_accept) + num_to_accept = slots_left / 4; + if (256 < num_to_accept) + num_to_accept = 256; + } + else if (slots_left > limit / 4) + { + num_to_accept = slots_left * 4 / num_workers; + if (slots_left / 8 < num_to_accept) + num_to_accept = slots_left / 8; + if (128 < num_to_accept) + num_to_accept = 128; + } + else if (slots_left > limit / 8) + { + num_to_accept = slots_left * 2 / num_workers; + if (slots_left / 16 < num_to_accept) + num_to_accept = slots_left / 16; + if (64 < num_to_accept) + num_to_accept = 64; + } + else /* (slots_left <= limit / 8) */ + num_to_accept = slots_left / 16; + + if (0 == num_to_accept) + num_to_accept = 1; + else if (slots_left > num_to_accept) + num_to_accept = slots_left; + } +#endif /* MHD_USE_THREADS */ + } + + while (0 != --num_to_accept) + { + enum mhd_DaemonAcceptResult res; + res = mhd_daemon_accept_connection (d); + if (mhd_DAEMON_ACCEPT_NO_MORE_PENDING == res) + return true; + if (mhd_DAEMON_ACCEPT_FAILED == res) + break; + } + return false; +} + + +static bool +daemon_process_all_active_conns (struct MHD_Daemon *restrict d) +{ + struct MHD_Connection *c; + mhd_assert (! mhd_D_HAS_WORKERS (d)); + + c = mhd_DLINKEDL_GET_FIRST (&(d->events),proc_ready); + while (NULL != c) + { + struct MHD_Connection *next; + next = mhd_DLINKEDL_GET_NEXT (c, proc_ready); /* The current connection can be closed */ + if (! mhd_conn_process_recv_send_data (c)) + mhd_conn_close_final (c); + + c = next; + } + return true; +} + + +static void +close_all_daemon_conns (struct MHD_Daemon *d) +{ + struct MHD_Connection *c; + + for (c = mhd_DLINKEDL_GET_LAST (&(d->conns),all_conn); + NULL != c; + c = mhd_DLINKEDL_GET_LAST (&(d->conns),all_conn)) + { + mhd_conn_pre_close_d_shutdown (c); + mhd_conn_close_final (c); + } +} + + +#ifdef MHD_USE_SELECT + +/** + * Add socket to the fd_set + * @param fd the socket to add + * @param fs the pointer to fd_set + * @param max the pointer to variable to be updated with maximum FD value (or + * set to non-zero in case of WinSock) + * @param d the daemon object + */ +MHD_static_inline_ MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (2) +MHD_FN_PAR_INOUT_ (3) void +fd_set_wrap (MHD_Socket fd, + fd_set *restrict fs, + int *restrict max, + struct MHD_Daemon *restrict d) +{ + mhd_assert (mhd_FD_FITS_DAEMON (d, fd)); /* Must be checked for every FD before + it is added */ + mhd_assert (mhd_POLL_TYPE_SELECT == d->events.poll_type); + (void) d; /* Unused with non-debug builds */ +#if defined(MHD_POSIX_SOCKETS) + FD_SET (fd, fs); + if (*max < fd) + *max = fd; +#elif defined(MHD_WINSOCK_SOCKETS) + /* Use custom set function to take advantage of know uniqueness of + * used sockets (to skip useless (for this function) check for duplicated + * sockets implemented in system's macro). */ + mhd_assert (fs->fd_count < FD_SETSIZE - 1); /* Daemon limits set to always fit FD_SETSIZE */ + mhd_assert (! FD_ISSET (fd, fs)); /* All sockets must be unique */ + fs->fd_array[fs->fd_count++] = fd; + *max = 1; +#else +#error Unknown sockets type +#endif +} + + +/** + * Set daemon's FD_SETs to monitor all daemon's sockets + * @param d the daemon to use + * @param listen_only set to 'true' if connections's sockets should NOT + * be monitored + * @return with POSIX sockets: the maximum number of the socket used in + * the FD_SETs; + * with winsock: non-zero if at least one socket has been added to + * the FD_SETs, + * zero if no sockets in the FD_SETs + */ +static MHD_FN_PAR_NONNULL_ (1) int +select_update_fdsets (struct MHD_Daemon *restrict d, + bool listen_only) +{ + struct MHD_Connection *c; + fd_set *const restrict rfds = d->events.data.select.rfds; + fd_set *const restrict wfds = d->events.data.select.wfds; + fd_set *const restrict efds = d->events.data.select.efds; + int ret; + + mhd_assert (mhd_POLL_TYPE_SELECT == d->events.poll_type); + mhd_assert (NULL != rfds); + mhd_assert (NULL != wfds); + mhd_assert (NULL != efds); + FD_ZERO (rfds); + FD_ZERO (wfds); + FD_ZERO (efds); + + ret = 0; +#ifdef MHD_USE_THREADS + mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); + fd_set_wrap (mhd_itc_r_fd (d->threading.itc), + rfds, + &ret, + d); + fd_set_wrap (mhd_itc_r_fd (d->threading.itc), + efds, + &ret, + d); +#endif + if (MHD_INVALID_SOCKET != d->net.listen.fd) + { + fd_set_wrap (d->net.listen.fd, + rfds, + &ret, + d); + fd_set_wrap (d->net.listen.fd, + efds, + &ret, + d); + } + if (listen_only) + return ret; + + for (c = mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn); NULL != c; + c = mhd_DLINKEDL_GET_NEXT (c,all_conn)) + { + mhd_assert (MHD_CONNECTION_CLOSED != c->state); + + if (0 != (c->event_loop_info & MHD_EVENT_LOOP_INFO_READ)) + fd_set_wrap (c->socket_fd, + rfds, + &ret, + d); + if (0 != (c->event_loop_info & MHD_EVENT_LOOP_INFO_WRITE)) + fd_set_wrap (c->socket_fd, + wfds, + &ret, + d); + fd_set_wrap (c->socket_fd, + efds, + &ret, + d); + } + + return ret; +} + + +static MHD_FN_PAR_NONNULL_ (1) bool +select_update_statuses_from_fdsets (struct MHD_Daemon *d, + int num_events) +{ + struct MHD_Connection *c; + fd_set *const restrict rfds = d->events.data.select.rfds; + fd_set *const restrict wfds = d->events.data.select.wfds; + fd_set *const restrict efds = d->events.data.select.efds; + + mhd_assert (mhd_POLL_TYPE_SELECT == d->events.poll_type); + mhd_assert (0 <= num_events); + mhd_assert (((unsigned int) num_events) <= d->dbg.num_events_elements); + +#ifndef MHD_FAVOR_SMALL_CODE + if (0 == num_events) + return true; +#endif /* MHD_FAVOR_SMALL_CODE */ + +#ifdef MHD_USE_THREADS + mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); + if (FD_ISSET (mhd_itc_r_fd (d->threading.itc), efds)) + { + mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ + "System reported that ITC has an error status."); + /* ITC is broken, need to stop the daemon thread now as otherwise + application will not be able to stop the thread. */ + return false; + } + if (FD_ISSET (mhd_itc_r_fd (d->threading.itc), rfds)) + { + --num_events; + /* Clear ITC here, as before any other data processing. + * Any external events may activate ITC again if any data to process is + * added externally. Cleaning ITC early ensures guaranteed that new data + * will not be missed. */ + mhd_itc_clear (d->threading.itc); + } + +#ifndef MHD_FAVOR_SMALL_CODE + if (0 == num_events) + return true; +#endif /* MHD_FAVOR_SMALL_CODE */ +#endif /* MHD_USE_THREADS */ + + if (MHD_INVALID_SOCKET != d->net.listen.fd) + { + if (FD_ISSET (d->net.listen.fd, efds)) + { + --num_events; + mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ + "System reported that the listening socket has an error " \ + "status. The daemon will not listen any more."); + /* Close the listening socket unless the master daemon should close it */ + if (! mhd_D_HAS_MASTER (d)) + mhd_socket_close (d->net.listen.fd); + + /* Stop monitoring socket to avoid spinning with busy-waiting */ + d->net.listen.fd = MHD_INVALID_SOCKET; + } + else if (FD_ISSET (d->net.listen.fd, rfds)) + { + --num_events; + d->events.act_req.accept = true; + } + } + + mhd_assert ((0 == num_events) || \ + (mhd_DAEMON_TYPE_LISTEN_ONLY != d->threading.d_type)); + +#ifdef MHD_FAVOR_SMALL_CODE + (void) num_events; + num_events = 1; /* Use static value to optimise out the next look */ +#endif /* ! MHD_FAVOR_SMALL_CODE */ + + for (c = mhd_DLINKEDL_GET_FIRST (&(d->conns), all_conn); + (NULL != c) && (0 != num_events); + c = mhd_DLINKEDL_GET_NEXT (c, all_conn)) + { + const MHD_Socket sk = c->socket_fd; + bool recv_ready = FD_ISSET (sk, rfds); + bool send_ready = FD_ISSET (sk, wfds); + bool err_state = FD_ISSET (sk, efds); + + update_conn_net_status (d, + c, + recv_ready, + send_ready, + err_state); +#ifndef MHD_FAVOR_SMALL_CODE + if (recv_ready || send_ready || err_state) + --num_events; +#endif /* MHD_FAVOR_SMALL_CODE */ + } + + #ifndef MHD_FAVOR_SMALL_CODE + mhd_assert (0 == num_events); +#endif /* MHD_FAVOR_SMALL_CODE */ + return true; +} + + +/** + * Update states of all connections, check for connection pending + * to be accept()'ed, check for the events on ITC. + * @param listen_only set to 'true' if connections's sockets should NOT + * be monitored + * @return 'true' if processed successfully, + * 'false' is unrecoverable error occurs and the daemon must be + * closed + */ +static MHD_FN_PAR_NONNULL_ (1) bool +get_all_net_updates_by_select (struct MHD_Daemon *restrict d, + bool listen_only) +{ + int max_socket; + int max_wait; + struct timeval tmvl; + int num_events; + mhd_assert (mhd_POLL_TYPE_SELECT == d->events.poll_type); + + max_socket = select_update_fdsets (d, + listen_only); + + max_wait = get_max_wait (d); // TODO: use correct timeout value + +#ifdef MHD_WINSOCK_SOCKETS + if (0 == max_socket) + { + Sleep ((unsigned int) max_wait); + return true; + } +#endif /* MHD_WINSOCK_SOCKETS */ + + tmvl.tv_sec = max_wait / 1000; +#ifndef MHD_WINSOCK_SOCKETS + tmvl.tv_usec = (uint_least16_t) ((max_wait % 1000) * 1000); +#else + tmvl.tv_usec = (int) ((max_wait % 1000) * 1000); +#endif + + num_events = select (max_socket + 1, + d->events.data.select.rfds, + d->events.data.select.wfds, + d->events.data.select.efds, + &tmvl); + + if (0 > num_events) + { + int err; + bool is_hard_error; + bool is_ignored_error; + is_hard_error = false; + is_ignored_error = false; +#if defined(MHD_POSIX_SOCKETS) + err = errno; + if (0 != err) + { + is_hard_error = + ((mhd_EBADF_OR_ZERO == err) || (mhd_EINVAL_OR_ZERO == err)); + is_ignored_error = (mhd_EINTR_OR_ZERO == err); + } +#elif defined(MHD_WINSOCK_SOCKETS) + err = WSAGetLastError (); + is_hard_error = + ((WSAENETDOWN == err) || (WSAEFAULT == err) || (WSAEINVAL == err) || + (WSANOTINITIALISED == err)); +#endif + if (! is_ignored_error) + { + if (is_hard_error) + { + mhd_LOG_MSG (d, MHD_SC_SELECT_HARD_ERROR, \ + "The select() encountered unrecoverable error."); + return false; + } + mhd_LOG_MSG (d, MHD_SC_SELECT_SOFT_ERROR, \ + "The select() encountered error."); + return true; + } + } + + return select_update_statuses_from_fdsets (d, num_events); +} + + +#endif /* MHD_USE_SELECT */ + + +#ifdef MHD_USE_POLL + +static MHD_FN_PAR_NONNULL_ (1) unsigned int +poll_update_fds (struct MHD_Daemon *restrict d, + bool listen_only) +{ + unsigned int i_s; + unsigned int i_c; + struct MHD_Connection *restrict c; + mhd_assert (mhd_POLL_TYPE_POLL == d->events.poll_type); + + i_s = 0; +#ifdef MHD_USE_THREADS + mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); + mhd_assert (d->events.data.poll.fds[i_s].fd == \ + mhd_itc_r_fd (d->threading.itc)); + mhd_assert (mhd_SOCKET_REL_MARKER_ITC == \ + d->events.data.poll.rel[i_s].fd_id); + ++i_s; +#endif + if (MHD_INVALID_SOCKET != d->net.listen.fd) + { + mhd_assert (d->events.data.poll.fds[i_s].fd == d->net.listen.fd); + mhd_assert (mhd_SOCKET_REL_MARKER_LISTEN == \ + d->events.data.poll.rel[i_s].fd_id); + ++i_s; + } + if (listen_only) + return i_s; + + i_c = i_s; + for (c = mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn); NULL != c; + c = mhd_DLINKEDL_GET_NEXT (c,all_conn)) + { + unsigned short events; /* 'unsigned' for correct bits manipulations */ + mhd_assert ((i_c - i_s) < d->conns.cfg.count_limit); + mhd_assert (i_c < d->dbg.num_events_elements); + mhd_assert (MHD_CONNECTION_CLOSED != c->state); + + d->events.data.poll.fds[i_c].fd = c->socket_fd; + d->events.data.poll.rel[i_c].connection = c; + 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)) + events |= MHD_POLL_OUT; + + d->events.data.poll.fds[i_c].events = (short) events; + ++i_c; + } + mhd_assert (d->conns.count == (i_c - i_s)); + mhd_assert (i_c <= d->dbg.num_events_elements); + return i_c; +} + + +static MHD_FN_PAR_NONNULL_ (1) bool +poll_update_statuses_from_fds (struct MHD_Daemon *restrict d, + int num_events) +{ + unsigned int i_s; + unsigned int i_c; + mhd_assert (mhd_POLL_TYPE_POLL == d->events.poll_type); + mhd_assert (0 <= num_events); + mhd_assert (((unsigned int) num_events) <= d->dbg.num_events_elements); + + if (0 == num_events) + return true; + + i_s = 0; +#ifdef MHD_USE_THREADS + mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); + mhd_assert (d->events.data.poll.fds[i_s].fd == \ + mhd_itc_r_fd (d->threading.itc)); + mhd_assert (mhd_SOCKET_REL_MARKER_ITC == \ + d->events.data.poll.rel[i_s].fd_id); + if (0 != (d->events.data.poll.fds[i_s].revents & (POLLERR | POLLNVAL))) + { + mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ + "System reported that ITC has an error status."); + /* ITC is broken, need to stop the daemon thread now as otherwise + application will not be able to stop the thread. */ + return false; + } + if (0 != (d->events.data.poll.fds[i_s].revents & (MHD_POLL_IN | POLLIN))) + { + --num_events; + /* Clear ITC here, as before any other data processing. + * Any external events may activate ITC again if any data to process is + * added externally. Cleaning ITC early ensures guaranteed that new data + * will not be missed. */ + mhd_itc_clear (d->threading.itc); + } + ++i_s; + + if (0 == num_events) + return true; +#endif /* MHD_USE_THREADS */ + + if (MHD_INVALID_SOCKET != d->net.listen.fd) + { + const short revents = d->events.data.poll.fds[i_s].revents; + + mhd_assert (d->events.data.poll.fds[i_s].fd == d->net.listen.fd); + mhd_assert (mhd_SOCKET_REL_MARKER_LISTEN == \ + d->events.data.poll.rel[i_s].fd_id); + if (0 != (revents & (POLLERR | POLLNVAL | POLLHUP))) + { + --num_events; + mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ + "System reported that the listening socket has an error " \ + "status. The daemon will not listen any more."); + /* Close the listening socket unless the master daemon should close it */ + if (! mhd_D_HAS_MASTER (d)) + mhd_socket_close (d->net.listen.fd); + + /* Stop monitoring socket to avoid spinning with busy-waiting */ + d->net.listen.fd = MHD_INVALID_SOCKET; + } + else if (0 != + (d->events.data.poll.fds[i_s].revents & (MHD_POLL_IN | POLLIN))) + { + --num_events; + d->events.act_req.accept = true; + } + ++i_s; + } + + mhd_assert ((0 == num_events) || \ + (mhd_DAEMON_TYPE_LISTEN_ONLY != d->threading.d_type)); + + for (i_c = i_s; (i_c < i_s + d->conns.count) && (0 < num_events); ++i_c) + { + struct MHD_Connection *restrict c; + bool recv_ready; + bool send_ready; + bool err_state; + short revents; + mhd_assert (i_c < d->dbg.num_events_elements); + mhd_assert (mhd_SOCKET_REL_MARKER_EMPTY != \ + d->events.data.poll.rel[i_c].fd_id); + mhd_assert (mhd_SOCKET_REL_MARKER_ITC != \ + d->events.data.poll.rel[i_c].fd_id); + mhd_assert (mhd_SOCKET_REL_MARKER_LISTEN != \ + d->events.data.poll.rel[i_c].fd_id); + + c = d->events.data.poll.rel[i_c].connection; + mhd_assert (c->socket_fd == d->events.data.poll.fds[i_c].fd); + revents = d->events.data.poll.fds[i_c].revents; + recv_ready = (0 != (revents & (MHD_POLL_IN | POLLIN))); + send_ready = (0 != (revents & (MHD_POLL_OUT | POLLOUT))); +#ifndef MHD_POLLHUP_ON_REM_SHUT_WR + err_state = (0 != (revents & (POLLHUP | POLLERR | POLLNVAL))); +#else + err_state = (0 != (revents & (POLLERR | POLLNVAL))); + if (0 != (revents & POLLHUP)) + { /* This can be a disconnect OR remote side set SHUT_WR */ + recv_ready = true; /* Check the socket by reading */ + if (0 == (c->event_loop_info & MHD_EVENT_LOOP_INFO_READ)) + err_state = true; /* The socket will not be checked by reading, the only way to avoid spinning */ + } +#endif + if (0 != (revents & (MHD_POLLPRI | MHD_POLLRDBAND))) + { /* Statuses were not requested, but returned */ + if (! recv_ready || + (0 == (c->event_loop_info & MHD_EVENT_LOOP_INFO_READ))) + err_state = true; /* The socket will not be read, the only way to avoid spinning */ + } + if (0 != (revents & MHD_POLLWRBAND)) + { /* Status was not requested, but returned */ + if (! send_ready || + (0 == (c->event_loop_info & MHD_EVENT_LOOP_INFO_WRITE))) + err_state = true; /* The socket will not be written, the only way to avoid spinning */ + } + + update_conn_net_status (d, c, recv_ready, send_ready, err_state); + } + mhd_assert (d->conns.count >= (i_c - i_s)); + mhd_assert (i_c <= d->dbg.num_events_elements); + return true; +} + + +static MHD_FN_PAR_NONNULL_ (1) bool +get_all_net_updates_by_poll (struct MHD_Daemon *restrict d, + bool listen_only) +{ + unsigned int num_fds; + int num_events; + mhd_assert (mhd_POLL_TYPE_POLL == d->events.poll_type); + + num_fds = poll_update_fds (d, listen_only); + + // TODO: handle empty list situation + + num_events = mhd_poll (d->events.data.poll.fds, + num_fds, + get_max_wait (d)); // TODO: use correct timeout value + if (0 > num_events) + { + int err; + bool is_hard_error; + bool is_ignored_error; + is_hard_error = false; + is_ignored_error = false; +#if defined(MHD_POSIX_SOCKETS) + err = errno; + if (0 != err) + { + is_hard_error = + ((mhd_EFAULT_OR_ZERO == err) || (mhd_EINVAL_OR_ZERO == err)); + is_ignored_error = (mhd_EINTR_OR_ZERO == err); + } +#elif defined(MHD_WINSOCK_SOCKETS) + err = WSAGetLastError (); + is_hard_error = + ((WSAENETDOWN == err) || (WSAEFAULT == err) || (WSAEINVAL == err)); +#endif + if (! is_ignored_error) + { + if (is_hard_error) + { + mhd_LOG_MSG (d, MHD_SC_POLL_HARD_ERROR, \ + "The poll() encountered unrecoverable error."); + return false; + } + mhd_LOG_MSG (d, MHD_SC_POLL_SOFT_ERROR, \ + "The poll() encountered error."); + } + return true; + } + + return poll_update_statuses_from_fds (d, num_events); +} + + +#endif /* MHD_USE_POLL */ + +#ifdef MHD_USE_EPOLL + +/** + * Map events provided by epoll to connection states, ITC and + * listen socket states + */ +static MHD_FN_PAR_NONNULL_ (1) bool +poll_update_statuses_from_eevents (struct MHD_Daemon *restrict d, + unsigned int num_events) +{ + unsigned int i; + struct epoll_event *const restrict events = + d->events.data.epoll.events; + for (i = 0; num_events > i; ++i) + { + struct epoll_event *const e = events + i; + if (((uint64_t) mhd_SOCKET_REL_MARKER_ITC) == e->data.u64) /* uint64_t is in the system header */ + { + if (0 != (e->events & (EPOLLPRI | EPOLLERR | EPOLLHUP))) + { + mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ + "System reported that ITC has an error status."); + /* ITC is broken, need to stop the daemon thread now as otherwise + application will not be able to stop the thread. */ + return false; + } + if (0 != (e->events & EPOLLIN)) + { + /* Clear ITC here, as before any other data processing. + * Any external events may activate ITC again if any data to process is + * added externally. Cleaning ITC early ensures guaranteed that new data + * will not be missed. */ + mhd_itc_clear (d->threading.itc); + } + } + else if (((uint64_t) mhd_SOCKET_REL_MARKER_LISTEN) == e->data.u64) /* uint64_t is in the system header */ + { + if (0 != (e->events & (EPOLLPRI | EPOLLERR | EPOLLHUP))) + { + mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ + "System reported that the listening socket has an error " \ + "status. The daemon will not listen any more."); + + // TODO: remove listen from epoll monitoring + + /* Close the listening socket unless the master daemon should close it */ + if (! mhd_D_HAS_MASTER (d)) + mhd_socket_close (d->net.listen.fd); + + /* Stop monitoring socket to avoid spinning with busy-waiting */ + d->net.listen.fd = MHD_INVALID_SOCKET; + } + if (0 != (e->events & EPOLLIN)) + d->events.act_req.accept = true; + } + else + { + bool recv_ready; + bool send_ready; + bool err_state; + struct MHD_Connection *const restrict c = + (struct MHD_Connection *) e->data.ptr; + recv_ready = (0 != (e->events & (EPOLLIN | EPOLLERR | EPOLLHUP))); + send_ready = (0 != (e->events & (EPOLLOUT | EPOLLERR | EPOLLHUP))); + err_state = (0 != (e->events & (EPOLLERR | EPOLLHUP))); + + update_conn_net_status (d, c, recv_ready, send_ready, err_state); + } + } + return true; +} + + +/** + * Update states of all connections, check for connection pending + * to be accept()'ed, check for the events on ITC. + */ +static MHD_FN_PAR_NONNULL_ (1) bool +get_all_net_updates_by_epoll (struct MHD_Daemon *restrict d) +{ + int num_events; + unsigned int events_processed; + int max_wait; + mhd_assert (mhd_POLL_TYPE_EPOLL == d->events.poll_type); + mhd_assert (0 < ((int) d->events.data.epoll.num_elements)); + mhd_assert (d->events.data.epoll.num_elements == \ + (size_t) ((int) d->events.data.epoll.num_elements)); + mhd_assert (0 != d->events.data.epoll.num_elements); + mhd_assert (0 != d->conns.cfg.count_limit); + mhd_assert (d->events.data.epoll.num_elements == d->dbg.num_events_elements); + + // TODO: add listen socket enable/disable + + events_processed = 0; + max_wait = get_max_wait (d); // TODO: use correct timeout value + do + { + num_events = epoll_wait (d->events.data.epoll.e_fd, + d->events.data.epoll.events, + (int) d->events.data.epoll.num_elements, + max_wait); + max_wait = 0; + if (0 > num_events) + { + const int err = errno; + if (EINTR != err) + { + mhd_LOG_MSG (d, MHD_SC_EPOLL_HARD_ERROR, \ + "The epoll_wait() encountered unrecoverable error."); + return false; + } + return true; /* EINTR, try next time */ + } + if (! poll_update_statuses_from_eevents (d, (unsigned int) num_events)) + return false; + + events_processed += (unsigned int) num_events; /* Avoid reading too many events */ + } while ((((unsigned int) num_events) == d->events.data.epoll.num_elements) && + (events_processed < d->conns.cfg.count_limit + 2)); + + return true; +} + + +#endif /* MHD_USE_EPOLL */ + + +static MHD_FN_PAR_NONNULL_ (1) bool +process_all_events_and_data (struct MHD_Daemon *restrict d) +{ + switch (d->events.poll_type) + { + case mhd_POLL_TYPE_EXT: + return false; // TODO: implement + break; +#ifdef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: + if (! get_all_net_updates_by_select (d, false)) + return false; + break; +#endif /* MHD_USE_SELECT */ +#ifdef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: + if (! get_all_net_updates_by_poll (d, false)) + return false; + break; +#endif /* MHD_USE_POLL */ +#ifdef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: + if (! get_all_net_updates_by_epoll (d)) + return false; + break; +#endif /* MHD_USE_EPOLL */ +#ifndef MHD_USE_SELECT + case mhd_POLL_TYPE_SELECT: +#endif /* ! MHD_USE_SELECT */ +#ifndef MHD_USE_POLL + case mhd_POLL_TYPE_POLL: +#endif /* ! MHD_USE_POLL */ +#ifndef MHD_USE_EPOLL + case mhd_POLL_TYPE_EPOLL: +#endif /* ! MHD_USE_EPOLL */ + case mhd_POLL_TYPE_NOT_SET_YET: + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + MHD_PANIC ("Daemon data integrity broken"); + } + if (d->events.act_req.accept) + { + if (daemon_accept_new_conns (d)) + d->events.act_req.accept = false; + else if (! d->net.listen.non_block) + d->events.act_req.accept = false; + } + daemon_process_all_active_conns (d); + return ! d->threading.stop_requested; +} + + +/** + * The entry point for the daemon worker thread + * @param cls the closure + */ +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)); + mhd_assert (mhd_D_TYPE_HAS_EVENTS_PROCESSING (d->threading.d_type)); + mhd_assert (mhd_DAEMON_TYPE_LISTEN_ONLY != d->threading.d_type); + mhd_assert (! mhd_D_TYPE_HAS_WORKERS (d->threading.d_type)); + mhd_assert (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != d->wmode_int); + mhd_assert (d->dbg.events_fully_inited); + mhd_assert (d->dbg.connections_inited); + + while (! d->threading.stop_requested) + { + if (d->threading.resume_requested) + mhd_daemon_resume_conns (d); + + if (! process_all_events_and_data (d)) + break; + } + 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 for the daemon."); + } + close_all_daemon_conns (d); + + return (mhd_THRD_RTRN_TYPE) 0; +} + + +static MHD_FN_PAR_NONNULL_ (1) bool +process_listening_and_itc_only (struct MHD_Daemon *restrict d) +{ + if (false) + (void) 0; +#ifdef MHD_USE_SELECT + else if (mhd_POLL_TYPE_SELECT == d->events.poll_type) + { + return false; // TODO: implement + } +#endif /* MHD_USE_SELECT */ +#ifdef MHD_USE_POLL + else if (mhd_POLL_TYPE_POLL == d->events.poll_type) + { + if (! get_all_net_updates_by_poll (d, true)) + return false; + } +#endif /* MHD_USE_POLL */ + else + { + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + MHD_PANIC ("Daemon data integrity broken"); + } + // TODO: Accept connections + return false; +} + + +/** + * The entry point for the daemon listening thread + * @param cls the closure + */ +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); + mhd_assert (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION == d->wmode_int); + mhd_assert (d->dbg.events_fully_inited); + mhd_assert (d->dbg.connections_inited); + + while (! d->threading.stop_requested) + { + if (! process_listening_and_itc_only (d)) + break; + } + 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."); + } + return (mhd_THRD_RTRN_TYPE) 0; +} + + +mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC +mhd_worker_connection (void *cls) +{ + (void) cls; + mhd_assert (0 && "Not yet implemented"); + return (mhd_THRD_RTRN_TYPE) 0; +} diff --git a/src/mhd2/events_process.h b/src/mhd2/events_process.h @@ -0,0 +1,55 @@ +/* + 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/events_process.h + * @brief The declarations of events processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_EVENTS_PROCESS_H +#define MHD_EVENTS_PROCESS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_thread_entry_type.h" + +/** + * The entry point for the daemon worker thread + * @param cls the closure + */ +mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC +mhd_worker_all_events (void *cls); + +/** + * The entry point for the daemon listening thread + * @param cls the closure + */ +mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC +mhd_worker_listening_only (void *cls); + +/** + * The entry point for the connection thread for thread-per-connection mode + * @param cls the closure + */ +mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC +mhd_worker_connection (void *cls); + +#endif /* ! MHD_EVENTS_PROCESS_H */ diff --git a/src/mhd2/http_method.h b/src/mhd2/http_method.h @@ -0,0 +1,179 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2021-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/http_method.h + * @brief The definition of the enums for the HTTP methods + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_HTTP_METHOD_H +#define MHD_HTTP_METHOD_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_HTTP_METHOD_DEFINED + +enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_Method +{ + + /** + * Method did not match any of the methods given below. + */ + MHD_HTTP_METHOD_OTHER = 255 + , + /* Main HTTP methods. */ + + /** + * "GET" + * Safe. Idempotent. RFC9110, Section 9.3.1. + */ + MHD_HTTP_METHOD_GET = 1 + , + /** + * "HEAD" + * Safe. Idempotent. RFC9110, Section 9.3.2. + */ + MHD_HTTP_METHOD_HEAD = 2 + , + /** + * "POST" + * Not safe. Not idempotent. RFC9110, Section 9.3.3. + */ + MHD_HTTP_METHOD_POST = 3 + , + /** + * "PUT" + * Not safe. Idempotent. RFC9110, Section 9.3.4. + */ + MHD_HTTP_METHOD_PUT = 4 + , + /** + * "DELETE" + * Not safe. Idempotent. RFC9110, Section 9.3.5. + */ + MHD_HTTP_METHOD_DELETE = 5 + , + /** + * "CONNECT" + * Not safe. Not idempotent. RFC9110, Section 9.3.6. + */ + MHD_HTTP_METHOD_CONNECT = 6 + , + /** + * "OPTIONS" + * Safe. Idempotent. RFC9110, Section 9.3.7. + */ + MHD_HTTP_METHOD_OPTIONS = 7 + , + /** + * "TRACE" + * Safe. Idempotent. RFC9110, Section 9.3.8. + */ + MHD_HTTP_METHOD_TRACE = 8 + , + /** + * "*" + * Not safe. Not idempotent. RFC9110, Section 18.2. + */ + MHD_HTTP_METHOD_ASTERISK = 9 +}; + + +#define MHD_HTTP_METHOD_DEFINED 1 +#endif /* ! MHD_HTTP_METHOD_DEFINED */ + +/** + * Internal version of MHD_HTTP_Method + * Extended with the #mhd_HTTP_METHOD_NO_METHOD value + */ +enum MHD_FIXED_ENUM_ mhd_HTTP_Method +{ + + /** + * No method has been detected yet + */ + mhd_HTTP_METHOD_NO_METHOD = 0 + , + + /** + * Method did not match any of the methods given below. + */ + mhd_HTTP_METHOD_OTHER = MHD_HTTP_METHOD_OTHER + , + /* Main HTTP methods. */ + + /** + * "GET" + * Safe. Idempotent. RFC9110, Section 9.3.1. + */ + mhd_HTTP_METHOD_GET = MHD_HTTP_METHOD_GET + , + /** + * "HEAD" + * Safe. Idempotent. RFC9110, Section 9.3.2. + */ + mhd_HTTP_METHOD_HEAD = MHD_HTTP_METHOD_HEAD + , + /** + * "POST" + * Not safe. Not idempotent. RFC9110, Section 9.3.3. + */ + mhd_HTTP_METHOD_POST = MHD_HTTP_METHOD_POST + , + /** + * "PUT" + * Not safe. Idempotent. RFC9110, Section 9.3.4. + */ + mhd_HTTP_METHOD_PUT = MHD_HTTP_METHOD_PUT + , + /** + * "DELETE" + * Not safe. Idempotent. RFC9110, Section 9.3.5. + */ + mhd_HTTP_METHOD_DELETE = MHD_HTTP_METHOD_DELETE + , + /** + * "CONNECT" + * Not safe. Not idempotent. RFC9110, Section 9.3.6. + */ + mhd_HTTP_METHOD_CONNECT = MHD_HTTP_METHOD_CONNECT + , + /** + * "OPTIONS" + * Safe. Idempotent. RFC9110, Section 9.3.7. + */ + mhd_HTTP_METHOD_OPTIONS = MHD_HTTP_METHOD_OPTIONS + , + /** + * "TRACE" + * Safe. Idempotent. RFC9110, Section 9.3.8. + */ + mhd_HTTP_METHOD_TRACE = MHD_HTTP_METHOD_TRACE + , + /** + * "*" + * Not safe. Not idempotent. RFC9110, Section 18.2. + */ + mhd_HTTP_METHOD_ASTERISK = MHD_HTTP_METHOD_ASTERISK +}; + +#define MHD_HTTP_METHOD_DEFINED 1 +#endif /* ! MHD_HTTP_METHOD_H */ diff --git a/src/mhd2/http_prot_ver.h b/src/mhd2/http_prot_ver.h @@ -0,0 +1,71 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2021-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/http_prot_ver.h + * @brief The definition of the HTTP versions enum + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_HTTP_PROT_VER_H +#define MHD_HTTP_PROT_VER_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_HTTP_PROTOCOL_VER_DEFINED + +/** + * @brief HTTP protocol versions + * @defgroup versions HTTP versions + * @{ + */ +enum MHD_FIXED_ENUM_MHD_SET_ MHD_HTTP_ProtocolVersion +{ + MHD_HTTP_VERSION_INVALID = 0 + , + MHD_HTTP_VERSION_1_0 = 1 + , + MHD_HTTP_VERSION_1_1 = 2 + , + MHD_HTTP_VERSION_2 = 3 + , + MHD_HTTP_VERSION_3 = 4 + , + MHD_HTTP_VERSION_FUTURE = 255 +}; + + +#define MHD_HTTP_PROTOCOL_VER_DEFINED 1 +#endif /* ! MHD_HTTP_PROTOCOL_VER_DEFINED */ + +/** + * Check whether version of HTTP protocol is supported + */ +#define MHD_HTTP_VERSION_IS_SUPPORTED(v) \ + ((MHD_HTTP_VERSION_1_0 <= (v)) && (MHD_HTTP_VERSION_1_1 >= (v))) + +/** + * Check whether version of HTTP protocol is valid + */ +#define MHD_HTTP_VERSION_IS_VALID(v) \ + (((MHD_HTTP_VERSION_1_0 <= (v)) && (MHD_HTTP_VERSION_3 >= (v))) || \ + (MHD_HTTP_VERSION_FUTURE == (v))) + +#endif /* ! MHD_HTTP_PROT_VER_H */ diff --git a/src/mhd2/http_status_str.c b/src/mhd2/http_status_str.c @@ -0,0 +1,209 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2017-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007, 2011, 2017, 2019 Christian Grothoff + + This library 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. + + This library 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/http_status_str.c + * @brief Tables of the string response phrases + * @author Elliot Glaysher + * @author Christian Grothoff (minor code clean up) + * @author Karlson2k (Evgeny Grin) (massively refactored and updated) + */ +#include "mhd_sys_options.h" + +#include "http_status_str.h" + +#include "sys_base_types.h" +#include "mhd_public_api.h" +#include "mhd_str_macros.h" + +#define UNUSED_STATUS {0, NULL} + +static const struct MHD_String invalid_hundred[] = { + { 0, NULL } +}; + +static const struct MHD_String one_hundred[] = { + /* 100 */ mhd_MSTR_INIT ("Continue"), /* RFC9110, Section 15.2.1 */ + /* 101 */ mhd_MSTR_INIT ("Switching Protocols"), /* RFC9110, Section 15.2.2 */ + /* 102 */ mhd_MSTR_INIT ("Processing"), /* RFC2518 */ + /* 103 */ mhd_MSTR_INIT ("Early Hints") /* RFC8297 */ +}; + +static const struct MHD_String two_hundred[] = { + /* 200 */ mhd_MSTR_INIT ("OK"), /* RFC9110, Section 15.3.1 */ + /* 201 */ mhd_MSTR_INIT ("Created"), /* RFC9110, Section 15.3.2 */ + /* 202 */ mhd_MSTR_INIT ("Accepted"), /* RFC9110, Section 15.3.3 */ + /* 203 */ mhd_MSTR_INIT ("Non-Authoritative Information"), /* RFC9110, Section 15.3.4 */ + /* 204 */ mhd_MSTR_INIT ("No Content"), /* RFC9110, Section 15.3.5 */ + /* 205 */ mhd_MSTR_INIT ("Reset Content"), /* RFC9110, Section 15.3.6 */ + /* 206 */ mhd_MSTR_INIT ("Partial Content"), /* RFC9110, Section 15.3.7 */ + /* 207 */ mhd_MSTR_INIT ("Multi-Status"), /* RFC4918 */ + /* 208 */ mhd_MSTR_INIT ("Already Reported"), /* RFC5842 */ + /* 209 */ UNUSED_STATUS, /* Not used */ + /* 210 */ UNUSED_STATUS, /* Not used */ + /* 211 */ UNUSED_STATUS, /* Not used */ + /* 212 */ UNUSED_STATUS, /* Not used */ + /* 213 */ UNUSED_STATUS, /* Not used */ + /* 214 */ UNUSED_STATUS, /* Not used */ + /* 215 */ UNUSED_STATUS, /* Not used */ + /* 216 */ UNUSED_STATUS, /* Not used */ + /* 217 */ UNUSED_STATUS, /* Not used */ + /* 218 */ UNUSED_STATUS, /* Not used */ + /* 219 */ UNUSED_STATUS, /* Not used */ + /* 220 */ UNUSED_STATUS, /* Not used */ + /* 221 */ UNUSED_STATUS, /* Not used */ + /* 222 */ UNUSED_STATUS, /* Not used */ + /* 223 */ UNUSED_STATUS, /* Not used */ + /* 224 */ UNUSED_STATUS, /* Not used */ + /* 225 */ UNUSED_STATUS, /* Not used */ + /* 226 */ mhd_MSTR_INIT ("IM Used") /* RFC3229 */ +}; + +static const struct MHD_String three_hundred[] = { + /* 300 */ mhd_MSTR_INIT ("Multiple Choices"), /* RFC9110, Section 15.4.1 */ + /* 301 */ mhd_MSTR_INIT ("Moved Permanently"), /* RFC9110, Section 15.4.2 */ + /* 302 */ mhd_MSTR_INIT ("Found"), /* RFC9110, Section 15.4.3 */ + /* 303 */ mhd_MSTR_INIT ("See Other"), /* RFC9110, Section 15.4.4 */ + /* 304 */ mhd_MSTR_INIT ("Not Modified"), /* RFC9110, Section 15.4.5 */ + /* 305 */ mhd_MSTR_INIT ("Use Proxy"), /* RFC9110, Section 15.4.6 */ + /* 306 */ mhd_MSTR_INIT ("Switch Proxy"), /* Not used! RFC9110, Section 15.4.7 */ + /* 307 */ mhd_MSTR_INIT ("Temporary Redirect"), /* RFC9110, Section 15.4.8 */ + /* 308 */ mhd_MSTR_INIT ("Permanent Redirect") /* RFC9110, Section 15.4.9 */ +}; + +static const struct MHD_String four_hundred[] = { + /* 400 */ mhd_MSTR_INIT ("Bad Request"), /* RFC9110, Section 15.5.1 */ + /* 401 */ mhd_MSTR_INIT ("Unauthorized"), /* RFC9110, Section 15.5.2 */ + /* 402 */ mhd_MSTR_INIT ("Payment Required"), /* RFC9110, Section 15.5.3 */ + /* 403 */ mhd_MSTR_INIT ("Forbidden"), /* RFC9110, Section 15.5.4 */ + /* 404 */ mhd_MSTR_INIT ("Not Found"), /* RFC9110, Section 15.5.5 */ + /* 405 */ mhd_MSTR_INIT ("Method Not Allowed"), /* RFC9110, Section 15.5.6 */ + /* 406 */ mhd_MSTR_INIT ("Not Acceptable"), /* RFC9110, Section 15.5.7 */ + /* 407 */ mhd_MSTR_INIT ("Proxy Authentication Required"), /* RFC9110, Section 15.5.8 */ + /* 408 */ mhd_MSTR_INIT ("Request Timeout"), /* RFC9110, Section 15.5.9 */ + /* 409 */ mhd_MSTR_INIT ("Conflict"), /* RFC9110, Section 15.5.10 */ + /* 410 */ mhd_MSTR_INIT ("Gone"), /* RFC9110, Section 15.5.11 */ + /* 411 */ mhd_MSTR_INIT ("Length Required"), /* RFC9110, Section 15.5.12 */ + /* 412 */ mhd_MSTR_INIT ("Precondition Failed"), /* RFC9110, Section 15.5.13 */ + /* 413 */ mhd_MSTR_INIT ("Content Too Large"), /* RFC9110, Section 15.5.14 */ + /* 414 */ mhd_MSTR_INIT ("URI Too Long"), /* RFC9110, Section 15.5.15 */ + /* 415 */ mhd_MSTR_INIT ("Unsupported Media Type"), /* RFC9110, Section 15.5.16 */ + /* 416 */ mhd_MSTR_INIT ("Range Not Satisfiable"), /* RFC9110, Section 15.5.17 */ + /* 417 */ mhd_MSTR_INIT ("Expectation Failed"), /* RFC9110, Section 15.5.18 */ + /* 418 */ UNUSED_STATUS, /* Not used */ + /* 419 */ UNUSED_STATUS, /* Not used */ + /* 420 */ UNUSED_STATUS, /* Not used */ + /* 421 */ mhd_MSTR_INIT ("Misdirected Request"), /* RFC9110, Section 15.5.20 */ + /* 422 */ mhd_MSTR_INIT ("Unprocessable Content"), /* RFC9110, Section 15.5.21 */ + /* 423 */ mhd_MSTR_INIT ("Locked"), /* RFC4918 */ + /* 424 */ mhd_MSTR_INIT ("Failed Dependency"), /* RFC4918 */ + /* 425 */ mhd_MSTR_INIT ("Too Early"), /* RFC8470 */ + /* 426 */ mhd_MSTR_INIT ("Upgrade Required"), /* RFC9110, Section 15.5.22 */ + /* 427 */ UNUSED_STATUS, /* Not used */ + /* 428 */ mhd_MSTR_INIT ("Precondition Required"), /* RFC6585 */ + /* 429 */ mhd_MSTR_INIT ("Too Many Requests"), /* RFC6585 */ + /* 430 */ UNUSED_STATUS, /* Not used */ + /* 431 */ mhd_MSTR_INIT ("Request Header Fields Too Large"), /* RFC6585 */ + /* 432 */ UNUSED_STATUS, /* Not used */ + /* 433 */ UNUSED_STATUS, /* Not used */ + /* 434 */ UNUSED_STATUS, /* Not used */ + /* 435 */ UNUSED_STATUS, /* Not used */ + /* 436 */ UNUSED_STATUS, /* Not used */ + /* 437 */ UNUSED_STATUS, /* Not used */ + /* 438 */ UNUSED_STATUS, /* Not used */ + /* 439 */ UNUSED_STATUS, /* Not used */ + /* 440 */ UNUSED_STATUS, /* Not used */ + /* 441 */ UNUSED_STATUS, /* Not used */ + /* 442 */ UNUSED_STATUS, /* Not used */ + /* 443 */ UNUSED_STATUS, /* Not used */ + /* 444 */ UNUSED_STATUS, /* Not used */ + /* 445 */ UNUSED_STATUS, /* Not used */ + /* 446 */ UNUSED_STATUS, /* Not used */ + /* 447 */ UNUSED_STATUS, /* Not used */ + /* 448 */ UNUSED_STATUS, /* Not used */ + /* 449 */ mhd_MSTR_INIT ("Reply With"), /* MS IIS extension */ + /* 450 */ mhd_MSTR_INIT ("Blocked by Windows Parental Controls"), /* MS extension */ + /* 451 */ mhd_MSTR_INIT ("Unavailable For Legal Reasons") /* RFC7725 */ +}; + +static const struct MHD_String five_hundred[] = { + /* 500 */ mhd_MSTR_INIT ("Internal Server Error"), /* RFC9110, Section 15.6.1 */ + /* 501 */ mhd_MSTR_INIT ("Not Implemented"), /* RFC9110, Section 15.6.2 */ + /* 502 */ mhd_MSTR_INIT ("Bad Gateway"), /* RFC9110, Section 15.6.3 */ + /* 503 */ mhd_MSTR_INIT ("Service Unavailable"), /* RFC9110, Section 15.6.4 */ + /* 504 */ mhd_MSTR_INIT ("Gateway Timeout"), /* RFC9110, Section 15.6.5 */ + /* 505 */ mhd_MSTR_INIT ("HTTP Version Not Supported"), /* RFC9110, Section 15.6.6 */ + /* 506 */ mhd_MSTR_INIT ("Variant Also Negotiates"), /* RFC2295 */ + /* 507 */ mhd_MSTR_INIT ("Insufficient Storage"), /* RFC4918 */ + /* 508 */ mhd_MSTR_INIT ("Loop Detected"), /* RFC5842 */ + /* 509 */ mhd_MSTR_INIT ("Bandwidth Limit Exceeded"), /* Apache extension */ + /* 510 */ mhd_MSTR_INIT ("Not Extended"), /* (OBSOLETED) RFC2774; status-change-http-experiments-to-historic */ + /* 511 */ mhd_MSTR_INIT ("Network Authentication Required") /* RFC6585 */ +}; + + +struct mhd_HttpStatusesBlock +{ + size_t num_elmnts; + const struct MHD_String *const data; +}; + +#define STATUSES_BLOCK(m) { (sizeof(m) / sizeof(m[0])), m} + +static const struct mhd_HttpStatusesBlock statuses[] = { + STATUSES_BLOCK (invalid_hundred), + STATUSES_BLOCK (one_hundred), + STATUSES_BLOCK (two_hundred), + STATUSES_BLOCK (three_hundred), + STATUSES_BLOCK (four_hundred), + STATUSES_BLOCK (five_hundred) +}; + +MHD_EXTERN_ MHD_FN_CONST_ const struct MHD_String * +MHD_HTTP_status_code_to_string (enum MHD_HTTP_StatusCode code) +{ + const struct MHD_String *res; + const unsigned int code_i = (unsigned int) code; + if (100 > code_i) + return NULL; + if (600 < code) + return NULL; + 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) + return NULL; + return res; +} + + +MHD_INTERNAL MHD_FN_CONST_ const struct MHD_String * +mhd_HTTP_status_code_to_string_int (uint_fast16_t code) +{ + static const struct MHD_String no_status = + mhd_MSTR_INIT ("Nonstandard Status"); + const struct MHD_String *res; + + res = MHD_HTTP_status_code_to_string ((enum MHD_HTTP_StatusCode) code); + if (NULL != res) + return res; + + return &no_status; +} diff --git a/src/mhd2/http_status_str.h b/src/mhd2/http_status_str.h @@ -0,0 +1,47 @@ +/* + 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/http_status_str.h + * @brief The declaration for internal HTTP status string functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_HTTP_STATUS_STR_H +#define MHD_HTTP_STATUS_STR_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "mhd_str_types.h" + +/** + * Get string for provided HTTP status code. + * Substitute a replacement string for unknown codes. + * + * @param code the HTTP status code + * @return pointer to MHD_String, never NULL. + */ +MHD_INTERNAL const struct MHD_String * +mhd_HTTP_status_code_to_string_int (uint_fast16_t code) +MHD_FN_CONST_; + + +#endif /* ! MHD_HTTP_STATUS_STR_H */ diff --git a/libmicrohttpd.pc.in b/src/mhd2/libmicrohttpd2.pc.in diff --git a/src/mhd2/mhd_action.h b/src/mhd2/mhd_action.h @@ -0,0 +1,308 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_action.h + * @brief The definition of the MHD_Action and MHD_UploadAction structures + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_ACTION_H +#define MHD_ACTION_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#include "mhd_str_types.h" + + +/** + * The type of the action requested by application + */ +enum mhd_ActionType +{ + /** + * Action has not been set yet. + */ + mhd_ACTION_NO_ACTION = 0 + , + /** + * Start replying with the response + */ + mhd_ACTION_RESPONSE + , + /** + * Process clients upload by application callback + */ + mhd_ACTION_UPLOAD + , + /** + * Process clients upload by POST processor + */ + mhd_ACTION_POST_PROCESS + , + /** + * Suspend requests (connection) + */ + mhd_ACTION_SUSPEND + , + /** + * Hard close request with no response + */ + mhd_ACTION_ABORT +}; + +/** + * Check whether provided mhd_ActionType value is valid + */ +#define mhd_ACTION_IS_VALID(act) \ + ((mhd_ACTION_RESPONSE <= (act)) && (mhd_ACTION_ABORT >= (act))) + + +struct MHD_Response; /* forward declaration */ +struct MHD_Request; /* forward declaration */ + +#ifndef MHD_UPLOADCALLBACK_DEFINED + +typedef const struct MHD_UploadAction * +(MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_INOUT_SIZE_ (4,3) + *MHD_UploadCallback)(void *upload_cls, + struct MHD_Request *request, + size_t content_data_size, + void *content_data); + +#define MHD_UPLOADCALLBACK_DEFINED 1 +#endif /* ! MHD_UPLOADCALLBACK_DEFINED */ + +/** + * Upload callback data + */ +struct mhd_UploadCallbackData +{ + /** + * The callback + */ + MHD_UploadCallback cb; + + /** + * The closure for @a cb + */ + void *cls; +}; + +/** + * The data for upload callbacks + */ +struct mhd_UploadCallbacks +{ + /** + * The size of the buffer for the @a full upload callback + */ + size_t large_buffer_size; + + /** + * The data for the callback that processes only complete upload + */ + struct mhd_UploadCallbackData full; + + /** + * The data for the callback that processes only incremental uploads + */ + struct mhd_UploadCallbackData inc; +}; + +#ifndef MHD_POST_DATA_READER_DEFINED + +typedef const struct MHD_UploadAction * +(*MHD_PostDataReader) (void *cls, + const struct MHD_String *name, + const struct MHD_String *filename, + const struct MHD_String *content_type, + const struct MHD_String *encoding, + const void *data, + uint_fast64_t off, + size_t size); + + +typedef const struct MHD_UploadAction * +(*MHD_PostDataFinished) (struct MHD_Request *req, + void *cls); + +#define MHD_POST_DATA_READER_DEFINED 1 +#endif /* ! MHD_POST_DATA_READER_DEFINED */ + +#ifndef MHD_HTTP_POSTENCODING_DEFINED + +enum MHD_FIXED_ENUM_MHD_APP_SET_ MHD_HTTP_PostEncoding +{ + /** + * No post encoding / broken data / unknown encoding + */ + MHD_HTTP_POST_ENCODING_OTHER = 0 + , + /** + * "application/x-www-form-urlencoded" + * See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#url-encoded-form-data + * See https://url.spec.whatwg.org/#application/x-www-form-urlencoded + * See https://datatracker.ietf.org/doc/html/rfc3986#section-2 + */ + MHD_HTTP_POST_ENCODING_FORM_URLENCODED = 1 + , + /** + * "multipart/form-data" + * See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data + * See https://www.rfc-editor.org/rfc/rfc7578.html + */ + MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA = 2 + , + /** + * "text/plain" + * Introduced by HTML5 + * See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data + * @warning Format is ambiguous. Do not use unless there is a very strong reason. + */ + MHD_HTTP_POST_ENCODING_TEXT_PLAIN = 3 +}; + + +/** @} */ /* end of group postenc */ + +#define MHD_HTTP_POSTENCODING_DEFINED 1 +#endif /* ! MHD_HTTP_POSTENCODING_DEFINED */ + + +// TODO: correct and describe +struct mhd_PostProcessorActionData +{ + size_t pp_buffer_size; + size_t pp_stream_limit; // FIXME: Remove? Duplicated with pp_buffer_size + enum MHD_HTTP_PostEncoding enc; + MHD_PostDataReader reader; + void *reader_cls; + MHD_PostDataFinished done_cb; + void *done_cb_cls; +}; + +/** + * The data for the application action + */ +union mhd_ActionData +{ + /** + * The data for the action #mhd_ACTION_RESPONSE + */ + struct MHD_Response *response; + + /** + * The data for the action #mhd_ACTION_UPLOAD + */ + struct mhd_UploadCallbacks upload; + + /** + * The data for the action #mhd_ACTION_POST_PROCESS + */ + struct mhd_PostProcessorActionData post_process; +}; + + +/** + * The action provided after reporting all headers to application + */ +struct MHD_Action +{ + /** + * The action + */ + enum mhd_ActionType act; + + /** + * The data for the @a act action + */ + union mhd_ActionData data; +}; + +/** + * The type of the action requested by application + */ +enum mhd_UploadActionType +{ + /** + * Action has not been set yet. + */ + mhd_UPLOAD_ACTION_NO_ACTION = 0 + , + /** + * Continue processing the upload + */ + mhd_UPLOAD_ACTION_CONTINUE + , + /** + * Start replying with the response + */ + mhd_UPLOAD_ACTION_RESPONSE + , + /** + * Suspend requests (connection) + */ + mhd_UPLOAD_ACTION_SUSPEND + , + /** + * Hard close request with no response + */ + mhd_UPLOAD_ACTION_ABORT +}; + +/** + * Check whether provided mhd_UploadActionType value is valid + */ +#define mhd_UPLOAD_ACTION_IS_VALID(act) \ + ((mhd_UPLOAD_ACTION_CONTINUE <= (act)) && \ + (mhd_UPLOAD_ACTION_ABORT >= (act))) + + +/** + * The data for the application action + */ +union mhd_UploadActionData +{ + /** + * The data for the action #mhd_ACTION_RESPONSE + */ + struct MHD_Response *response; +}; + +/** + * The action provided when consuming client's upload + */ +struct MHD_UploadAction +{ + /** + * The action + */ + enum mhd_UploadActionType act; + + /** + * The data for the @a act action + */ + union mhd_UploadActionData data; +}; + +#endif /* ! MHD_ACTION_H */ diff --git a/src/mhd2/mhd_assert.h b/src/mhd2/mhd_assert.h @@ -0,0 +1,88 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2017-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_assert.h + * @brief macros for mhd_assert() + * @author Karlson2k (Evgeny Grin) + */ + +/* Unlike POSIX version of 'assert.h', MHD version of 'assert' header + * does not allow multiple redefinition of 'mhd_assert' macro within single + * source file. */ +#ifndef MHD_ASSERT_H +#define MHD_ASSERT_H 1 + +#include "mhd_sys_options.h" + +#if ! defined(_DEBUG) && ! defined(NDEBUG) +# ifndef DEBUG /* Used by some toolchains */ +# define NDEBUG 1 /* Use NDEBUG by default */ +# else /* DEBUG */ +# define _DEBUG 1 /* Non-standart macro */ +# endif /* DEBUG */ +#endif /* !_DEBUG && !NDEBUG */ + +#if defined(_DEBUG) && defined(NDEBUG) +#error Both _DEBUG and NDEBUG are defined +#endif /* _DEBUG && NDEBUG */ + +#ifdef NDEBUG +# define mhd_assert(ignore) ((void) 0) +#else /* ! NDEBUG */ +# ifdef HAVE_ASSERT +# include <assert.h> +# define mhd_assert(CHK) assert (CHK) +# else /* ! HAVE_ASSERT */ +# include <stdio.h> +# ifdef HAVE_STDLIB_H +# include <stdlib.h> +# elif defined(HAVE_UNISTD_H) +# include <unistd.h> +# endif +# ifdef MHD_HAVE_MHD_FUNC_ +# define mhd_assert(CHK) \ + do { \ + if (! (CHK)) { \ + fprintf (stderr, \ + "%s:%s:%u Assertion failed: %s\nProgram aborted.\n", \ + __FILE__, MHD_FUNC_, (unsigned) __LINE__, #CHK); \ + fflush (stderr); abort (); } \ + } while (0) +# else +# define mhd_assert(CHK) \ + do { \ + if (! (CHK)) { \ + fprintf (stderr, "%s:%u Assertion failed: %s\nProgram aborted.\n", \ + __FILE__, (unsigned) __LINE__, #CHK); \ + fflush (stderr); abort (); } \ + } while (0) +# endif +# 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_atomic_counter.c b/src/mhd2/mhd_atomic_counter.c @@ -0,0 +1,78 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_atomic_counter.c + * @brief The definition of the atomic counter functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "mhd_atomic_counter.h" + +#if defined(mhd_ATOMIC_BY_LOCKS) + +#include "mhd_assert.h" + +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_inc_get (struct mhd_AtomicCounter *pcnt) +{ + mhd_ATOMIC_COUNTER_TYPE ret; + + mhd_mutex_lock_chk (&(pcnt->lock)); + ret = ++(pcnt->count); + mhd_mutex_unlock_chk (&(pcnt->lock)); + + mhd_assert (0 != ret); /* check for overflow */ + + return ret; +} + + +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_dec_get (struct mhd_AtomicCounter *pcnt) +{ + mhd_ATOMIC_COUNTER_TYPE ret; + + mhd_mutex_lock_chk (&(pcnt->lock)); + ret = --(pcnt->count); + mhd_mutex_unlock_chk (&(pcnt->lock)); + + mhd_assert (mhd_ATOMIC_COUNTER_MAX != ret); /* check for underflow */ + + return ret; +} + + +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_get (struct mhd_AtomicCounter *pcnt) +{ + mhd_ATOMIC_COUNTER_TYPE ret; + + mhd_mutex_lock_chk (&(pcnt->lock)); + ret = pcnt->count; + mhd_mutex_unlock_chk (&(pcnt->lock)); + + return ret; +} + + +#endif /* mhd_ATOMIC_BY_LOCKS */ diff --git a/src/mhd2/mhd_atomic_counter.h b/src/mhd2/mhd_atomic_counter.h @@ -0,0 +1,205 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_atomic_counter.h + * @brief The definition of the atomic counter type and related functions + * declarations + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_ATOMIC_COUNTER_H +#define MHD_ATOMIC_COUNTER_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" /* for size_t */ + +/* Use 'size_t' to make sure it would never overflow when used for + * MHD needs. */ + +/** + * The type used to contain the counter value. + * Always unsigned. + */ +#define mhd_ATOMIC_COUNTER_TYPE size_t +/** + * The maximum counter value + */ +#define mhd_ATOMIC_COUNTER_MAX \ + ((mhd_ATOMIC_COUNTER_TYPE) (~((mhd_ATOMIC_COUNTER_TYPE) 0))) + +#ifdef MHD_USE_THREADS + +/** + * Atomic operations are based on locks + */ +# define mhd_ATOMIC_BY_LOCKS 1 + +#else /* ! MHD_USE_THREADS */ + +/** + * Atomic because single thread environment is used + */ +# define mhd_ATOMIC_SINGLE_THREAD 1 +#endif /* ! MHD_USE_THREADS */ + + +#if defined(mhd_ATOMIC_BY_LOCKS) +# include "mhd_locks.h" +# include "sys_bool_type.h" + +/** + * The atomic counter + */ +struct mhd_AtomicCounter +{ + /** + * Counter value. + * Must be read or written only with @a lock held. + */ + volatile mhd_ATOMIC_COUNTER_TYPE count; + /** + * The mutex. + */ + mhd_mutex lock; +}; + +#elif defined(mhd_ATOMIC_SINGLE_THREAD) + +/** + * The atomic counter + */ +struct mhd_AtomicCounter +{ + /** + * Counter value. + */ + volatile mhd_ATOMIC_COUNTER_TYPE count; +}; + +#endif /* mhd_ATOMIC_SINGLE_THREAD */ + + +#if defined(mhd_ATOMIC_BY_LOCKS) + +/** + * Initialise the counter to specified value. + * @param pcnt the pointer to the counter to initialise + * @param initial_value the initial value for the counter + * @return 'true' if succeed, "false' if failed + * @warning Must not be called for the counters that has been initialised + * already. + */ +# define mhd_atomic_counter_init(pcnt,initial_value) \ + ((pcnt)->count = (initial_value), \ + mhd_mutex_init_short (&((pcnt)->lock))) + +/** + * Deinitialise the counter. + * @param pcnt the pointer to the counter to deinitialise + * @warning Must be called only for the counters that has been initialised. + */ +# define mhd_atomic_counter_deinit(pcnt) \ + mhd_mutex_destroy_chk (&((pcnt)->lock)) + +/** + * Atomically increment the value of the counter + * @param pcnt the pointer to the counter to increment + */ +# define mhd_atomic_counter_inc(pcnt) do { \ + mhd_mutex_lock_chk (&((pcnt)->lock)); \ + ++(pcnt->count); \ + mhd_mutex_unlock_chk (&((pcnt)->lock)); } while (0) + +/** + * Atomically increment the value of the counter and return the result + * @param pcnt the pointer to the counter to increment + * @return the final/resulting counter value + */ +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_inc_get (struct mhd_AtomicCounter *pcnt); + +/** + * Atomically decrement the value of the counter and return the result + * @param pcnt the pointer to the counter to decrement + * @return the final/resulting counter value + */ +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_dec_get (struct mhd_AtomicCounter *pcnt); + +/** + * Atomically get the value of the counter + * @param pcnt the pointer to the counter to get + * @return the counter value + */ +MHD_INTERNAL mhd_ATOMIC_COUNTER_TYPE +mhd_atomic_counter_get (struct mhd_AtomicCounter *pcnt); + +#elif defined(mhd_ATOMIC_SINGLE_THREAD) + +/** + * Initialise the counter to specified value. + * @param pcnt the pointer to the counter to initialise + * @param initial_value the initial value for the counter + * @return 'true' if succeed, "false' if failed + * @warning Must not be called for the counters that has been initialised + * already. + */ +# define mhd_atomic_counter_init(pcnt,initial_value) \ + ((pcnt)->count = (initial_value), (! 0)) + +/** + * Deinitialise the counter. + * @param pcnt the pointer to the counter to deinitialise + * @warning Must be called only for the counters that has been initialised. + */ +# define mhd_atomic_counter_deinit(pcnt) ((void) 0) + +/** + * Atomically increment the value of the counter + * @param pcnt the pointer to the counter to increment + */ +# define mhd_atomic_counter_inc(pcnt) do { ++(pcnt->count); } while (0) + +/** + * Atomically increment the value of the counter and return the result + * @param pcnt the pointer to the counter to increment + * @return the final/resulting counter value + */ +# define mhd_atomic_counter_inc_get(pcnt) (++((pcnt)->count)) + +/** + * Atomically decrement the value of the counter and return the result + * @param pcnt the pointer to the counter to decrement + * @return the final/resulting counter value + */ +# define mhd_atomic_counter_dec_get(pcnt) (--((pcnt)->count)) + +/** + * Atomically get the value of the counter + * @param pcnt the pointer to the counter to get + * @return the counter value + */ +# define mhd_atomic_counter_get(pcnt) ((pcnt)->count) + +#endif /* mhd_ATOMIC_SINGLE_THREAD */ + +#endif /* ! MHD_ATOMIC_COUNTER_H */ diff --git a/src/mhd2/mhd_buffer.h b/src/mhd2/mhd_buffer.h @@ -0,0 +1,49 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_buffer.h + * @brief The definition of the MHD_Buffer type + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_BUFFER_H +#define MHD_BUFFER_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" + +/** + * The buffer with size + */ +struct mhd_Buffer +{ + /** + * The size of the allocated @a buf buffer + */ + size_t size; + + /** + * The pointer to the allocation + */ + char *buf; +}; + +#endif /* ! MHD_BUFFER_H */ diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h @@ -0,0 +1,627 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_connection.h + * @brief Definition of struct MHD_connection + * @author Karlson2k (Evgeny Grin) + * @author Daniel Pittman + * @author Christian Grothoff + * + * @warning Imported from MHD1 with minimal changes + * TODO: Rewrite + */ + +#ifndef MHD_CONNECTION_H +#define MHD_CONNECTION_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" +#include "mhd_socket_type.h" + +#include "mhd_threads.h" + +#include "mhd_tristate.h" +#include "mhd_dlinked_list.h" + +#include "mhd_request.h" +#include "mhd_reply.h" + +#include "mhd_socket_error.h" + +#include "mhd_public_api.h" + +/** + * Minimum reasonable size by which MHD tries to increment read/write buffers. + * We usually begin with half the available pool space for the + * IO-buffer, but if absolutely needed we additively grow by the + * number of bytes given here (up to -- theoretically -- the full pool + * space). + * + * Currently set to reasonable maximum MSS size. + */ +#define mhd_BUF_INC_SIZE 1500 + +/** + * Message to transmit when http 1.1 request is received + */ +#define mdh_HTTP_1_1_100_CONTINUE_REPLY "HTTP/1.1 100 Continue\r\n\r\n" + + +struct MHD_Connection; /* forward declaration */ + +/** + * What is this connection waiting for? + */ +enum MHD_FIXED_FLAGS_ENUM_ MHD_ConnectionEventLoopInfo +{ + /** + * We are waiting to be able to read. + * The same value as #mhd_SOCKET_NET_STATE_RECV_READY + */ + MHD_EVENT_LOOP_INFO_READ = 1 << 0 + , + /** + * We are waiting to be able to write. + * The same value as #mhd_SOCKET_NET_STATE_SEND_READY + */ + MHD_EVENT_LOOP_INFO_WRITE = 1 << 1 + , + /** + * We are waiting for the application to provide data. + */ + MHD_EVENT_LOOP_INFO_PROCESS = 1 << 4 + , + /** + * We are finished and are awaiting cleanup. + */ + MHD_EVENT_LOOP_INFO_CLEANUP = 1 << 5 +}; + +#define MHD_EVENT_LOOP_INFO_PROCESS_READ \ + (MHD_EVENT_LOOP_INFO_READ | MHD_EVENT_LOOP_INFO_PROCESS) + + +/** + * The network states for connected sockets + * An internal version of #MHD_FdState. Keep in sync! + */ +enum MHD_FIXED_FLAGS_ENUM_ mhd_SocketNetState +{ + /** + * No active states of the socket + */ + mhd_SOCKET_NET_STATE_NOTHING = 0 + , + /** + * The socket is ready for receiving + */ + mhd_SOCKET_NET_STATE_RECV_READY = 1 << 0 + , + /** + * The socket is ready for sending + */ + mhd_SOCKET_NET_STATE_SEND_READY = 1 << 1 + , + /** + * The socket has some unrecoverable error + */ + mhd_SOCKET_NET_STATE_ERROR_READY = 1 << 2 +}; + + +/** + * The reason for the socket closure + */ +enum mhd_SocketClosureReason +{ + /** + * The socket is not closed / closing. + */ + mhd_SCOKET_CLOSURE_REASON_NO_CLOSURE = 0 + , + /** + * Socket has to be closed because HTTP protocol successfully finished data + * exchange. + */ + mhd_SCOKET_CLOSURE_REASON_PROTOCOL_SUCCESS + , + /** + * Socket has to be closed because remote side violated some HTTP + * specification requirements or request processed with an error. + * The HTTP error response should be sent. + */ + mhd_SCOKET_CLOSURE_REASON_PROTOCOL_FAILURE_SOFT + , + /** + * Timeout expired + */ + mhd_SCOKET_CLOSURE_REASON_TIMEOUT + , + /** + * Socket has to be closed because received data cannot be interpreted as + * valid HTTP data. + */ + mhd_SCOKET_CLOSURE_REASON_PROTOCOL_FAILURE_HARD + , + /** + * Unrecoverable TLS error + */ + mhd_SCOKET_CLOSURE_REASON_TLS_ERROR + , + /** + * The remote side closed connection in abortive way + */ + mhd_SCOKET_CLOSURE_REASON_REMOTE_HARD_DISCONN + , + /** + * The connection has been broken for some reason + */ + mhd_SCOKET_CLOSURE_REASON_CONN_BROKEN +}; + +/** + * States in a state machine for a connection. + * + * The main transitions are any-state to #MHD_CONNECTION_CLOSED, any + * state to state+1, #MHD_CONNECTION_FOOTERS_SENT to + * #MHD_CONNECTION_INIT. #MHD_CONNECTION_CLOSED is the terminal state + * and #MHD_CONNECTION_INIT the initial state. + * + * Note that transitions for *reading* happen only after the input has + * been processed; transitions for *writing* happen after the + * respective data has been put into the write buffer (the write does + * not have to be completed yet). A transition to + * #MHD_CONNECTION_CLOSED or #MHD_CONNECTION_INIT requires the write + * to be complete. + */ +enum MHD_FIXED_ENUM_ MHD_CONNECTION_STATE +{ + /** + * Connection just started (no headers received). + * Waiting for the line with the request type, URL and version. + */ + MHD_CONNECTION_INIT = 0, + + /** + * Part of the request line was received. + * Wait for complete line. + */ + MHD_CONNECTION_REQ_LINE_RECEIVING, + + /** + * We got the URL (and request type and version). Wait for a header line. + * + * A milestone state. No received data is processed in this state. + */ + MHD_CONNECTION_REQ_LINE_RECEIVED, + + /** + * Receiving request headers. Wait for the rest of the headers. + */ + MHD_CONNECTION_REQ_HEADERS_RECEIVING, + + /** + * We got the request headers. Process them. + */ + MHD_CONNECTION_HEADERS_RECEIVED, + + /** + * We have processed the request headers. Call application callback. + */ + MHD_CONNECTION_HEADERS_PROCESSED, + + /** + * We have processed the headers and need to send 100 CONTINUE. + */ + MHD_CONNECTION_CONTINUE_SENDING, + + /** + * We have sent 100 CONTINUE (or do not need to). Read the message body. + */ + MHD_CONNECTION_BODY_RECEIVING, + + /** + * We got the request body. + * + * A milestone state. No received data is processed in this state. + */ + MHD_CONNECTION_BODY_RECEIVED, + + /** + * We are reading the request footers. + */ + MHD_CONNECTION_FOOTERS_RECEIVING, + + /** + * We received the entire footer. + * + * A milestone state. No data is receiving in this state. + */ + MHD_CONNECTION_FOOTERS_RECEIVED, + + /** + * We received the entire request. + * + * A milestone state. No data is receiving in this state. + */ + MHD_CONNECTION_FULL_REQ_RECEIVED, + + /** + * Finished receiving request data: either complete request received or + * MHD is going to send reply early, without getting full request. + */ + MHD_CONNECTION_REQ_RECV_FINISHED, + + /** + * Finished reading of the request and the response is ready. + * Switch internal logic from receiving to sending, prepare connection + * sending the reply and build the reply header. + */ + MHD_CONNECTION_START_REPLY, + + /** + * We have prepared the response headers in the write buffer. + * Send the response headers. + */ + MHD_CONNECTION_HEADERS_SENDING, + + /** + * We have sent the response headers. Get ready to send the body. + */ + MHD_CONNECTION_HEADERS_SENT, + + /** + * We are waiting for the client to provide more + * data of a non-chunked body. + */ + MHD_CONNECTION_UNCHUNKED_BODY_UNREADY, + + /** + * We are ready to send a part of a non-chunked body. Send it. + */ + MHD_CONNECTION_UNCHUNKED_BODY_READY, + + /** + * We are waiting for the client to provide a chunk of the body. + */ + MHD_CONNECTION_CHUNKED_BODY_UNREADY, + + /** + * We are ready to send a chunk. + */ + MHD_CONNECTION_CHUNKED_BODY_READY, + + /** + * We have sent the chunked response body. Prepare the footers. + */ + MHD_CONNECTION_CHUNKED_BODY_SENT, + + /** + * We have prepared the response footer. Send it. + */ + MHD_CONNECTION_FOOTERS_SENDING, + + /** + * We have sent the entire reply. + * Shutdown connection or restart processing to get a new request. + */ + MHD_CONNECTION_FULL_REPLY_SENT, + + /** + * This connection is to be closed. + */ + MHD_CONNECTION_CLOSED + +}; + +struct mhd_ConnDebugData +{ + bool pre_closed; + bool pre_cleaned; +}; + +/** + * Ability to use same connection for next request + */ +enum MHD_FIXED_ENUM_ mhd_ConnReuse +{ + /** + * Connection must be closed after sending response. + */ + mhd_CONN_MUST_CLOSE = -1 + , + /** + * KeepAlive state is possible + */ + mhd_CONN_KEEPALIVE_POSSIBLE = 0 + , + /** + * Connection will be upgraded + */ + mhd_CONN_MUST_UPGRADE = 1 +}; + +/** + * The helper struct for the connections list + */ +mhd_DLINKEDL_LINKS_DEF (MHD_Connection); + +/** + * State kept for HTTP network connection. + */ +struct MHD_Connection +{ + + /** + * The list with all daemon's connections + */ + mhd_DLNKDL_LINKS (MHD_Connection,all_conn); + + /** + * The state of the connected socket + */ + enum mhd_SocketNetState sk_ready; + + /** + * The type of the error when disconnected early + */ + enum mhd_SocketError sk_discnt_err; + + /** + * Set to 'true' when the client shut down write/send and + * __the last byte from the remote has been read__. + */ + bool sk_rmt_shut_wr; + + /** + * 'true' if connection is in 'process ready' list, + * 'false' otherwise + */ + bool in_proc_ready; + + /** + * The list with all daemon's connections that ready to processing + */ + mhd_DLNKDL_LINKS (MHD_Connection,proc_ready); + + /** + * The list of connections sorted by timeout + */ + mhd_DLNKDL_LINKS (MHD_Connection,by_timeout); + + /** + * True if connection is suspended + */ + bool suspended; + + /** + * True if connection is resuming + */ + bool resuming; + + /** + * Reference to the MHD_Daemon struct. + */ + struct MHD_Daemon *daemon; + + /** + * Request-specific data + */ + struct MHD_Request rq; + + /** + * Reply-specific data + */ + struct MHD_Reply rp; + + /** + * The memory pool is created whenever we first read from the TCP + * stream and destroyed at the end of each request (and re-created + * for the next request). In the meantime, this pointer is NULL. + * The pool is used for all connection-related data except for the + * response (which maybe shared between connections) and the IP + * address (which persists across individual requests). + */ + struct mhd_MemoryPool *pool; + + /** + * We allow the main application to associate some pointer with the + * TCP connection (which may span multiple HTTP requests). Here is + * where we store it. (MHD does not know or care what it is). + * The location is given to the #MHD_NotifyConnectionCallback and + * also accessible via #MHD_CONNECTION_INFO_SOCKET_CONTEXT. + */ + void *socket_context; + + /** + * Close connection after sending response? + * Functions may change value from "KeepAlive" to "Must close", + * but no functions reset value "Must Close" to any other value. + */ + enum mhd_ConnReuse conn_reuse; + + /** + * Buffer for reading requests. Allocated in pool. Actually one + * byte larger than @e read_buffer_size (if non-NULL) to allow for + * 0-termination. + */ + char *read_buffer; + + /** + * Buffer for writing response (headers only). Allocated + * in pool. + */ + char *write_buffer; + + /** + * Foreign address (of length @e addr_len). MALLOCED (not + * in pool!). + */ + struct sockaddr_storage *addr; + +#if defined(MHD_USE_THREADS) + /** + * Thread handle for this connection (if we are using + * one thread per connection). + */ + mhd_thread_handle_ID tid; +#endif + + /** + * Size of @e read_buffer (in bytes). + * This value indicates how many bytes we're willing to read + * into the buffer. + */ + size_t read_buffer_size; + + /** + * Position where we currently append data in @e read_buffer (the + * next char after the last valid position). + */ + size_t read_buffer_offset; + + /** + * Size of @e write_buffer (in bytes). + */ + size_t write_buffer_size; + + /** + * Offset where we are with sending from @e write_buffer. + */ + size_t write_buffer_send_offset; + + /** + * Last valid location in write_buffer (where do we + * append and up to where is it safe to send?) + */ + size_t write_buffer_append_offset; + + /** + * Position in the 100 CONTINUE message that + * we need to send when receiving http 1.1 requests. + */ + size_t continue_message_write_offset; + + /** + * Length of the foreign address. + */ + size_t addr_len; + + /** + * Last time this connection had any activity + * (reading or writing). + */ + uint_fast64_t last_activity; + + /** + * After how many milliseconds of inactivity should + * this connection time out? + * Zero for no timeout. + */ + uint_fast64_t connection_timeout_ms; + + /** + * Socket for this connection. Set to #MHD_INVALID_SOCKET if + * this connection has died (daemon should clean + * up in that case). + */ + MHD_Socket socket_fd; + + /** + * The type of the socket: TCP/IP or non TCP/IP (a UNIX domain socket, a pipe) + */ + enum mhd_Tristate is_nonip; + + /** + * true if @a socket_fd is non-blocking, false otherwise. + */ + bool sk_nonblck; + + /** + * true if connection socket has set SIGPIPE suppression + */ + bool sk_spipe_suppress; + +// #ifndef MHD_WINSOCK_SOCKETS // TODO: conditionally use in the code + /** + * Tracks TCP_CORK / TCP_NOPUSH of the connection socket. + */ + enum mhd_Tristate sk_corked; +// #endif + + /** + * Tracks TCP_NODELAY state of the connection socket. + */ + enum mhd_Tristate sk_nodelay; + + /** + * Some error happens during processing the connection therefore this + * connection must be closed. + * The error may come from the client side (like wrong request format), + * from the application side (like data callback returned error), or from + * the OS side (like out-of-memory). + */ + bool stop_with_error; + + /** + * Response queued early, before the request is fully processed, + * the client upload is rejected. + * The connection cannot be reused for additional requests as the current + * request is incompletely read and it is unclear where is the initial + * byte of the next request. + */ + bool discard_request; + +#if defined(MHD_USE_THREADS) + /** + * Set to `true` if the thread has been joined. + */ + bool thread_joined; +#endif + + /** + * Connection is in the cleanup DL-linked list. + */ + bool in_cleanup; + + /** + * State in the FSM for this connection. + */ + enum MHD_CONNECTION_STATE state; + + /** + * What is this connection waiting for? + */ + enum MHD_ConnectionEventLoopInfo event_loop_info; + +#ifndef NDEBUG + /** + * Debugging data + */ + struct mhd_ConnDebugData dbg; +#endif +}; + + +#endif /* ! MHD_CONNECTION_H */ diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h @@ -0,0 +1,969 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_daemon.h + * @brief The header for declaration of struct MHD_Daemon + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DAEMON_H +#define MHD_DAEMON_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "mhd_socket_type.h" + +#include "mhd_public_api.h" + +#ifdef MHD_USE_THREADS +# include "mhd_threads.h" +# include "mhd_itc_types.h" +# include "mhd_locks.h" +#endif + +#include "sys_select.h" +#include "sys_poll.h" +#ifdef MHD_USE_EPOLL +# include <sys/epoll.h> +#endif + +#include "mhd_dlinked_list.h" + +struct DaemonOptions; /* Forward declaration */ +struct MHD_Connection; /* Forward declaration */ + +/** + * The helper struct for the connections list + */ +mhd_DLINKEDL_LIST_DEF (MHD_Connection); + +/** + * The current phase of the daemon life + */ +enum MHD_FIXED_ENUM_ mhd_DaemonState +{ + /** + * The daemon has been created, but not yet started. + * Setting configuration options is possible. + */ + mhd_DAEMON_STATE_NOT_STARTED = 0 + , + /** + * The daemon is being started. + */ + mhd_DAEMON_STATE_STARTING + , + /** + * The daemon has been started. + * Normal operations. + */ + mhd_DAEMON_STATE_STARTED + , + /** + * The daemon has failed to start + */ + mhd_DAEMON_STATE_FAILED + , + /** + * The daemon is being stopped. + */ + mhd_DAEMON_STATE_STOPPING + , + /** + * The daemon is stopped. + * The state should rarely visible as daemon should be destroyed when stopped. + */ + mhd_DAEMON_STATE_STOPPED +}; + + +/** + * Internal version of the daemon work mode type + */ +enum MHD_FIXED_ENUM_ mhd_WorkModeIntType +{ + /** + * Network edge-triggered events are monitored and provided by application. + * Receiving, sending and processing of the network data if performed when + * special MHD function is called by application. + * No threads managed by the daemon. + */ + mhd_WM_INT_EXTERNAL_EVENTS_EDGE + , + /** + * Network level-triggered events are monitored and provided by application. + * Receiving, sending and processing of the network data if performed when + * special MHD function is called by application. + * No threads managed by the daemon. + */ + mhd_WM_INT_EXTERNAL_EVENTS_LEVEL + , + /** + * The daemon checks for the network events, receives, sends and process + * the network data when special MHD function is called by application. + * No threads managed by the daemon. + */ + mhd_WM_INT_INTERNAL_EVENTS_NO_THREADS + , + /** + * The daemon runs its own single thread, where the daemon monitors + * all network events, receives, sends and process the network data. + */ + mhd_WM_INT_INTERNAL_EVENTS_ONE_THREAD + , + /** + * The daemon runs its own single thread, where the daemon monitors + * the new incoming connections, and runs individual thread for each + * established connection, where the daemon monitors connection, receives, + * sends and process the network data. + */ + mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION + , + /** + * The daemon runs its fixed number of threads, all threads monitors the + * new incoming connections and each thread handles own subset of the network + * connections (monitors connections network events, receives, sends and + * process the network data). + */ + mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL +}; + +/** + * Check whether given mhd_WorkModeIntType value should have internal threads, + * either directly controlled or indirectly, via additional workers daemons. + */ +#define mhd_WM_INT_HAS_THREADS(wm_i) \ + (mhd_WM_INT_INTERNAL_EVENTS_ONE_THREAD <= wm_i) + +/** + * Check whether given mhd_WorkModeIntType value has external events + */ +#define mhd_WM_INT_HAS_EXT_EVENTS(wm_i) \ + (mhd_WM_INT_EXTERNAL_EVENTS_LEVEL >= wm_i) + + +/** + * Sockets polling internal syscalls used by MHD. + * + * The same value used as by #MHD_SockPollSyscall, however instead of "auto" + * method this enum uses "not yet set" and enum is extended with an additional + * "external" value. + */ +enum MHD_FIXED_ENUM_ mhd_IntPollType +{ + /** + * External sockets polling is used. + */ + mhd_POLL_TYPE_EXT = -1 + , + /** + * Internal sockets polling syscall has not been selected yet. + */ + mhd_POLL_TYPE_NOT_SET_YET = MHD_SPS_AUTO + , + /** + * Use select(). + */ + mhd_POLL_TYPE_SELECT = MHD_SPS_SELECT + , + /** + * Use poll(). + */ + mhd_POLL_TYPE_POLL = MHD_SPS_POLL + , + /** + * Use epoll. + */ + mhd_POLL_TYPE_EPOLL = MHD_SPS_EPOLL +}; + +#if defined(HAVE_UINTPTR_T) +typedef uintptr_t mhd_SockRelMarker; +#else +typedef unsigned char *mhd_SockRelMarker; +#endif + + +#define mhd_SOCKET_REL_MARKER_EMPTY ((mhd_SockRelMarker) 0) + +#define mhd_SOCKET_REL_MARKER_ITC ((mhd_SockRelMarker) - 1) + +#define mhd_SOCKET_REL_MARKER_LISTEN (mhd_SOCKET_REL_MARKER_ITC - 1) +/** + * Identifier of the FD related to event + */ +union mhd_SocketRelation +{ + /** + * Identifier of the FD. + * Only valid when it is equal to #mhd_SOCKET_REL_MARKER_EMPTY, + * #mhd_SOCKET_REL_MARKER_ITC or #mhd_SOCKET_REL_MARKER_LISTEN. + */ + mhd_SockRelMarker fd_id; + /** + * This is a connection's FD. + * This is valid only when @a fd_id is not valid. + */ + struct MHD_Connection *connection; +}; + +#ifdef MHD_USE_SELECT + +/** + * Daemon's pointers to the preallocated arrays for running sockets monitoring + * by poll(). + */ +struct mhd_DaemonEventsSelectData +{ + /** + * Set of sockets monitored for read (receive) readiness. + */ + fd_set *rfds; + /** + * Set of sockets monitored for write (send) readiness. + */ + fd_set *wfds; + /** + * Set of sockets monitored for exception (error) readiness. + */ + fd_set *efds; +}; + +#endif /* MHD_USE_SELECT */ + +#ifdef MHD_USE_POLL + +/** + * Daemon's pointers to the preallocated arrays for running sockets monitoring + * by poll(). + */ +struct mhd_DaemonEventsPollData +{ + /** + * Array of sockets monitored for read (receive) readiness. + * The size of the array is maximum number of connections per this daemon plus + * two (one for the listen socket and one for ITC). + * ITC FDs and the listening are always the first (and the second), if used. + * The number of elements is always two plus maximum number of connections + * allowed for the daemon. + */ + struct pollfd *fds; + /** + * Array of the @a fds identifications. + * Each slot matches the slot with the same number in @a fds. + * Up to two first positions reserved for the ITC and the listening. + * The number of elements is always two plus maximum number of connections + * allowed for the daemon. + */ + union mhd_SocketRelation *rel; +}; + +#endif /* MHD_USE_POLL */ + +#ifdef MHD_USE_EPOLL +/** + * Daemon's parameters and pointers to the preallocated memory for running + * sockets monitoring by epoll. + */ +struct mhd_DaemonEventsEPollData +{ + /** + * The epoll control FD. + */ + int e_fd; + /** + * The array of events reported by epoll. + */ + struct epoll_event *events; + + /** + * The number of elements in the allocated @a events arrays. + */ + size_t num_elements; +}; + +#endif + +/** + * Daemon's data for external events for sockets monitoring. + * Internal version of struct MHD_WorkModeExternalEventLoopCBParam. + */ +struct mhd_DaemonEventsExternal +{ + /** + * Socket registration callback + */ + MHD_SocketRegistrationUpdateCallback cb; + /** + * Closure for the @a cb + */ + void *cls; +}; + +/** + * Type-specific events monitoring data + */ +union mhd_DaemonEventMonitoringTypeSpecificData +{ +#ifdef MHD_USE_SELECT + /** + * Daemon's pointers to the preallocated arrays for running sockets monitoring + * by poll(). + */ + struct mhd_DaemonEventsSelectData select; +#endif /* MHD_USE_SELECT */ + +#ifdef MHD_USE_POLL + /** + * Daemon's pointers to the preallocated arrays for running sockets monitoring + * by poll(). + */ + struct mhd_DaemonEventsPollData poll; +#endif /* MHD_USE_POLL */ + +#ifdef MHD_USE_EPOLL + /** + * Daemon's parameters and pointers to the preallocated memory for running + * sockets monitoring by epoll. + */ + struct mhd_DaemonEventsEPollData epoll; +#endif + + /** + * Daemon's data for external events for sockets monitoring. + * Internal version of struct MHD_WorkModeExternalEventLoopCBParam. + */ + struct mhd_DaemonEventsExternal ext; +}; + + +/** + * The required actions for the daemon + */ +struct mhd_DaemonEventActionRequired +{ + /** + * If 'true' then connection is waiting to be accepted + */ + bool accept; +}; + + +/** + * The data for events monitoring + */ +struct mhd_DaemonEventMonitoringData +{ + /** + * Sockets polling type used by the daemon. + */ + enum mhd_IntPollType poll_type; + + /** + * Type-specific events monitoring data + */ + union mhd_DaemonEventMonitoringTypeSpecificData data; + + /** + * The required actions for the daemon. + * If daemon has internal thread, this should be changed only inside + * the daemon's thread. + */ + struct mhd_DaemonEventActionRequired act_req; + + /** + * Indicate that daemon already has some data to be processed on the next + * cycle + */ + bool zero_wait; + + /** + * The list of the daemon's connections that need processing + */ + mhd_DLNKDL_LIST (MHD_Connection,proc_ready); + +}; + + +/** + * The type of the socket + */ +enum MHD_FIXED_ENUM_ mhd_SocketType +{ + /** + * The socket type is some non-IP type. + */ + mhd_SOCKET_TYPE_NON_IP = -2 + , + /** + * The socket type is UNIX (LOCAL) + */ + mhd_SOCKET_TYPE_UNIX = -1 + , + /** + * The socket is unknown yet. It can be IP or non-IP. + */ + mhd_SOCKET_TYPE_UNKNOWN = 0 + , + /** + * The socket is definitely IP. + */ + mhd_SOCKET_TYPE_IP = 1 +}; + +/** + * Listen socket data + */ +struct mhd_ListenSocket +{ + /** + * The listening socket + */ + MHD_Socket fd; + /** + * The type of the listening socket @a fd + */ + enum mhd_SocketType type; + /** + * 'true' if @a fd is non-blocking + */ + bool non_block; + /** + * The port number for @a fd + * + * Zero if unknown and for non-IP socket. + */ + uint_least16_t port; +}; + +/** + * Configured settings for the daemon's network data + */ +struct mhd_DaemonNetworkSettings +{ +#ifdef MHD_POSIX_SOCKETS + /** + * The maximum number for the network FDs. + * The valid FD number must be less then @a max_fd_num. + */ + MHD_Socket max_fd_num; +#else + int dummy; /* mute compiler warning */ +#endif +}; + +/** + * The daemon network/sockets data + */ +struct mhd_DaemonNetwork +{ + /** + * The listening socket + */ + struct mhd_ListenSocket listen; + +#ifdef MHD_USE_EPOLL + /** + * The epoll FD. + * Set to '-1' when epoll is not used. + */ + int epoll_fd; +#endif + /** + * Configured settings for the daemon's network data + */ + struct mhd_DaemonNetworkSettings cfg; +}; + +#ifdef MHD_USE_THREADS + +/** + * The type of the daemon + */ +enum MHD_FIXED_ENUM_ mhd_DaemonType +{ + /** + * A single daemon, performing all the work. + * + * This daemon may have a optional single thread, managed by MHD. + */ + mhd_DAEMON_TYPE_SINGLE +#ifndef NDEBUG + = 1 +#endif + , + /** + * A master daemon, only controlling worker daemons. + * + * This daemon never handle any network activity directly. + */ + mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY + , + /** + * A daemon with single internal thread for listening and multiple threads + * handling connections with the clients, one thread per connection. + */ + mhd_DAEMON_TYPE_LISTEN_ONLY + , + /** + * A worker daemon, performing the same work as a single daemon, but + * controlled by master daemon. + * + * This type of daemon always have single internal tread and never exposed + * to application directly. + */ + mhd_DAEMON_TYPE_WORKER +}; + +/** + * Check whether the daemon type is allowed to have internal thread with + * direct control + */ +#define mhd_D_TYPE_IS_VALID(t) \ + ((mhd_DAEMON_TYPE_SINGLE <= (t)) && (mhd_DAEMON_TYPE_WORKER >= (t))) + +/** + * Check whether the daemon type must not be exposed to the application + */ +#define mhd_D_TYPE_IS_INTERNAL_ONLY(t) \ + (mhd_DAEMON_TYPE_WORKER == (t)) + +/** + * Check whether the daemon type is allowed to process the network data + */ +#define mhd_D_TYPE_HAS_EVENTS_PROCESSING(t) \ + (mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY != (t)) + +/** + * Check whether the daemon type must not be exposed to the application + */ +#define mhd_D_TYPE_HAS_WORKERS(t) \ + (mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY == (t)) + +/** + * Check whether the daemon type has master (controlling) daemon + */ +#define mhd_D_TYPE_HAS_MASTER_DAEMON(t) \ + (mhd_DAEMON_TYPE_WORKER == (t)) + +#else /* ! MHD_USE_THREADS */ + +/** + * Check whether the daemon type is allowed to have internal thread with + * direct control + */ +#define mhd_D_TYPE_IS_VALID(t) (! 0) + +/** + * Check whether the daemon type must not be exposed to the application + */ +#define mhd_D_TYPE_IS_INTERNAL_ONLY(t) (0) + +/** + * Check whether the daemon type is allowed to process the network data + */ +#define mhd_D_TYPE_HAS_EVENTS_PROCESSING(t) (! 0) + +/** + * Check whether the daemon type must not be exposed to the application + */ +#define mhd_D_TYPE_HAS_WORKERS(t) (0) + +/** + * Check whether the daemon type has master (controlling) daemon + */ +#define mhd_D_TYPE_HAS_MASTER_DAEMON(t) (0) + +#endif /* ! MHD_USE_THREADS */ + +#ifdef MHD_USE_THREADS + +/** + * Workers pool data + */ +struct mhd_DaemonWorkerPoolData +{ + /** + * Array of worker daemons + */ + struct MHD_Daemon *workers; + + /** + * The number of workers in the @a workers array + */ + unsigned int num; +}; + +/** + * Hierarchy data for the daemon + */ +union mhd_DeamonHierarchyData +{ + /** + * The pointer to the master daemon + * Only for #mhd_DAEMON_TYPE_WORKER daemons. + */ + struct MHD_Daemon *master; + + /** + * Workers pool data. + * Only for #mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY daemons. + */ + struct mhd_DaemonWorkerPoolData pool; +}; + +/** + * Configured settings for threading + */ +struct mhd_DaemonThreadingDataSettings +{ + /** + * The size of the stack. + * Zero to use system's defaults. + */ + size_t stack_size; +}; + +/** + * Threading and Inter-Thread Communication data + */ +struct mhd_DaemonThreadingData +{ + /** + * The type of this daemon + */ + enum mhd_DaemonType d_type; + + /** + * Inter-Thread Communication channel. + * Used to trigger processing of the command or the data provided or updated + * by the application. + */ + struct mhd_itc itc; + + /** + * 'True' if stop has been requested. + * The daemon thread should stop all connections and then close. + */ + volatile bool stop_requested; + + /** + * 'True' if resuming of any connection has been requested. + */ + volatile bool resume_requested; + + /** + * The handle of the daemon's thread (if managed by the daemon) + */ + mhd_thread_handle_ID tid; + + /** + * The hierarchy data for the daemon. + * Used only when @a d_type is #mhd_DAEMON_TYPE_MASTER_CONTROL_ONLY or + * #mhd_DAEMON_TYPE_WORKER. + */ + union mhd_DeamonHierarchyData hier; + + /** + * Configured settings for threading + */ + struct mhd_DaemonThreadingDataSettings cfg; +}; + +#endif /* MHD_USE_THREADS */ + +/** + * Configured settings for the daemon's connections + */ +struct mhd_DaemonConnectionsSettings +{ + /** + * The maximum number of connections handled by the daemon + */ + unsigned int count_limit; + + /** + * Connection's default timeout value (in seconds) + */ + unsigned int timeout; + + /** + * Connection's memory pool size + */ + size_t mem_pool_size; +}; + +/** + * Connections handling data + */ +struct mhd_DaemonConnections +{ + + /** + * The list of all daemon's connections. + * All connection are listed here, expect connection in @a to_clean list. + */ + mhd_DLNKDL_LIST (MHD_Connection,all_conn); + + /** + * 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 + */ + mhd_DLNKDL_LIST (MHD_Connection,to_clean); + + /** + * The current number of connections handled by the daemon + */ + unsigned int count; + + /** + * If set to 'true' then no new connection is allowed. + * New connection may be blocked because of various system limits, when + * additional connection would fail anyway. This flag should be cleared + * when any already processing connection closed. + * Can be checked from other threads + */ + volatile bool block_new; + + /** + * Configured settings for the daemon's connections + */ + struct mhd_DaemonConnectionsSettings cfg; +}; + +/** + * Early URI callback + */ +struct mhd_DaemonRequestUriCB +{ + /** + * The callback + */ + MHD_EarlyUriLogCallback cb; + /** + * The callback closure + */ + void *cls; +}; + +/** + * Shared large buffer data + */ +struct mhd_DeamonLargeBuffer +{ + /** + * The amount of memory left allowed to be allocated for the large buffer + */ + size_t space_left; + +#ifdef MHD_USE_THREADS + /** + * The mutex to change or check the @a space_left value + */ + mhd_mutex lock; +#endif +}; + +/** + * Settings for requests processing + */ +struct mhd_DaemonRequestProcessingSettings +{ + /** + * Request callback. + * The main request processing callback. + */ + MHD_RequestCallback cb; + + /** + * The closure for @a req_cb + */ + void *cb_cls; + + /** + * Protocol strictness enforced by MHD on clients. + */ + enum MHD_ProtocolStrictLevel strictnees; + + /** + * Early URI callback + */ + struct mhd_DaemonRequestUriCB uri_cb; // TODO: set from settings + + /** + * Shared large buffer data + */ + struct mhd_DeamonLargeBuffer large_buf; // TODO: set from settings + + /** + * Suppress "Date:" header in responses + */ + bool suppress_date; // TODO: set from settings +}; + + +#ifndef NDEBUG +/** + * Various debugging data + */ +struct mhd_daemon_debug +{ + bool net_inited; + bool net_deinited; + bool events_allocated; + unsigned int num_events_elements; + bool events_fully_inited; + bool thread_pool_inited; + bool threading_inited; + bool connections_inited; + bool avoid_accept4; +}; +#endif /* NDEBUG */ + + +struct MHD_Daemon +{ + /* General data */ + + /** + * The daemon state + */ + enum mhd_DaemonState state; + + /** + * The daemon work mode (private version) + */ + enum mhd_WorkModeIntType wmode_int; + + /* Events/sockets monitoring/polling data */ + + /** + * The data for events monitoring + */ + struct mhd_DaemonEventMonitoringData events; + + /* Network/sockets data */ + + /** + * The daemon network/sockets data + */ + struct mhd_DaemonNetwork net; + +#ifdef MHD_USE_THREADS + /* Threading data */ + + /** + * The daemon threading and Inter-Thread Communication data + */ + struct mhd_DaemonThreadingData threading; +#endif + + /* Connections handling */ + + /** + * The connections handling data + */ + struct mhd_DaemonConnections conns; + + /* Request processing data */ + + /** + * Settings for requests processing + */ + struct mhd_DaemonRequestProcessingSettings req_cfg; + + /* Other data */ + + /** + * Daemon logging parameters + */ + struct MHD_DaemonOptionValueLog log_params; + + + /* Temporal data */ + + /** + * User settings, before applied to the daemon itself + */ + struct DaemonOptions *settings; + +#ifndef NDEBUG + /* Debug data */ + + struct mhd_daemon_debug dbg; +#endif +}; + + +#ifdef MHD_POSIX_SOCKETS +/** + * Checks whether @a fd socket number fits limitations for the @a d_ptr daemon + */ +# define mhd_FD_FITS_DAEMON(d_ptr,fd) \ + ((MHD_INVALID_SOCKET == d_ptr->net.cfg.max_fd_num) || \ + (d_ptr->net.cfg.max_fd_num > fd)) +#else +# define mhd_FD_FITS_DAEMON(d_ptr,fd) (! 0) +#endif +#endif /* ! MHD_DAEMON_H */ + +#ifdef MHD_USE_EPOLL +# define mhd_D_IS_USING_EPOLL(d) \ + (mhd_POLL_TYPE_EPOLL == ((d)->events.poll_type)) +#else +# define mhd_D_IS_USING_EPOLL(d) (0) +#endif + +#ifdef MHD_USE_THREADS +# define mhd_D_HAS_THREADS(d) mhd_WM_INT_HAS_THREADS ((d)->wmode_int) +#else +# define mhd_D_HAS_THREADS(d) (0) +#endif + +#ifdef MHD_USE_THREADS +# define mhd_D_HAS_THR_PER_CONN(d) \ + (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION == \ + ((d)->wmode_int)) +#else +# define mhd_D_HAS_THR_PER_CONN(d) (0) +#endif + +#define mhd_D_HAS_WORKERS(d) mhd_D_TYPE_HAS_WORKERS ((d)->threading.d_type) + +#define mhd_D_HAS_MASTER(d) mhd_D_TYPE_HAS_MASTER_DAEMON ((d)->threading.d_type) + +#define mhd_D_IS_INTERNAL_ONLY(d) \ + mhd_D_TYPE_IS_INTERNAL_ONLY ((d)->threading.d_type) + +#define mhd_D_IS_USING_EDGE_TRIG(d) \ + (mhd_D_IS_USING_EPOLL (d) || \ + (mhd_WM_INT_EXTERNAL_EVENTS_EDGE ==((d)->wmode_int))) diff --git a/src/mhd2/mhd_dcc_action.h b/src/mhd2/mhd_dcc_action.h @@ -0,0 +1,173 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_dcc_action.h + * @brief The definition of the MHD_Action and MHD_UploadAction structures + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DCC_ACTION_H +#define MHD_DCC_ACTION_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * The context used for Dynamic Content Creator callback + */ +struct MHD_DynamicContentCreatorContext +{ + struct MHD_Connection *connection; +}; + +/** + * The type of the dynamic content creator action requested by application + */ +enum mhd_DccActionType +{ + /** + * Action has not been set yet. + */ + mhd_DCC_ACTION_NO_ACTION = 0 + , + /** + * Send new portion of data, provided by application + */ + mhd_DCC_ACTION_CONTINUE + , + /** + * Signal the end of the data stream. + */ + mhd_DCC_ACTION_FINISH + , + /** + * Suspend requests (connection) + */ + mhd_DCC_ACTION_SUSPEND + , + /** + * Hard close request with no or partial response + */ + mhd_DCC_ACTION_ABORT +}; + +/** + * Check whether provided mhd_ActionType value is valid + */ +#define mhd_DCC_ACTION_IS_VALID(act) \ + ((mhd_DCC_ACTION_CONTINUE <= (act)) && (mhd_DCC_ACTION_ABORT >= (act))) + + +#ifndef MHD_FREECALLBACK_DEFINED + +/** + * This method is called by libmicrohttpd when response with dynamic content + * is being destroyed. It should be used to free resources associated + * with the dynamic content. + * + * @param[in] free_cls closure + * @ingroup response + */ +typedef void +(*MHD_FreeCallback) (void *free_cls); + +#define MHD_FREECALLBACK_DEFINED 1 +#endif /* ! MHD_FREECALLBACK_DEFINED */ +#ifndef MHD_DYNCONTENTZCIOVEC_DEFINED + + +/** + * Structure for iov type of the response. + * Used for zero-copy response content data. + */ +struct MHD_DynContentZCIoVec +{ + /** + * The number of elements in @a iov + */ + unsigned int iov_count; + /** + * The pointer to the array with @a iov_count elements. + */ + const struct MHD_IoVec *iov; + /** + * The callback to free resources. + * It is called once the full array of iov elements is sent. + * No callback is called if NULL. + */ + MHD_FreeCallback iov_fcb; + /** + * The parameter for @a iov_fcb + */ + void *iov_fcb_cls; +}; + +#define MHD_DYNCONTENTZCIOVEC_DEFINED 1 +#endif /* ! MHD_DYNCONTENTZCIOVEC_DEFINED */ + +/** + * The data for DCC "continue" action + */ +struct mhd_DccActionContinueData +{ + /** + * The size of the content data in the buffer + */ + size_t buf_data_size; + /** + * Zero-copy content data data + */ + const struct MHD_DynContentZCIoVec *iov_data; +}; + + +/** + * The data for the DCC application action + */ +union mhd_DccActionData +{ + /** + * The data for the action #mhd_DCC_ACTION_CONTINUE + */ + struct mhd_DccActionContinueData cntnue; +}; + + +/** + * The action type returned by Dynamic Content Creator callback + */ +struct MHD_DynamicContentCreatorAction +{ + /** + * The action + */ + enum mhd_DccActionType act; + + /** + * The data for the @a act action + */ + union mhd_DccActionData data; +}; + +#endif /* ! MHD_DCC_ACTION_H */ diff --git a/src/mhd2/mhd_dlinked_list.h b/src/mhd2/mhd_dlinked_list.h @@ -0,0 +1,290 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_dlinked_list.h + * @brief Double-linked list macros and declarations + * @author Karlson2k (Evgeny Grin) + * + * Double-linked list macros help create and manage the chain of objects + * connected via inter-object pointers (named here @a links_name), while + * the list is held by the owner within the helper (named here @a list_name). + */ + +#ifndef MHD_DLINKED_LIST_H +#define MHD_DLINKED_LIST_H 1 + +#include "mhd_sys_options.h" + +#include "sys_null_macro.h" +#include "mhd_assert.h" + + +/* This header defines macros for handling double-linked lists of object. + The pointers to the first and the last objects in the list are held in + the "owner". + The list member objects links to each other via "next" and "prev" links. + Each member object can be part of several lists. For example, connections are + maintained in "all connections" and "need to process" lists simultaneously. + List member can be removed from the list or inserted to the list at any + moment. + Typically the name of the list (inside the "owner" object) is the same as + the name of inter-links. However, it is possible to use different names. + For example, connections can be removed from "all connections" list and + moved the "clean up" list using the same internal links "all connections". + As this is a double-linked list, it can be walked from begin to the end and + in the opposite direction. + The list is compatible only with "struct" types. + */ + +/* Helpers */ + +#define mhd_DLNKDL_LIST_TYPE_(base_name) struct base_name ## s_list + +#define mhd_DLNKDL_LINKS_TYPE_(base_name) struct base_name ## _link + + +/* Names */ + +/** + * The name of struct that hold the list in the owner object + */ +#define mhd_DLNKDL_LIST_TYPE(base_name) mhd_DLNKDL_LIST_TYPE_ (base_name) + +/** + * The name of struct that links between the list members + */ +#define mhd_DLNKDL_LINKS_TYPE(base_name) mhd_DLNKDL_LINKS_TYPE_ (base_name) + +/* Definitions of the structures */ + +/** + * Template for declaration of the list helper struct + * @param l_type the name of the struct objects that list links + */ +#define mhd_DLINKEDL_LIST_DEF(l_type) \ + mhd_DLNKDL_LIST_TYPE (l_type) { /* Holds the list in the owner */ \ + struct l_type *first; /* The pointer to the first object in the list */ \ + struct l_type *last; /* The pointer to the last object in the list */ \ + } + +/** + * Template for declaration of links helper struct + * @param l_type the name of the struct objects that list links + */ +#define mhd_DLINKEDL_LINKS_DEF(l_type) \ + mhd_DLNKDL_LINKS_TYPE (l_type) { /* Holds the links in the members */ \ + struct l_type *prev; /* The pointer to the previous object in the list */ \ + struct l_type *next; /* The pointer to the next object in the list */ \ + } + + +/** + * Template for declaration of list helper structs + * @param l_type the name of the struct objects that list links + */ +#define mhd_DLINKEDL_STRUCTS_DEFS(l_type) \ + mhd_DLINKEDL_LIST_DEF (l_type); mhd_DLINKEDL_LINKS_DEF (l_type) + +/* Declarations for the list owners and the list members */ + +/** + * Declare the owner's list member + */ +#define mhd_DLNKDL_LIST(l_type,list_name) \ + mhd_DLNKDL_LIST_TYPE (l_type) list_name + +/** + * Declare the list object links member + */ +#define mhd_DLNKDL_LINKS(l_type,links_name) \ + mhd_DLNKDL_LINKS_TYPE (l_type) links_name + +/* Direct work with the list */ +/* These macros directly use the pointer to the list and allow using + * names of the list object (within the owner object) different from the names + * of link object (in the list members). */ + +/** + * Initialise the double linked list pointers in the list object using + * the directly pointer to the list + * @param p_list the pointer to the list + * @param l_name the name of the list + */ +#define mhd_DLINKEDL_INIT_LIST_D(p_list) \ + do {(p_list)->first = NULL; (p_list)->last = NULL;} while (0) + +/** + * Insert object into the first position in the list using direct pointer + * to the list + * @param p_list the pointer to the list + * @param p_obj the pointer to the new list member object to insert to + * the @a l_name list + * @param l_name the name of the list + */ +#define mhd_DLINKEDL_INS_FIRST_D(p_list,p_obj,links_name) do { \ + mhd_assert (NULL == (p_obj)->links_name.prev); \ + mhd_assert (NULL == (p_obj)->links_name.next); \ + mhd_assert (((p_list)->first) || (! ((p_list)->last))); \ + mhd_assert ((! ((p_list)->first)) || ((p_list)->last)); \ + if (NULL != (p_list)->first) \ + { mhd_assert (NULL == (p_list)->first->links_name.prev); \ + (p_obj)->links_name.next = (p_list)->first; \ + (p_obj)->links_name.next->links_name.prev = (p_obj); } else \ + { (p_list)->last = (p_obj); } \ + (p_list)->first = (p_obj); } while (0) + +/** + * Insert object into the last position in the list using direct pointer + * to the list + * @param p_list the pointer to the list + * @param p_obj the pointer to the new list member object to insert to + * the @a l_name list + * @param l_name the name of the list + */ +#define mhd_DLINKEDL_INS_LAST_D(p_list,p_obj,links_name) do { \ + mhd_assert (NULL == (p_obj)->links_name.prev); \ + mhd_assert (NULL == (p_obj)->links_name.next); \ + mhd_assert (((p_list)->first) || (! ((p_list)->last))); \ + mhd_assert ((! ((p_list)->first)) || ((p_list)->last)); \ + if (NULL != (p_list)->last) \ + { mhd_assert (NULL == (p_list)->last->links_name.next); \ + (p_obj)->links_name.prev = (p_list)->last; \ + (p_obj)->links_name.prev->links_name.next = (p_obj); } else \ + { (p_list)->first = (p_obj); } \ + (p_list)->last = (p_obj); } while (0) + +/** + * Remove object from the list using direct pointer to the list + * @param p_list the pointer to the list + * @param p_own the pointer to the existing @a l_name list member object + * to remove from the list + * @param l_name the name of the list + */ +#define mhd_DLINKEDL_DEL_D(p_list,p_obj,links_name) do { \ + mhd_assert (NULL != (p_list)->first); \ + mhd_assert (NULL != (p_list)->last); \ + if (NULL != (p_obj)->links_name.next) \ + { mhd_assert (NULL != (p_obj)->links_name.next->links_name.prev); \ + (p_obj)->links_name.next->links_name.prev = \ + (p_obj)->links_name.prev; } else \ + { mhd_assert ((p_obj) == (p_list)->last); \ + (p_list)->last = (p_obj)->links_name.prev; } \ + if (NULL != (p_obj)->links_name.prev) \ + { mhd_assert (NULL != (p_obj)->links_name.prev->links_name.next); \ + (p_obj)->links_name.prev->links_name.next = \ + (p_obj)->links_name.next; } else \ + { mhd_assert ((p_obj) == (p_list)->first); \ + (p_list)->first = (p_obj)->links_name.next; } \ + (p_obj)->links_name.prev = NULL; \ + (p_obj)->links_name.next = NULL; } while (0) + +/** + * Get the fist object in the list using direct pointer to the list + */ +#define mhd_DLINKEDL_GET_FIRST_D(p_list) ((p_list)->first) + +/** + * Get the last object in the list using direct pointer to the list + */ +#define mhd_DLINKEDL_GET_LAST_D(p_list) ((p_list)->last) + + +/* ** The main interface ** */ +/* These macros use identical names for the list object itself (within the + * owner object) and the links object (within the list members). */ + +/* Initialisers */ + +/** + * Initialise the double linked list pointers in the owner object + * @param p_own the pointer to the owner object with the @a l_name list + * @param l_name the name of the list + */ +#define mhd_DLINKEDL_INIT_LIST(p_own,list_name) \ + mhd_DLINKEDL_INIT_LIST_D (&((p_own)->list_name)) + +/** + * Initialise the double linked list pointers in the list member object + * @param p_obj the pointer to the future member object of the @a l_name list + * @param l_name the name of the list + */ +#define mhd_DLINKEDL_INIT_LINKS(p_obj,links_name) \ + do {(p_obj)->links_name.prev = NULL; \ + (p_obj)->links_name.next = NULL;} while (0) + +/* List manipulations */ + +/** + * Insert object into the first position in the list + * @param p_own the pointer to the owner object with the @a l_name list + * @param p_obj the pointer to the new list member object to insert to + * the @a l_name list + * @param l_name the same name for the list and the links + */ +#define mhd_DLINKEDL_INS_FIRST(p_own,p_obj,l_name) \ + mhd_DLINKEDL_INS_FIRST_D (&((p_own)->l_name),(p_obj),l_name) + +/** + * Insert object into the last position in the list + * @param p_own the pointer to the owner object with the @a l_name list + * @param p_obj the pointer to the new list member object to insert to + * the @a l_name list + * @param l_name the same name for the list and the links + */ +#define mhd_DLINKEDL_INS_LAST(p_own,p_obj,l_name) \ + mhd_DLINKEDL_INS_LAST_D (&((p_own)->l_name),(p_obj),l_name) + +/** + * Remove object from the list + * @param p_mem the pointer to the owner object with the @a l_name list + * @param p_own the pointer to the existing @a l_name list member object + * to remove from the list + * @param l_name the same name for the list and the links + */ +#define mhd_DLINKEDL_DEL(p_own,p_obj,l_name) \ + mhd_DLINKEDL_DEL_D (&((p_own)->l_name),(p_obj),l_name) + +/* List iterations */ + +/** + * Get the fist object in the list + */ +#define mhd_DLINKEDL_GET_FIRST(p_own,list_name) \ + mhd_DLINKEDL_GET_FIRST_D (&((p_own)->list_name)) + +/** + * Get the last object in the list + */ +#define mhd_DLINKEDL_GET_LAST(p_own,list_name) \ + mhd_DLINKEDL_GET_LAST_D (&((p_own)->list_name)) + +/** + * Get the next object in the list + */ +#define mhd_DLINKEDL_GET_NEXT(p_obj,links_name) ((p_obj)->links_name.next) + +/** + * Get the previous object in the list + */ +#define mhd_DLINKEDL_GET_PREV(p_obj,links_name) ((p_obj)->links_name.prev) + + +#endif /* ! MHD_DLINKED_LIST_H */ diff --git a/src/mhd2/mhd_iovec.h b/src/mhd2/mhd_iovec.h @@ -0,0 +1,114 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_iovec.h + * @brief The definition of the tristate type and helper macros + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_IOVEC_H +#define MHD_IOVEC_H 1 + +#include "mhd_sys_options.h" +#include "mhd_socket_type.h" +#include "sys_base_types.h" + +#if defined(HAVE_WRITEV) || defined(HAVE_SENDMSG) +# ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +# ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +# elif defined(HAVE_UNISTD_H) +# include <unistd.h> +# endif +# ifdef HAVE_SOCKLIB_H +# include <sockLib.h> +# endif +# ifdef HAVE_SYS_UIO_H +# include <sys/uio.h> +# endif +#endif + +#include "mhd_limits.h" + +#if defined(MHD_WINSOCK_SOCKETS) +/** + * Internally used I/O vector type for use with winsock. + * Binary matches system "WSABUF". + */ +struct mhd_w32_iovec +{ + unsigned long iov_len; + char *iov_base; +}; +typedef struct mhd_w32_iovec mhd_iovec; +#define mhd_IOV_ELMN_MAX_SIZE ULONG_MAX +typedef unsigned long mhd_iov_elmn_size; +#define mhd_IOV_RET_MAX_SIZE LONG_MAX +typedef long mhd_iov_ret_type; +#elif defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) +/** + * Internally used I/O vector type for use when writev or sendmsg + * is available. Matches system "struct iovec". + */ +typedef struct iovec mhd_iovec; +#define mhd_IOV_ELMN_MAX_SIZE SIZE_MAX +typedef size_t mhd_iov_elmn_size; +#define mhd_IOV_RET_MAX_SIZE SSIZE_MAX +typedef ssize_t mhd_iov_ret_type; +#else +/** + * Internally used I/O vector type for use when writev or sendmsg + * is not available. + */ +typedef struct MHD_IoVec mhd_iovec; +#define mhd_IOV_ELMN_MAX_SIZE SIZE_MAX +typedef size_t mhd_iov_elmn_size; +#define mhd_IOV_RET_MAX_SIZE SSIZE_MAX +typedef ssize_t mhd_iov_ret_type; +#endif + + +struct mhd_iovec_track +{ + /** + * The copy of array of iovec elements. + * The copy of elements are updated during sending. + * The number of elements is not changed during lifetime. + */ + mhd_iovec *iov; + + /** + * The number of elements in @a iov. + * This value is not changed during lifetime. + */ + size_t cnt; + + /** + * The number of sent elements. + * At the same time, it is the index of the next (or current) element + * to send. + */ + size_t sent; +}; + +#endif /* ! MHD_IOVEC_H */ diff --git a/src/mhd2/mhd_itc.c b/src/mhd2/mhd_itc.c @@ -0,0 +1,45 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +*/ + +/** + * @file src/mhd2/mhd_itc.c + * @brief Implementation of inter-thread communication functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_itc.h" +#if defined(MHD_ITC_PIPE_) +# ifdef MHD_HAVE_MHD_ITC_NONBLOCKING +# include "mhd_sockets_funcs.h" +# ifndef MHD_POSIX_SOCKETS +#error Pipe-based ITC can be used only with POSIX sockets +# endif + +MHD_INTERNAL bool +mhd_itc_nonblocking (struct mhd_itc *pitc) +{ + return mhd_socket_nonblocking ((MHD_Socket) pitc->sk[0]) && + mhd_socket_nonblocking ((MHD_Socket) pitc->sk[1]); +} + + +# endif /* ! MHD_HAVE_MHD_ITC_NONBLOCKING */ +#endif /* MHD_ITC_PIPE_ */ diff --git a/src/mhd2/mhd_itc.h b/src/mhd2/mhd_itc.h @@ -0,0 +1,343 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016-2024 Evgeny Grin (Karlson2k), Christian Grothoff + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_itc.h + * @brief Header for platform-independent inter-thread communication + * @author Karlson2k (Evgeny Grin) + * @author Christian Grothoff + * + * Provides basic abstraction for inter-thread communication. + * Any functions can be implemented as macro on some platforms + * unless explicitly marked otherwise. + * Any "function" argument can be unused in macro, so avoid + * variable modification in function parameters. + */ +#ifndef MHD_ITC_H +#define MHD_ITC_H 1 +#include "mhd_itc_types.h" + +#include "mhd_panic.h" + +#if defined(MHD_ITC_EVENTFD_) + +/* **************** Optimised ITC implementation by eventfd ********** */ +# include <sys/eventfd.h> +# include <stdint.h> /* for uint_fast64_t */ +# ifdef HAVE_UNISTD_H +# include <unistd.h> /* for read(), write() */ +# else +# include <stdlib.h> +# endif /* HAVE_UNISTD_H */ +# include "sys_errno.h" + +/** + * Number of FDs used by every ITC. + */ +# define mhd_ITC_NUM_FDS (1) + +/** + * Set @a itc to the invalid value. + * @param pitc the pointer to the itc to set + */ +#define mhd_itc_set_invalid(pitc) ((pitc)->fd = -1) + +/** + * Check whether ITC has valid value. + * + * Macro check whether @a itc value is valid (allowed), + * macro does not check whether @a itc was really initialised. + * @param itc the itc to check + * @return boolean true if @a itc has valid value, + * boolean false otherwise. + */ +#define mhd_ITC_IS_VALID(itc) (0 <= ((itc).fd)) + +/** + * Initialise ITC by generating eventFD + * @param pitc the pointer to the ITC to initialise + * @return non-zero if succeeded, zero otherwise + */ +# define mhd_itc_init(pitc) \ + (-1 != ((pitc)->fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK))) + +/** + * Helper for mhd_itc_activate() + */ +# ifdef HAVE_COMPOUND_LITERALS_LVALUES +# define mhd_ITC_WR_DATA ((uint_fast64_t){1}) +# else +/** + * Internal static const helper for mhd_itc_activate() + */ +static const uint_fast64_t mhd_ITC_WR_DATA = 1; +# endif + +/** + * Activate signal on the @a itc + * @param itc the itc to use + * @return non-zero if succeeded, zero otherwise + */ +#define mhd_itc_activate(itc) \ + ((write ((itc).fd, (const void*) &mhd_ITC_WR_DATA, 8) > 0) \ + || (EAGAIN == errno)) + +/** + * Return read FD of @a itc which can be used for poll(), select() etc. + * @param itc the itc to get FD + * @return FD of read side + */ +#define mhd_itc_r_fd(itc) ((itc).fd) + +/** + * Clear signalled state on @a itc + * @param itc the itc to clear + */ +#define mhd_itc_clear(itc) \ + do { uint_fast64_t __b; \ + (void) read ((itc).fd, (void*) &__b, 8); \ + } while (0) + +/** + * Destroy previously initialised ITC. Note that close() + * on some platforms returns odd errors, so we ONLY fail + * if the errno is EBADF. + * @param itc the itc to destroy + * @return non-zero if succeeded, zero otherwise + */ +#define mhd_itc_destroy(itc) \ + ((0 == close ((itc).fd)) || (EBADF != errno)) + +#elif defined(MHD_ITC_PIPE_) + +/* **************** Standard UNIX ITC implementation by pipe ********** */ + +# if defined(HAVE_PIPE2_FUNC) +# include <fcntl.h> /* for O_CLOEXEC, O_NONBLOCK */ +# endif /* HAVE_PIPE2_FUNC && HAVE_FCNTL_H */ +# ifdef HAVE_UNISTD_H +# include <unistd.h> /* for read(), write() */ +# else +# include <stdlib.h> +# endif /* HAVE_UNISTD_H */ +# include "sys_errno.h" +# if defined(HAVE_PIPE2_FUNC) && defined(O_CLOEXEC) && defined(O_NONBLOCK) +# define MHD_USE_PIPE2 1 +# else +# include "sys_bool_type.h" +# endif + + +/** + * Number of FDs used by every ITC. + */ +# define mhd_ITC_NUM_FDS (2) + +/** + * Set @a itc to the invalid value. + * @param pitc the pointer to the itc to set + */ +# define mhd_itc_set_invalid(pitc) ((pitc)->fd[0] = (pitc)->fd[1] = -1) + +/** + * Check whether ITC has valid value. + * + * Macro check whether @a itc value is valid (allowed), + * macro does not check whether @a itc was really initialised. + * @param itc the itc to check + * @return boolean true if @a itc has valid value, + * boolean false otherwise. + */ +# define mhd_ITC_IS_VALID(itc) (0 <= (itc).fd[0]) + +/** + * Initialise ITC by generating pipe + * @param pitc the pointer to the ITC to initialise + * @return non-zero if succeeded, zero otherwise + */ +# if defined(MHD_USE_PIPE2) +# define mhd_itc_init(pitc) (! pipe2 ((pitc)->fd, O_CLOEXEC | O_NONBLOCK)) +# else /* ! MHD_USE_PIPE2 */ +# define mhd_itc_init(pitc) \ + ( (! pipe ((pitc)->fd)) ? \ + (mhd_itc_nonblocking ((pitc)) ? \ + (! 0) : \ + (mhd_itc_destroy (*(pitc)), 0) ) \ + : (0) ) + +# define MHD_HAVE_MHD_ITC_NONBLOCKING 1 +/** + * Change itc FD options to be non-blocking. + * + * @param pitc the pointer to ITC to manipulate + * @return true if succeeded, false otherwise + */ +MHD_INTERNAL bool +mhd_itc_nonblocking (struct mhd_itc *pitc); + +# endif /* ! MHD_USE_PIPE2 */ + +/** + * Activate signal on @a itc + * @param itc the itc to use + * @return non-zero if succeeded, zero otherwise + */ +# define mhd_itc_activate(itc) \ + ((write ((itc).fd[1], (const void*) "", 1) > 0) || (EAGAIN == errno)) + +/** + * Return read FD of @a itc which can be used for poll(), select() etc. + * @param itc the itc to get FD + * @return FD of read side + */ +# define mhd_itc_r_fd(itc) ((itc).fd[0]) + +/** + * Clear signaled state on @a itc + * @param itc the itc to clear + */ +# define mhd_itc_clear(itc) do \ + { long __b; \ + while (0 < read ((itc).fd[0], (void*) &__b, sizeof(__b))) \ + {(void) 0;} } while (0) + +/** + * Destroy previously initialised ITC + * @param itc the itc to destroy + * @return non-zero if succeeded, zero otherwise + */ +# define mhd_itc_destroy(itc) \ + (0 == (close ((itc).fd[0]) + close ((itc).fd[1]))) + +#elif defined(MHD_ITC_SOCKETPAIR_) + +/* **************** ITC implementation by socket pair ********** */ + +# include "mhd_sockets_macros.h" +# if ! defined(mhd_socket_pair_nblk) +# include "mhd_sockets_funcs.h" +# endif + +/** + * Number of FDs used by every ITC. + */ +# define mhd_ITC_NUM_FDS (2) + +/** + * Set @a itc to the invalid value. + * @param pitc the pointer to the itc to set + */ +# define mhd_itc_set_invalid(pitc) \ + ((pitc)->sk[0] = (pitc)->sk[1] = MHD_INVALID_SOCKET) + +/** + * Check whether ITC has valid value. + * + * Macro check whether @a itc value is valid (allowed), + * macro does not check whether @a itc was really initialised. + * @param itc the itc to check + * @return boolean true if @a itc has valid value, + * boolean false otherwise. + */ +# define mhd_ITC_IS_VALID(itc) (MHD_INVALID_SOCKET != (itc).sk[0]) + +/** + * Initialise ITC by generating socketpair + * @param itc the itc to initialise + * @return non-zero if succeeded, zero otherwise + */ +# ifdef mhd_socket_pair_nblk +# define mhd_itc_init(pitc) mhd_socket_pair_nblk ((pitc)->sk) +# else /* ! mhd_socket_pair_nblk */ +# define mhd_itc_init(pitc) \ + ( (! mhd_socket_pair ((pitc)->sk)) ? \ + (0) : ( (! mhd_itc_nonblocking ((pitc))) ? \ + (mhd_itc_destroy (*(pitc)), 0) : (! 0) ) ) + +/** + * Change itc FD options to be non-blocking. + * + * @param pitc the pointer to ITC to manipulate + * @return true if succeeded, false otherwise + */ +# define mhd_itc_nonblocking(pitc) \ + (mhd_socket_nonblocking ((pitc)->sk[0]) && \ + mhd_socket_nonblocking ((pitc)->sk[1])) + +# endif /* ! mhd_socket_pair_nblk */ + +/** + * Activate signal on @a itc + * @param itc the itc to use + * @return non-zero if succeeded, zero otherwise + */ +# define mhd_itc_activate(itc) \ + ((0 < mhd_sys_send ((itc).sk[1], "", 1)) || mhd_SCKT_LERR_IS_EAGAIN ()) + +/** + * Return read FD of @a itc which can be used for poll(), select() etc. + * @param itc the itc to get FD + * @return FD of read side + */ +# define mhd_itc_r_fd(itc) ((itc).sk[0]) + +/** + * Clear signaled state on @a itc + * @param itc the itc to clear + */ +# define mhd_itc_clear(itc) do \ + { long __b; \ + while (0 < recv ((itc).sk[0], (void*) &__b, sizeof(__b), 0)) \ + {(void) 0;} } while (0) + +/** + * Destroy previously initialised ITC + * @param itc the itc to destroy + * @return non-zero if succeeded, zero otherwise + */ +# define mhd_itc_destroy(itc) \ + (mhd_socket_close ((itc).sk[1]) ? \ + mhd_socket_close ((itc).sk[0]) : \ + ((void) mhd_socket_close ((itc).sk[0]), ! ! 0) ) + +#endif /* MHD_ITC_SOCKETPAIR_ */ + +/** + * Destroy previously initialised ITC and abort execution + * if error is detected. + * @param itc the itc to destroy + */ +#define mhd_itc_destroy_chk(itc) do { \ + if (! mhd_itc_destroy (itc)) \ + MHD_PANIC ("Failed to destroy ITC.\n"); \ +} while (0) + +/** + * Check whether ITC has invalid value. + * + * Macro check whether @a itc value is invalid, + * macro does not check whether @a itc was destroyed. + * @param itc the itc to check + * @return boolean true if @a itc has invalid value, + * boolean false otherwise. + */ +#define mhd_ITC_IS_INVALID(itc) (! mhd_ITC_IS_VALID (itc)) + +#endif /* ! MHD_ITC_H */ diff --git a/src/mhd2/mhd_itc_types.h b/src/mhd2/mhd_itc_types.h @@ -0,0 +1,94 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016-2024 Evgeny Grin (Karlson2k), Christian Grothoff + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_itc_types.h + * @brief Types for platform-independent inter-thread communication + * @author Karlson2k (Evgeny Grin) + * @author Christian Grothoff + * + * Provides basic types for inter-thread communication. + * Designed to be included by other headers. + */ +#ifndef MHD_ITC_TYPES_H +#define MHD_ITC_TYPES_H 1 +#include "mhd_sys_options.h" + +/* Force socketpair on native W32 */ +#if defined(_WIN32) && ! defined(__CYGWIN__) && ! defined(MHD_ITC_SOCKETPAIR_) +#error MHD_ITC_SOCKETPAIR_ is not defined on native W32 platform +#endif /* _WIN32 && !__CYGWIN__ && !MHD_ITC_SOCKETPAIR_ */ + +#if defined(MHD_ITC_EVENTFD_) +/* **************** Optimised ITC implementation by eventfd ********** */ + +/** + * Data type for a MHD ITC. + */ +struct mhd_itc +{ + int fd; +}; + +/** + * Static initialiser for struct mhd_itc + */ +# define mhd_ITC_STATIC_INIT_INVALID { -1 } + + +#elif defined(MHD_ITC_PIPE_) +/* **************** Standard UNIX ITC implementation by pipe ********** */ + +/** + * Data type for a MHD ITC. + */ +struct mhd_itc +{ + int fd[2]; +}; + +/** + * Static initialiser for struct mhd_itc + */ +# define mhd_ITC_STATIC_INIT_INVALID { { -1, -1 } } + + +#elif defined(MHD_ITC_SOCKETPAIR_) +/* **************** ITC implementation by socket pair ********** */ + +# include "mhd_socket_type.h" + +/** + * Data type for a MHD ITC. + */ +struct mhd_itc +{ + MHD_Socket sk[2]; +}; + +/** + * Static initialiser for struct mhd_itc + */ +# define mhd_ITC_STATIC_INIT_INVALID \ + { { MHD_INVALID_SOCKET, MHD_INVALID_SOCKET } } + +#endif /* MHD_ITC_SOCKETPAIR_ */ + +#endif /* ! MHD_ITC_TYPES_H */ diff --git a/src/mhd2/mhd_lib_init.c b/src/mhd2/mhd_lib_init.c @@ -0,0 +1,89 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_lib_init_impl.c + * @brief Library global initialisers and de-initialisers + * @author Karlson2k (Evgeny Grin) + */ +#include "mhd_sys_options.h" +#include "mhd_lib_init.h" +#include "mhd_panic.h" +#include "mhd_mono_clock.h" +#include "mhd_socket_type.h" +#include "mhd_send.h" +#ifdef MHD_WINSOCK_SOCKETS +# include <winsock2.h> +#endif + +void +mhd_lib_global_init (void) +{ + mhd_panic_init_default (); + +#if defined(MHD_WINSOCK_SOCKETS) + if (1) + { + WSADATA wsd; + if ((0 != WSAStartup (MAKEWORD (2, 2), &wsd)) || (MAKEWORD (2, 2) != wsd. + wVersion)) + MHD_PANIC ("Failed to initialise WinSock."); + } +#endif /* MHD_WINSOCK_SOCKETS */ + MHD_monotonic_msec_counter_init(); + mhd_send_init_static_vars(); +} + + +void +mhd_lib_global_deinit (void) +{ + MHD_monotonic_msec_counter_finish(); +#if defined(MHD_WINSOCK_SOCKETS) + (void) WSACleanup (); +#endif /* MHD_WINSOCK_SOCKETS */ +} + + +#ifndef _AUTOINIT_FUNCS_ARE_SUPPORTED +static volatile int mhd_lib_global_inited = 0; +static volatile int mhd_lib_global_not_inited = ! 0; + +MHD_EXTERN_ void +MHD_lib_global_check_init (void) +{ + if ((! mhd_lib_global_inited) || (mhd_lib_global_not_inited)) + mhd_lib_global_init (); + mhd_lib_global_inited = ! 0; + mhd_lib_global_not_inited = 0; +} + + +MHD_EXTERN_ void +MHD_lib_global_check_deinit (void) +{ + if ((mhd_lib_global_inited) && (! mhd_lib_global_not_inited)) + mhd_lib_global_deinit (); + mhd_lib_global_inited = 0; + mhd_lib_global_not_inited = ! 0; +} + + +#endif /* ! _AUTOINIT_FUNCS_ARE_SUPPORTED */ diff --git a/src/mhd2/mhd_lib_init.h b/src/mhd2/mhd_lib_init.h @@ -0,0 +1,66 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_lib_init.h + * @brief Declarations for the library global initialiser + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_LIB_INIT_H +#define MHD_LIB_INIT_H 1 +#include "mhd_sys_options.h" +#include "autoinit_funcs.h" + +/** + * Initialise library global resources + */ +void +mhd_lib_global_init (void); + +/** + * Deinitialise and free library global resources + */ +void +mhd_lib_global_deinit (void); + +#ifdef _AUTOINIT_FUNCS_ARE_SUPPORTED +# define MHD_GLOBAL_INIT_CHECK() ((void) 0) +#else /* ! _AUTOINIT_FUNCS_ARE_SUPPORTED */ +/* The functions are exported, but not declared in public header */ + +/** + * Check whether the library was initialised and initialise if needed + */ +MHD_EXTERN_ void +MHD_lib_global_check_init (void); + +/** + * Check whether the library has been de-initialised and de-initialise if needed + */ +MHD_EXTERN_ void +MHD_lib_global_check_deinit (void) + +# define MHD_GLOBAL_INIT_CHECK() MHD_lib_global_check_init () + +#endif /* ! _AUTOINIT_FUNCS_ARE_SUPPORTED */ + + +#endif /* ! MHD_LIB_INIT_H */ diff --git a/src/mhd2/mhd_lib_init_impl.h b/src/mhd2/mhd_lib_init_impl.h @@ -0,0 +1,76 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_lib_init_impl.h + * @brief Library global initialisers and de-initialisers + * @author Karlson2k (Evgeny Grin) + * + * This file should be a .c file, but used as .h file to workaround + * a GCC/binutils bug. + */ + +#ifdef MHD_LIB_INIT_IMPL_H +#error This file must not be included more the one time only +#endif +#define MHD_LIB_INIT_IMPL_H 1 + +/* Due to the bug in GCC/binutils, on some platforms (at least on W32) + * the automatic initialisation functions are not called when library is used + * as a static library and no function is used/referred from the same + * object/module/c-file. + */ +#ifndef MHD_LIB_INIT_IMPL_H_IN_DAEMON_CREATE_C +#error This file must in included only in 'daemon_create.c' file +#else /* MHD_LIB_INIT_IMPL_H_IN_DAEMON_CREATE_C */ + +#include "mhd_sys_options.h" +#include "mhd_lib_init.h" + +/* Forward declarations */ +void +mhd_lib_global_init_wrap (void); + +void +mhd_lib_global_deinit_wrap (void); + + +void +mhd_lib_global_init_wrap (void) +{ + mhd_lib_global_init (); +} + + +void +mhd_lib_global_deinit_wrap (void) +{ + mhd_lib_global_deinit (); +} + + +#ifdef _AUTOINIT_FUNCS_ARE_SUPPORTED + +_SET_INIT_AND_DEINIT_FUNCS (mhd_lib_global_init_wrap, \ + mhd_lib_global_deinit_wrap); + +#endif /* _AUTOINIT_FUNCS_ARE_SUPPORTED */ + +#endif /* MHD_LIB_INIT_IMPL_H_IN_DAEMON_CREATE_C */ diff --git a/src/mhd2/mhd_limits.h b/src/mhd2/mhd_limits.h @@ -0,0 +1,177 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file src/mhd2/mhd_limits.h + * @brief limits values definitions + * @author Karlson2k (Evgeny Grin) + * + * This file provides maximum types values as macros. Macros may not work + * in preprocessor expressions, while macros always work in compiler + * expressions. + * This file does not include <stdint.h> and other type-definitions files. + * For best portability, make sure that this file is included after required + * type-definitions files. + */ + +#ifndef MHD_LIMITS_H +#define MHD_LIMITS_H + +#include "mhd_sys_options.h" + +#ifdef HAVE_LIMITS_H +# include <limits.h> +#endif /* HAVE_LIMITS_H */ + +#define mhd_UNSIGNED_TYPE_MAX(type) ((type)(~((type) 0))) + +/* Assume 8 bits per byte, no padding bits. */ +#define mhd_SIGNED_TYPE_MAX(type) \ + ( (type) ((( ((type) 1) << (sizeof(type) * 8 - 2)) - 1) * 2 + 1) ) + +/* The maximum value for signed type, based on knowledge of unsigned counterpart + type */ +#define mhd_SIGNED_TYPE_MAX2(type,utype) ((type)(((utype)(~((utype)0))) >> 1)) + +#define mhd_IS_TYPE_SIGNED(type) (((type) 0) > ((type) - 1)) + +#if defined(__GNUC__) || defined(__clang__) +# define mhd_USE_PREDEF_LIMITS +#endif + +#ifndef INT_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__INT_MAX__) +# define INT_MAX __INT_MAX__ +# else /* ! __INT_MAX__ */ +# define INT_MAX mhd_SIGNED_TYPE_MAX2 (int, unsigned int) +# endif /* ! __INT_MAX__ */ +#endif /* ! INT_MAX */ + +#ifndef UINT_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__UINT_MAX__) +# define UINT_MAX __UINT_MAX__ +# else /* ! __UINT_MAX__ */ +# define UINT_MAX mhd_UNSIGNED_TYPE_MAX (unsigned int) +# endif /* ! __UINT_MAX__ */ +#endif /* !UINT_MAX */ + +#ifndef LONG_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__LONG_MAX__) +# define LONG_MAX __LONG_MAX__ +# else /* ! __LONG_MAX__ */ +# define LONG_MAX mhd_SIGNED_TYPE_MAX2 (long, unsigned long) +# endif /* ! __LONG_MAX__ */ +#endif /* !LONG_MAX */ + +#ifndef ULONG_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__ULONG_MAX__) +# define ULONG_MAX ULONG_MAX +# else /* ! __ULONG_MAX__ */ +# define ULONG_MAX mhd_UNSIGNED_TYPE_MAX (unsigned long) +# endif /* ! __ULONG_MAX__ */ +#endif /* !ULONG_MAX */ + +#ifndef ULLONG_MAX +# ifdef ULONGLONG_MAX +# define ULLONG_MAX ULONGLONG_MAX +# else /* ! ULONGLONG_MAX */ +# define ULLONG_MAX mhd_UNSIGNED_TYPE_MAX (unsigned long long) +# endif /* ! ULONGLONG_MAX */ +#endif /* !ULLONG_MAX */ + +#ifndef INT32_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__INT32_MAX__) +# define INT32_MAX __INT32_MAX__ +# else /* ! __INT32_MAX__ */ +# define INT32_MAX ((int32_t) 0x7FFFFFFF) +# endif /* ! __INT32_MAX__ */ +#endif /* !INT32_MAX */ + +#ifndef UINT32_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__UINT32_MAX__) +# define UINT32_MAX __UINT32_MAX__ +# else /* ! __UINT32_MAX__ */ +# define UINT32_MAX ((uint32_t) 0xFFFFFFFFU) +# endif /* ! __UINT32_MAX__ */ +#endif /* !UINT32_MAX */ + +#ifndef INT64_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__INT64_MAX__) +# define INT64_MAX __INT64_MAX__ +# else /* ! __INT64_MAX__ */ +# define INT64_MAX ((int64_t) 0x7FFFFFFFFFFFFFFF) +# endif /* ! __UINT64_MAX__ */ +#endif /* !INT64_MAX */ + +#ifndef UINT64_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__UINT64_MAX__) +# define UINT64_MAX __UINT64_MAX__ +# else /* ! __UINT64_MAX__ */ +# define UINT64_MAX ((uint64_t) 0xFFFFFFFFFFFFFFFFU) +# endif /* ! __UINT64_MAX__ */ +#endif /* !UINT64_MAX */ + +#ifndef SIZE_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__SIZE_MAX__) +# define SIZE_MAX __SIZE_MAX__ +# else /* ! __SIZE_MAX__ */ +# define SIZE_MAX mhd_UNSIGNED_TYPE_MAX (size_t) +# endif /* ! __SIZE_MAX__ */ +#endif /* !SIZE_MAX */ + +#ifndef SSIZE_MAX +# if defined(mhd_USE_PREDEF_LIMITS) && defined(__SSIZE_MAX__) +# define SSIZE_MAX __SSIZE_MAX__ +# else +# define SSIZE_MAX mhd_SIGNED_TYPE_MAX2 (ssize_t, size_t) +# endif +#endif /* ! SSIZE_MAX */ + +#ifndef OFF_T_MAX +# ifdef OFF_MAX +# define OFF_T_MAX OFF_MAX +# elif defined(OFFT_MAX) +# define OFF_T_MAX OFFT_MAX +# elif defined(__APPLE__) && defined(__MACH__) +# define OFF_T_MAX INT64_MAX +# else +# define OFF_T_MAX mhd_SIGNED_TYPE_MAX (off_t) +# endif +#endif /* !OFF_T_MAX */ + +#if defined(_LARGEFILE64_SOURCE) && ! defined(OFF64_T_MAX) +# define OFF64_T_MAX mhd_SIGNED_TYPE_MAX (off64_t) +#endif /* _LARGEFILE64_SOURCE && !OFF64_T_MAX */ + +#ifndef TIME_T_MAX +# define TIME_T_MAX ((time_t) \ + (mhd_IS_TYPE_SIGNED (time_t) ? \ + mhd_SIGNED_TYPE_MAX (time_t) : \ + mhd_UNSIGNED_TYPE_MAX (time_t))) +#endif /* !TIME_T_MAX */ + +#ifndef TIMEVAL_TV_SEC_MAX +# ifndef _WIN32 +# define TIMEVAL_TV_SEC_MAX TIME_T_MAX +# else /* _WIN32 */ +# define TIMEVAL_TV_SEC_MAX LONG_MAX +# endif /* _WIN32 */ +#endif /* !TIMEVAL_TV_SEC_MAX */ + +#endif /* MHD_LIMITS_H */ diff --git a/src/mhd2/mhd_locks.h b/src/mhd2/mhd_locks.h @@ -0,0 +1,243 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2016-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_locks.h + * @brief Header for platform-independent locks abstraction + * @author Karlson2k (Evgeny Grin) + * @author Christian Grothoff + * + * Provides basic abstraction for locks/mutex. + * Any functions can be implemented as macro on some platforms + * unless explicitly marked otherwise. + * Any function argument can be skipped in macro, so avoid + * variable modification in function parameters. + * + * @warning Unlike pthread functions, most of functions return + * nonzero on success. + */ + +#ifndef MHD_LOCKS_H +#define MHD_LOCKS_H 1 + +#include "mhd_sys_options.h" + +#ifdef MHD_USE_THREADS + +#if defined(MHD_USE_W32_THREADS) +# define MHD_W32_MUTEX_ 1 +# if _WIN32_WINNT >= 0x0602 /* Win8 or later */ +# include <synchapi.h> +# else +# include <windows.h> +# endif +#elif defined(HAVE_PTHREAD_H) && defined(MHD_USE_POSIX_THREADS) +# define MHD_PTHREAD_MUTEX_ 1 +# include <pthread.h> +# ifdef HAVE_STDDEF_H +# include <stddef.h> /* for NULL */ +# else +# include "sys_base_types.h" +# endif +#else +#error No base mutex API is available. +#endif + +#include "mhd_panic.h" + +#if defined(MHD_PTHREAD_MUTEX_) +typedef pthread_mutex_t mhd_mutex; +#elif defined(MHD_W32_MUTEX_) +typedef CRITICAL_SECTION mhd_mutex; +#endif + +#if defined(MHD_PTHREAD_MUTEX_) +/** + * Initialise a new mutex. + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_init(pmutex) (! (pthread_mutex_init ((pmutex), NULL))) +#elif defined(MHD_W32_MUTEX_) +# if _WIN32_WINNT < 0x0600 +/* Before Vista */ +/** + * Initialise a new mutex. + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_init(pmutex) \ + (InitializeCriticalSectionAndSpinCount ((pmutex), 0)) +# else +/* The function always succeed starting from Vista */ +/** + * Initialise a new mutex. + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_init(pmutex) \ + (InitializeCriticalSection (pmutex), ! 0) +# endif +#endif + +#ifdef MHD_W32_MUTEX_ +# if _WIN32_WINNT < 0x0600 +/* Before Vista */ +/** + * Initialise a new mutex for short locks. + * + * Initialised mutex is optimised for locks held only for very short period of + * time. It should be used when only a single or just a few variables are + * modified under the lock. + * + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_init_short(pmutex) \ + (InitializeCriticalSectionAndSpinCount ((pmutex), 128)) +# else +/* The function always succeed starting from Vista */ +/** + * Initialise a new mutex for short locks. + * + * Initialised mutex is optimised for locks held only for very short period of + * time. It should be used when only a single or just a few variables are + * modified under the lock. + * + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_init_short(pmutex) \ + ((void) InitializeCriticalSectionAndSpinCount ((pmutex), 128), ! 0) +# endif +#endif + +#ifndef mhd_mutex_init_short +# define mhd_mutex_init_short(pmutex) mhd_mutex_init ((pmutex)) +#endif + +#if defined(MHD_PTHREAD_MUTEX_) +# if defined(PTHREAD_MUTEX_INITIALIZER) +/** + * Define static mutex and statically initialise it. + */ +# define MHD_MUTEX_STATIC_DEFN_INIT_(m) \ + static mhd_mutex m = PTHREAD_MUTEX_INITIALIZER +# endif /* PTHREAD_MUTEX_INITIALIZER */ +#endif + +#if defined(MHD_PTHREAD_MUTEX_) +/** + * Destroy previously initialised mutex. + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_destroy(pmutex) (! (pthread_mutex_destroy ((pmutex)))) +#elif defined(MHD_W32_MUTEX_) +/** + * Destroy previously initialised mutex. + * @param pmutex the pointer to the mutex + * @return Always nonzero + */ +# define mhd_mutex_destroy(pmutex) (DeleteCriticalSection ((pmutex)), ! 0) +#endif + + +#if defined(MHD_PTHREAD_MUTEX_) +/** + * Acquire a lock on previously initialised mutex. + * If the mutex was already locked by other thread, function blocks until + * the mutex becomes available. + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_lock(pmutex) (! (pthread_mutex_lock ((pmutex)))) +#elif defined(MHD_W32_MUTEX_) +/** + * Acquire a lock on previously initialised mutex. + * If the mutex was already locked by other thread, function blocks until + * the mutex becomes available. + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_lock(pmutex) (EnterCriticalSection ((pmutex)), ! 0) +#endif + +#if defined(MHD_PTHREAD_MUTEX_) +/** + * Unlock previously locked mutex. + * @param pmutex the pointer to the mutex + * @return nonzero on success, zero otherwise + */ +# define mhd_mutex_unlock(pmutex) (! (pthread_mutex_unlock ((pmutex)))) +#elif defined(MHD_W32_MUTEX_) +/** + * Unlock previously initialised and locked mutex. + * @param pmutex pointer to mutex + * @return Always nonzero + */ +# define mhd_mutex_unlock(pmutex) (LeaveCriticalSection ((pmutex)), ! 0) +#endif + +/** + * Destroy previously initialised mutex and abort execution if error is + * detected. + * @param pmutex the pointer to the mutex + */ +#define mhd_mutex_destroy_chk(pmutex) do { \ + if (! mhd_mutex_destroy (pmutex)) \ + MHD_PANIC ("Failed to destroy mutex.\n"); \ +} while (0) + +/** + * Acquire a lock on previously initialised mutex. + * If the mutex was already locked by other thread, function blocks until + * the mutex becomes available. + * If error is detected, execution is aborted. + * @param pmutex the pointer to the mutex + */ +#define mhd_mutex_lock_chk(pmutex) do { \ + if (! mhd_mutex_lock (pmutex)) \ + MHD_PANIC ("Failed to lock mutex.\n"); \ +} while (0) + +/** + * Unlock previously locked mutex. + * If error is detected, execution is aborted. + * @param pmutex the pointer to the mutex + */ +#define mhd_mutex_unlock_chk(pmutex) do { \ + if (! mhd_mutex_unlock (pmutex)) \ + MHD_PANIC ("Failed to unlock mutex.\n"); \ +} while (0) + +#else /* ! MHD_USE_THREADS */ + +#define mhd_mutex_init(ignore) (! 0) +#define mhd_mutex_destroy(ignore) (! 0) +#define mhd_mutex_destroy_chk(ignore) (void) 0 +#define mhd_mutex_lock(ignore) (! 0) +#define mhd_mutex_lock_chk(ignore) (void) 0 +#define mhd_mutex_unlock(ignore) (! 0) +#define mhd_mutex_unlock_chk(ignore) (void) 0 + +#endif /* ! MHD_USE_THREADS */ + +#endif /* ! MHD_LOCKS_H */ diff --git a/src/mhd2/mhd_mempool.c b/src/mhd2/mhd_mempool.c @@ -0,0 +1,809 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2007--2024 Daniel Pittman and Christian Grothoff + Copyright (C) 2014--2024 Evgeny Grin (Karlson2k) + + This library 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. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file src/mhd2/mhd_mempool.h + * @brief memory pool + * @author Christian Grothoff + * @author Karlson2k (Evgeny Grin) + * TODO: + * + Update code style + * + Detect mmap() in configure (it is purely optional!) + */ +#include "mhd_mempool.h" +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif /* HAVE_STDLIB_H */ +#include <string.h> +#include "mhd_assert.h" +#ifdef HAVE_SYS_MMAN_H +# include <sys/mman.h> +#endif +#ifdef _WIN32 +# include <windows.h> +#endif +#ifdef HAVE_SYSCONF +# include <unistd.h> +# if defined(_SC_PAGE_SIZE) +# define MHD_SC_PAGESIZE _SC_PAGE_SIZE +# elif defined(_SC_PAGESIZE) +# define MHD_SC_PAGESIZE _SC_PAGESIZE +# endif /* _SC_PAGESIZE */ +#endif /* HAVE_SYSCONF */ + +#if defined(MHD_USE_PAGESIZE_MACRO) || defined(MHD_USE_PAGE_SIZE_MACRO) +# ifndef HAVE_SYSCONF /* Avoid duplicate include */ +# include <unistd.h> +# endif /* HAVE_SYSCONF */ +# ifdef HAVE_LIMITS_H +# include <limits.h> +# endif +# ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +# endif /* HAVE_SYS_PARAM_H */ +#endif /* MHD_USE_PAGESIZE_MACRO || MHD_USE_PAGE_SIZE_MACRO */ + +#include "mhd_limits.h" + +/** + * Fallback value of page size + */ +#define _MHD_FALLBACK_PAGE_SIZE (4096) + +#if defined(MHD_USE_PAGESIZE_MACRO) +#define MHD_DEF_PAGE_SIZE_ PAGESIZE +#elif defined(MHD_USE_PAGE_SIZE_MACRO) +#define MHD_DEF_PAGE_SIZE_ PAGE_SIZE +#else /* ! PAGESIZE */ +#define MHD_DEF_PAGE_SIZE_ _MHD_FALLBACK_PAGE_SIZE +#endif /* ! PAGESIZE */ + + +#ifdef MHD_ASAN_POISON_ACTIVE +#include <sanitizer/asan_interface.h> +#endif /* MHD_ASAN_POISON_ACTIVE */ + +/* define MAP_ANONYMOUS for Mac OS X */ +#if defined(MAP_ANON) && ! defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif +#if defined(_WIN32) +#define MAP_FAILED NULL +#elif ! defined(MAP_FAILED) +#define MAP_FAILED ((void*) -1) +#endif + +/** + * Align to 2x word size (as GNU libc does). + */ +#define ALIGN_SIZE (2 * sizeof(void*)) + +/** + * Round up 'n' to a multiple of ALIGN_SIZE. + */ +#define ROUND_TO_ALIGN(n) (((n) + (ALIGN_SIZE - 1)) \ + / (ALIGN_SIZE) *(ALIGN_SIZE)) + + +#ifndef MHD_ASAN_POISON_ACTIVE +#define _MHD_NOSANITIZE_PTRS /**/ +#define _MHD_RED_ZONE_SIZE (0) +#define ROUND_TO_ALIGN_PLUS_RED_ZONE(n) ROUND_TO_ALIGN (n) +#define _MHD_POISON_MEMORY(pointer, size) (void) 0 +#define _MHD_UNPOISON_MEMORY(pointer, size) (void) 0 +/** + * Boolean 'true' if the first pointer is less or equal the second pointer + */ +#define mp_ptr_le_(p1,p2) \ + (((const uint8_t*) (p1)) <= ((const uint8_t*) (p2))) +/** + * The difference in bytes between positions of the first and + * the second pointers + */ +#define mp_ptr_diff_(p1,p2) \ + ((size_t) (((const uint8_t*) (p1)) - ((const uint8_t*) (p2)))) +#else /* MHD_ASAN_POISON_ACTIVE */ +#define _MHD_RED_ZONE_SIZE (ALIGN_SIZE) +#define ROUND_TO_ALIGN_PLUS_RED_ZONE(n) \ + (ROUND_TO_ALIGN (n) + _MHD_RED_ZONE_SIZE) +#define _MHD_POISON_MEMORY(pointer, size) \ + ASAN_POISON_MEMORY_REGION ((pointer), (size)) +#define _MHD_UNPOISON_MEMORY(pointer, size) \ + ASAN_UNPOISON_MEMORY_REGION ((pointer), (size)) +#if defined(FUNC_PTRCOMPARE_CAST_WORKAROUND_WORKS) +/** + * Boolean 'true' if the first pointer is less or equal the second pointer + */ +#define mp_ptr_le_(p1,p2) \ + (((uintptr_t) ((const void*) (p1))) <= \ + ((uintptr_t) ((const void*) (p2)))) +/** + * The difference in bytes between positions of the first and + * the second pointers + */ +#define mp_ptr_diff_(p1,p2) \ + ((size_t) (((uintptr_t) ((const uint8_t*) (p1))) - \ + ((uintptr_t) ((const uint8_t*) (p2))))) +#elif defined(FUNC_ATTR_PTRCOMPARE_WORKS) && \ + defined(FUNC_ATTR_PTRSUBTRACT_WORKS) +#ifdef _DEBUG +/** + * Boolean 'true' if the first pointer is less or equal the second pointer + */ +__attribute__((no_sanitize ("pointer-compare"))) static bool +mp_ptr_le_ (const void *p1, const void *p2) +{ + return (((const uint8_t *) p1) <= ((const uint8_t *) p2)); +} + + +#endif /* _DEBUG */ + + +/** + * The difference in bytes between positions of the first and + * the second pointers + */ +__attribute__((no_sanitize ("pointer-subtract"))) static size_t +mp_ptr_diff_ (const void *p1, const void *p2) +{ + return (size_t) (((const uint8_t *) p1) - ((const uint8_t *) p2)); +} + + +#elif defined(FUNC_ATTR_NOSANITIZE_WORKS) +#ifdef _DEBUG +/** + * Boolean 'true' if the first pointer is less or equal the second pointer + */ +__attribute__((no_sanitize ("address"))) static bool +mp_ptr_le_ (const void *p1, const void *p2) +{ + return (((const uint8_t *) p1) <= ((const uint8_t *) p2)); +} + + +#endif /* _DEBUG */ + +/** + * The difference in bytes between positions of the first and + * the second pointers + */ +__attribute__((no_sanitize ("address"))) static size_t +mp_ptr_diff_ (const void *p1, const void *p2) +{ + return (size_t) (((const uint8_t *) p1) - ((const uint8_t *) p2)); +} + + +#else /* ! FUNC_ATTR_NOSANITIZE_WORKS */ +#error User-poisoning cannot be used +#endif /* ! FUNC_ATTR_NOSANITIZE_WORKS */ +#endif /* MHD_ASAN_POISON_ACTIVE */ + +/** + * Size of memory page + */ +static size_t MHD_sys_page_size_ = (size_t) +#if defined(MHD_USE_PAGESIZE_MACRO_STATIC) + PAGESIZE; +#elif defined(MHD_USE_PAGE_SIZE_MACRO_STATIC) + PAGE_SIZE; +#else /* ! MHD_USE_PAGE_SIZE_MACRO_STATIC */ + _MHD_FALLBACK_PAGE_SIZE; /* Default fallback value */ +#endif /* ! MHD_USE_PAGE_SIZE_MACRO_STATIC */ + +/** + * Initialise values for memory pools + */ +void +mhd_init_mem_pools (void) +{ +#ifdef MHD_SC_PAGESIZE + long result; + result = sysconf (MHD_SC_PAGESIZE); + if (-1 != result) + MHD_sys_page_size_ = (size_t) result; + else + MHD_sys_page_size_ = (size_t) MHD_DEF_PAGE_SIZE_; +#elif defined(_WIN32) + SYSTEM_INFO si; + GetSystemInfo (&si); + MHD_sys_page_size_ = (size_t) si.dwPageSize; +#else + MHD_sys_page_size_ = (size_t) MHD_DEF_PAGE_SIZE_; +#endif /* _WIN32 */ + mhd_assert (0 == (MHD_sys_page_size_ % ALIGN_SIZE)); +} + + +/** + * Handle for a memory pool. Pools are not reentrant and must not be + * used by multiple threads. + */ +struct mhd_MemoryPool +{ + + /** + * Pointer to the pool's memory + */ + uint8_t *memory; + + /** + * Size of the pool. + */ + size_t size; + + /** + * Offset of the first unallocated byte. + */ + size_t pos; + + /** + * Offset of the byte after the last unallocated byte. + */ + size_t end; + + /** + * 'false' if pool was malloc'ed, 'true' if mmapped (VirtualAlloc'ed for W32). + */ + bool is_mmap; + + // TODO: implement *optional* zeroing on reset on reallocs +}; + + +/** + * Create a memory pool. + * + * @param max maximum size of the pool + * @return NULL on error + */ +MHD_INTERNAL struct mhd_MemoryPool * +mdh_pool_create (size_t max) +{ + struct mhd_MemoryPool *pool; + size_t alloc_size; + + mhd_assert (max > 0); + alloc_size = 0; + pool = malloc (sizeof (struct mhd_MemoryPool)); + if (NULL == pool) + return NULL; +#if defined(MAP_ANONYMOUS) || defined(_WIN32) + if ( (max <= 32 * 1024) || + (max < MHD_sys_page_size_ * 4 / 3) ) + { + pool->memory = MAP_FAILED; + } + else + { + /* Round up allocation to page granularity. */ + alloc_size = max + MHD_sys_page_size_ - 1; + alloc_size -= alloc_size % MHD_sys_page_size_; +#if defined(MAP_ANONYMOUS) && ! defined(_WIN32) + pool->memory = mmap (NULL, + alloc_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); +#elif defined(_WIN32) + pool->memory = VirtualAlloc (NULL, + alloc_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); +#endif /* _WIN32 */ + } +#else /* ! _WIN32 && ! MAP_ANONYMOUS */ + pool->memory = MAP_FAILED; +#endif /* ! _WIN32 && ! MAP_ANONYMOUS */ + if (MAP_FAILED == pool->memory) + { + alloc_size = ROUND_TO_ALIGN (max); + pool->memory = malloc (alloc_size); + if (NULL == pool->memory) + { + free (pool); + return NULL; + } + pool->is_mmap = false; + } +#if defined(MAP_ANONYMOUS) || defined(_WIN32) + else + { + pool->is_mmap = true; + } +#endif /* _WIN32 || MAP_ANONYMOUS */ + mhd_assert (0 == (((uintptr_t) pool->memory) % ALIGN_SIZE)); + pool->pos = 0; + pool->end = alloc_size; + pool->size = alloc_size; + mhd_assert (0 < alloc_size); + _MHD_POISON_MEMORY (pool->memory, pool->size); + return pool; +} + + +/** + * Destroy a memory pool. + * + * @param pool memory pool to destroy + */ +MHD_INTERNAL void +mhd_pool_destroy (struct mhd_MemoryPool *restrict pool) +{ + if (NULL == pool) + return; + + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); + _MHD_UNPOISON_MEMORY (pool->memory, pool->size); + if (! pool->is_mmap) + free (pool->memory); + else +#if defined(MAP_ANONYMOUS) && ! defined(_WIN32) + munmap (pool->memory, + pool->size); +#elif defined(_WIN32) + VirtualFree (pool->memory, + 0, + MEM_RELEASE); +#else + abort (); +#endif + free (pool); +} + + +/** + * Check how much memory is left in the @a pool + * + * @param pool pool to check + * @return number of bytes still available in @a pool + */ +MHD_INTERNAL size_t +mhd_pool_get_free (struct mhd_MemoryPool *restrict pool) +{ + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); +#ifdef MHD_ASAN_POISON_ACTIVE + if ((pool->end - pool->pos) <= _MHD_RED_ZONE_SIZE) + return 0; +#endif /* MHD_ASAN_POISON_ACTIVE */ + return (pool->end - pool->pos) - _MHD_RED_ZONE_SIZE; +} + + +/** + * Allocate size bytes from the pool. + * + * @param pool memory pool to use for the operation + * @param size number of bytes to allocate + * @param from_end allocate from end of pool (set to 'true'); + * use this for small, persistent allocations that + * will never be reallocated + * @return NULL if the pool cannot support size more + * bytes + */ +MHD_INTERNAL void * +mhd_pool_allocate (struct mhd_MemoryPool *restrict pool, + size_t size, + bool from_end) +{ + void *ret; + size_t asize; + + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); + asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (size); + if ( (0 == asize) && (0 != size) ) + return NULL; /* size too close to SIZE_MAX */ + if (asize > pool->end - pool->pos) + return NULL; + if (from_end) + { + ret = &pool->memory[pool->end - asize]; + pool->end -= asize; + } + else + { + ret = &pool->memory[pool->pos]; + pool->pos += asize; + } + _MHD_UNPOISON_MEMORY (ret, size); + return ret; +} + + +/** + * Checks whether allocated block is re-sizable in-place. + * If block is not re-sizable in-place, it still could be shrunk, but freed + * memory will not be re-used until reset of the pool. + * @param pool the memory pool to use + * @param block the pointer to the allocated block to check + * @param block_size the size of the allocated @a block + * @return true if block can be resized in-place in the optimal way, + * false otherwise + */ +MHD_INTERNAL bool +mhd_pool_is_resizable_inplace (struct mhd_MemoryPool *restrict pool, + void *restrict block, + size_t block_size) +{ + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (block != NULL || block_size == 0); + mhd_assert (pool->size >= block_size); + if (NULL != block) + { + const size_t block_offset = mp_ptr_diff_ (block, pool->memory); + mhd_assert (mp_ptr_le_ (pool->memory, block)); + mhd_assert (pool->size >= block_offset); + mhd_assert (pool->size >= block_offset + block_size); + return (pool->pos == + ROUND_TO_ALIGN_PLUS_RED_ZONE (block_offset + block_size)); + } + return false; /* Unallocated blocks cannot be resized in-place */ +} + + +/** + * Try to allocate @a size bytes memory area from the @a pool. + * + * If allocation fails, @a required_bytes is updated with size required to be + * freed in the @a pool from rellocatable area to allocate requested number + * of bytes. + * Allocated memory area is always not rellocatable ("from end"). + * + * @param pool memory pool to use for the operation + * @param size the size of memory in bytes to allocate + * @param[out] required_bytes the pointer to variable to be updated with + * the size of the required additional free + * memory area, set to 0 if function succeeds. + * Cannot be NULL. + * @return the pointer to allocated memory area if succeed, + * NULL if the pool doesn't have enough space, required_bytes is updated + * with amount of space needed to be freed in rellocatable area or + * set to SIZE_MAX if requested size is too large for the pool. + */ +MHD_INTERNAL void * +mhd_pool_try_alloc (struct mhd_MemoryPool *restrict pool, + size_t size, + size_t *restrict required_bytes) +{ + void *ret; + size_t asize; + + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); + asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (size); + if ( (0 == asize) && (0 != size) ) + { /* size is too close to SIZE_MAX, very unlikely */ + *required_bytes = SIZE_MAX; + return NULL; + } + if (asize > pool->end - pool->pos) + { + mhd_assert ((pool->end - pool->pos) == \ + ROUND_TO_ALIGN (pool->end - pool->pos)); + if (asize <= pool->end) + *required_bytes = asize - (pool->end - pool->pos); + else + *required_bytes = SIZE_MAX; + return NULL; + } + *required_bytes = 0; + ret = &pool->memory[pool->end - asize]; + pool->end -= asize; + _MHD_UNPOISON_MEMORY (ret, size); + return ret; +} + + +/** + * Reallocate a block of memory obtained from the pool. + * This is particularly efficient when growing or + * shrinking the block that was last (re)allocated. + * If the given block is not the most recently + * (re)allocated block, the memory of the previous + * allocation may be not released until the pool is + * destroyed or reset. + * + * @param pool memory pool to use for the operation + * @param old the existing block + * @param old_size the size of the existing block + * @param new_size the new size of the block + * @return new address of the block, or + * NULL if the pool cannot support @a new_size + * bytes (old continues to be valid for @a old_size) + */ +MHD_INTERNAL void * +mhd_pool_reallocate (struct mhd_MemoryPool *pool, + void *restrict old, + size_t old_size, + size_t new_size) +{ + size_t asize; + uint8_t *new_blc; + + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (old != NULL || old_size == 0); + mhd_assert (pool->size >= old_size); + mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); +#if defined(MHD_ASAN_POISON_ACTIVE) && defined(HAVE___ASAN_REGION_IS_POISONED) + mhd_assert (NULL == __asan_region_is_poisoned (old, old_size)); +#endif /* MHD_ASAN_POISON_ACTIVE && HAVE___ASAN_REGION_IS_POISONED */ + + if (NULL != old) + { /* Have previously allocated data */ + const size_t old_offset = mp_ptr_diff_ (old, pool->memory); + const bool shrinking = (old_size > new_size); + + mhd_assert (mp_ptr_le_ (pool->memory, old)); + /* (pool->memory + pool->size >= (uint8_t*) old + old_size) */ + mhd_assert ((pool->size - _MHD_RED_ZONE_SIZE) >= (old_offset + old_size)); + /* Blocks "from the end" must not be reallocated */ + /* (old_size == 0 || pool->memory + pool->pos > (uint8_t*) old) */ + mhd_assert ((old_size == 0) || \ + (pool->pos > old_offset)); + mhd_assert ((old_size == 0) || \ + ((pool->end - _MHD_RED_ZONE_SIZE) >= (old_offset + old_size))); + /* Try resizing in-place */ + if (shrinking) + { /* Shrinking in-place, zero-out freed part */ + memset ((uint8_t *) old + new_size, 0, old_size - new_size); + _MHD_POISON_MEMORY ((uint8_t *) old + new_size, old_size - new_size); + } + if (pool->pos == + ROUND_TO_ALIGN_PLUS_RED_ZONE (old_offset + old_size)) + { /* "old" block is the last allocated block */ + const size_t new_apos = + ROUND_TO_ALIGN_PLUS_RED_ZONE (old_offset + new_size); + if (! shrinking) + { /* Grow in-place, check for enough space. */ + if ( (new_apos > pool->end) || + (new_apos < pool->pos) ) /* Value wrap */ + return NULL; /* No space */ + } + /* Resized in-place */ + pool->pos = new_apos; + _MHD_UNPOISON_MEMORY (old, new_size); + return old; + } + if (shrinking) + return old; /* Resized in-place, freed part remains allocated */ + } + /* Need to allocate new block */ + asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (new_size); + if ( ( (0 == asize) && + (0 != new_size) ) || /* Value wrap, too large new_size. */ + (asize > pool->end - pool->pos) ) /* Not enough space */ + return NULL; + + new_blc = pool->memory + pool->pos; + pool->pos += asize; + + _MHD_UNPOISON_MEMORY (new_blc, new_size); + if (0 != old_size) + { + /* Move data to new block, old block remains allocated */ + memcpy (new_blc, old, old_size); + /* Zero-out old block */ + memset (old, 0, old_size); + _MHD_POISON_MEMORY (old, old_size); + } + return new_blc; +} + + +/** + * Deallocate a block of memory obtained from the pool. + * + * If the given block is not the most recently + * (re)allocated block, the memory of the this block + * allocation may be not released until the pool is + * destroyed or reset. + * + * @param pool memory pool to use for the operation + * @param block the allocated block, the NULL is tolerated + * @param block_size the size of the allocated block + */ +MHD_INTERNAL void +mhd_pool_deallocate (struct mhd_MemoryPool *restrict pool, + void *restrict block, + size_t block_size) +{ + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (block != NULL || block_size == 0); + mhd_assert (pool->size >= block_size); + mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); + + if (NULL != block) + { /* Have previously allocated data */ + const size_t block_offset = mp_ptr_diff_ (block, pool->memory); + mhd_assert (mp_ptr_le_ (pool->memory, block)); + mhd_assert (block_offset <= pool->size); + mhd_assert ((block_offset != pool->pos) || (block_size == 0)); + /* Zero-out deallocated region */ + if (0 != block_size) + { + memset (block, 0, block_size); + _MHD_POISON_MEMORY (block, block_size); + } +#if ! defined(MHD_FAVOR_SMALL_CODE) && ! defined(MHD_ASAN_POISON_ACTIVE) + else + return; /* Zero size, no need to do anything */ +#endif /* ! MHD_FAVOR_SMALL_CODE && ! MHD_ASAN_POISON_ACTIVE */ + if (block_offset <= pool->pos) + { + /* "Normal" block, not allocated "from the end". */ + const size_t alg_end = + ROUND_TO_ALIGN_PLUS_RED_ZONE (block_offset + block_size); + mhd_assert (alg_end <= pool->pos); + if (alg_end == pool->pos) + { + /* The last allocated block, return deallocated block to the pool */ + size_t alg_start = ROUND_TO_ALIGN (block_offset); + mhd_assert (alg_start >= block_offset); +#if defined(MHD_ASAN_POISON_ACTIVE) + if (alg_start != block_offset) + { + _MHD_POISON_MEMORY (pool->memory + block_offset, \ + alg_start - block_offset); + } + else if (0 != alg_start) + { + bool need_red_zone_before; + mhd_assert (_MHD_RED_ZONE_SIZE <= alg_start); +#if defined(HAVE___ASAN_REGION_IS_POISONED) + need_red_zone_before = + (NULL == __asan_region_is_poisoned (pool->memory + + alg_start + - _MHD_RED_ZONE_SIZE, + _MHD_RED_ZONE_SIZE)); +#elif defined(HAVE___ASAN_ADDRESS_IS_POISONED) + need_red_zone_before = + (0 == __asan_address_is_poisoned (pool->memory + alg_start - 1)); +#else /* ! HAVE___ASAN_ADDRESS_IS_POISONED */ + need_red_zone_before = true; /* Unknown, assume new red zone needed */ +#endif /* ! HAVE___ASAN_ADDRESS_IS_POISONED */ + if (need_red_zone_before) + { + _MHD_POISON_MEMORY (pool->memory + alg_start, _MHD_RED_ZONE_SIZE); + alg_start += _MHD_RED_ZONE_SIZE; + } + } +#endif /* MHD_ASAN_POISON_ACTIVE */ + mhd_assert (alg_start <= pool->pos); + mhd_assert (alg_start == ROUND_TO_ALIGN (alg_start)); + pool->pos = alg_start; + } + } + else + { + /* Allocated "from the end" block. */ + /* The size and the pointers of such block should not be manipulated by + MHD code (block split is disallowed). */ + mhd_assert (block_offset >= pool->end); + mhd_assert (ROUND_TO_ALIGN (block_offset) == block_offset); + if (block_offset == pool->end) + { + /* The last allocated block, return deallocated block to the pool */ + const size_t alg_end = + ROUND_TO_ALIGN_PLUS_RED_ZONE (block_offset + block_size); + pool->end = alg_end; + } + } + } +} + + +/** + * Clear all entries from the memory pool except + * for @a keep of the given @a copy_bytes. The pointer + * returned should be a buffer of @a new_size where + * the first @a copy_bytes are from @a keep. + * + * @param pool memory pool to use for the operation + * @param keep pointer to the entry to keep (maybe NULL) + * @param copy_bytes how many bytes need to be kept at this address + * @param new_size how many bytes should the allocation we return have? + * (should be larger or equal to @a copy_bytes) + * @return addr new address of @a keep (if it had to change) + */ +MHD_INTERNAL void * +mhd_pool_reset (struct mhd_MemoryPool *restrict pool, + void *restrict keep, + size_t copy_bytes, + size_t new_size) +{ + mhd_assert (pool->end >= pool->pos); + mhd_assert (pool->size >= pool->end - pool->pos); + mhd_assert (copy_bytes <= new_size); + mhd_assert (copy_bytes <= pool->size); + mhd_assert (keep != NULL || copy_bytes == 0); + mhd_assert (keep == NULL || mp_ptr_le_ (pool->memory, keep)); + /* (keep == NULL || pool->memory + pool->size >= (uint8_t*) keep + copy_bytes) */ + mhd_assert ((keep == NULL) || \ + (pool->size >= mp_ptr_diff_ (keep, pool->memory) + copy_bytes)); +#if defined(MHD_ASAN_POISON_ACTIVE) && defined(HAVE___ASAN_REGION_IS_POISONED) + mhd_assert (NULL == __asan_region_is_poisoned (keep, copy_bytes)); +#endif /* MHD_ASAN_POISON_ACTIVE && HAVE___ASAN_REGION_IS_POISONED */ + _MHD_UNPOISON_MEMORY (pool->memory, new_size); + if ( (NULL != keep) && + (keep != pool->memory) ) + { + if (0 != copy_bytes) + memmove (pool->memory, + keep, + copy_bytes); + } + /* technically not needed, but safer to zero out */ + if (pool->size > copy_bytes) + { + size_t to_zero; /** Size of area to zero-out */ + + to_zero = pool->size - copy_bytes; + _MHD_UNPOISON_MEMORY (pool->memory + copy_bytes, to_zero); +#ifdef _WIN32 + if (pool->is_mmap) + { + size_t to_recommit; /** Size of decommitted and re-committed area. */ + uint8_t *recommit_addr; + /* Round down to page size */ + to_recommit = to_zero - to_zero % MHD_sys_page_size_; + recommit_addr = pool->memory + pool->size - to_recommit; + + /* De-committing and re-committing again clear memory and make + * pages free / available for other needs until accessed. */ + if (VirtualFree (recommit_addr, + to_recommit, + MEM_DECOMMIT)) + { + to_zero -= to_recommit; + + if (recommit_addr != VirtualAlloc (recommit_addr, + to_recommit, + MEM_COMMIT, + PAGE_READWRITE)) + abort (); /* Serious error, must never happen */ + } + } +#endif /* _WIN32 */ + memset (&pool->memory[copy_bytes], + 0, + to_zero); + } + pool->pos = ROUND_TO_ALIGN_PLUS_RED_ZONE (new_size); + pool->end = pool->size; + _MHD_POISON_MEMORY (((uint8_t *) pool->memory) + new_size, \ + pool->size - new_size); + return pool->memory; +} + + +/* end of memorypool.c */ diff --git a/src/mhd2/mhd_mempool.h b/src/mhd2/mhd_mempool.h @@ -0,0 +1,197 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2007--2024 Daniel Pittman and Christian Grothoff + Copyright (C) 2016--2024 Evgeny Grin (Karlson2k) + + This library 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. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file src/mhd2/mhd_mempool.h + * @brief memory pool; mostly used for efficient (de)allocation + * for each connection and bounding memory use for each + * request + * @author Christian Grothoff + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_MEMPOOL_H +#define MHD_MEMPOOL_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" +#include "sys_bool_type.h" + +/** + * Opaque handle for a memory pool. + * Pools are not reentrant and must not be used + * by multiple threads. + */ +struct mhd_MemoryPool; + +/** + * Initialize values for memory pools + */ +void +mhd_init_mem_pools (void); + + +/** + * Create a memory pool. + * + * @param max maximum size of the pool + * @return NULL on error + */ +MHD_INTERNAL struct mhd_MemoryPool * +mdh_pool_create (size_t max); + + +/** + * Destroy a memory pool. + * + * @param pool memory pool to destroy + */ +MHD_INTERNAL void +mhd_pool_destroy (struct mhd_MemoryPool *restrict pool); + + +/** + * Allocate size bytes from the pool. + * + * @param pool memory pool to use for the operation + * @param size number of bytes to allocate + * @param from_end allocate from end of pool (set to 'true'); + * use this for small, persistent allocations that + * will never be reallocated + * @return NULL if the pool cannot support size more + * bytes + */ +MHD_INTERNAL void * +mhd_pool_allocate (struct mhd_MemoryPool *restrict pool, + size_t size, + bool from_end); + +/** + * Checks whether allocated block is re-sizable in-place. + * If block is not re-sizable in-place, it still could be shrunk, but freed + * memory will not be re-used until reset of the pool. + * @param pool the memory pool to use + * @param block the pointer to the allocated block to check + * @param block_size the size of the allocated @a block + * @return true if block can be resized in-place in the optimal way, + * false otherwise + */ +MHD_INTERNAL bool +mhd_pool_is_resizable_inplace (struct mhd_MemoryPool *restrict pool, + void *restrict block, + size_t block_size); + +/** + * Try to allocate @a size bytes memory area from the @a pool. + * + * If allocation fails, @a required_bytes is updated with size required to be + * freed in the @a pool from rellocatable area to allocate requested number + * of bytes. + * Allocated memory area is always not rellocatable ("from end"). + * + * @param pool memory pool to use for the operation + * @param size the size of memory in bytes to allocate + * @param[out] required_bytes the pointer to variable to be updated with + * the size of the required additional free + * memory area, set to 0 if function succeeds. + * Cannot be NULL. + * @return the pointer to allocated memory area if succeed, + * NULL if the pool doesn't have enough space, required_bytes is updated + * with amount of space needed to be freed in rellocatable area or + * set to SIZE_MAX if requested size is too large for the pool. + */ +MHD_INTERNAL void * +mhd_pool_try_alloc (struct mhd_MemoryPool *restrict pool, + size_t size, + size_t *restrict required_bytes); + + +/** + * Reallocate a block of memory obtained from the pool. + * This is particularly efficient when growing or + * shrinking the block that was last (re)allocated. + * If the given block is not the most recently + * (re)allocated block, the memory of the previous + * allocation may be not released until the pool is + * destroyed or reset. + * + * @param pool memory pool to use for the operation + * @param old the existing block + * @param old_size the size of the existing block + * @param new_size the new size of the block + * @return new address of the block, or + * NULL if the pool cannot support @a new_size + * bytes (old continues to be valid for @a old_size) + */ +MHD_INTERNAL void * +mhd_pool_reallocate (struct mhd_MemoryPool *restrict pool, + void *restrict old, + size_t old_size, + size_t new_size); + + +/** + * Check how much memory is left in the @a pool + * + * @param pool pool to check + * @return number of bytes still available in @a pool + */ +MHD_INTERNAL size_t +mhd_pool_get_free (struct mhd_MemoryPool *restrict pool); + + +/** + * Deallocate a block of memory obtained from the pool. + * + * If the given block is not the most recently + * (re)allocated block, the memory of the this block + * allocation may be not released until the pool is + * destroyed or reset. + * + * @param pool memory pool to use for the operation + * @param block the allocated block, the NULL is tolerated + * @param block_size the size of the allocated block + */ +MHD_INTERNAL void +mhd_pool_deallocate (struct mhd_MemoryPool *restrict pool, + void *restrict block, + size_t block_size); + + +/** + * Clear all entries from the memory pool except + * for @a keep of the given @a copy_bytes. The pointer + * returned should be a buffer of @a new_size where + * the first @a copy_bytes are from @a keep. + * + * @param pool memory pool to use for the operation + * @param keep pointer to the entry to keep (maybe NULL) + * @param copy_bytes how many bytes need to be kept at this address + * @param new_size how many bytes should the allocation we return have? + * (should be larger or equal to @a copy_bytes) + * @return addr new address of @a keep (if it had to change) + */ +MHD_INTERNAL void * +mhd_pool_reset (struct mhd_MemoryPool *restrict pool, + void *restrict keep, + size_t copy_bytes, + size_t new_size); + +#endif /* ! MHD_MEMPOOL_H */ diff --git a/src/mhd2/mhd_mono_clock.c b/src/mhd2/mhd_mono_clock.c @@ -0,0 +1,450 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2015-2022 Karlson2k (Evgeny Grin) + + This library 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. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file src/mhd2/mhd_mono_clock.h + * @brief internal monotonic clock functions implementations + * @author Karlson2k (Evgeny Grin) + * + * TODO: update code style + */ + +#include "mhd_mono_clock.h" + +#if defined(_WIN32) && ! defined(__CYGWIN__) +/* Prefer native clock source over wrappers */ +# ifdef HAVE_CLOCK_GETTIME +# undef HAVE_CLOCK_GETTIME +# endif /* HAVE_CLOCK_GETTIME */ +# ifdef HAVE_GETTIMEOFDAY +# undef HAVE_GETTIMEOFDAY +# endif /* HAVE_GETTIMEOFDAY */ +#endif /* _WIN32 && ! __CYGWIN__ */ + +#ifdef HAVE_TIME_H +# include <time.h> +#endif /* HAVE_TIME_H */ +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif /* HAVE_SYS_TIME_H */ + +#ifdef HAVE_CLOCK_GET_TIME +# include <mach/mach.h> +/* for host_get_clock_service(), mach_host_self(), mach_task_self() */ +# include <mach/clock.h> +/* for clock_get_time() */ + +# define _MHD_INVALID_CLOCK_SERV ((clock_serv_t) -2) + +static clock_serv_t mono_clock_service = _MHD_INVALID_CLOCK_SERV; +#endif /* HAVE_CLOCK_GET_TIME */ + +#ifdef _WIN32 +# include <windows.h> +#endif /* _WIN32 */ + +#ifdef HAVE_CLOCK_GETTIME +# ifdef CLOCK_REALTIME +# define _MHD_UNWANTED_CLOCK CLOCK_REALTIME +# else /* !CLOCK_REALTIME */ +# define _MHD_UNWANTED_CLOCK ((clockid_t) -2) +# endif /* !CLOCK_REALTIME */ + +static clockid_t mono_clock_id = _MHD_UNWANTED_CLOCK; +#endif /* HAVE_CLOCK_GETTIME */ + +/* sync clocks; reduce chance of value wrap */ +#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_CLOCK_GET_TIME) || \ + defined(HAVE_GETHRTIME) +static time_t mono_clock_start; +#endif /* HAVE_CLOCK_GETTIME || HAVE_CLOCK_GET_TIME || HAVE_GETHRTIME */ + +#if defined(HAVE_TIMESPEC_GET) || defined(HAVE_GETTIMEOFDAY) +/* The start value shared for timespec_get() and gettimeofday () */ +static time_t gettime_start; +#endif /* HAVE_TIMESPEC_GET || HAVE_GETTIMEOFDAY */ +static time_t sys_clock_start; + +#ifdef HAVE_GETHRTIME +static hrtime_t hrtime_start; +#endif /* HAVE_GETHRTIME */ + +#ifdef _WIN32 +# if _WIN32_WINNT >= 0x0600 +static uint64_t tick_start; +# else /* _WIN32_WINNT < 0x0600 */ +static uint64_t perf_freq; +static uint64_t perf_start; +# endif /* _WIN32_WINNT < 0x0600 */ +#endif /* _WIN32 */ + + +/** + * Type of monotonic clock source + */ +enum _MHD_mono_clock_source +{ + /** + * No monotonic clock + */ + _MHD_CLOCK_NO_SOURCE = 0, + + /** + * clock_gettime() with specific clock + */ + _MHD_CLOCK_GETTIME, + + /** + * clock_get_time() with specific clock service + */ + _MHD_CLOCK_GET_TIME, + + /** + * gethrtime() / 1000000000 + */ + _MHD_CLOCK_GETHRTIME, + + /** + * GetTickCount64() / 1000 + */ + _MHD_CLOCK_GETTICKCOUNT64, + + /** + * QueryPerformanceCounter() / QueryPerformanceFrequency() + */ + _MHD_CLOCK_PERFCOUNTER +}; + + +/** + * Initialise milliseconds counters. + */ +void +MHD_monotonic_msec_counter_init (void) +{ +#ifdef HAVE_CLOCK_GET_TIME + mach_timespec_t cur_time; +#endif /* HAVE_CLOCK_GET_TIME */ + enum _MHD_mono_clock_source mono_clock_source = _MHD_CLOCK_NO_SOURCE; +#ifdef HAVE_CLOCK_GETTIME + struct timespec ts; + + mono_clock_id = _MHD_UNWANTED_CLOCK; +#endif /* HAVE_CLOCK_GETTIME */ +#ifdef HAVE_CLOCK_GET_TIME + mono_clock_service = _MHD_INVALID_CLOCK_SERV; +#endif /* HAVE_CLOCK_GET_TIME */ + + /* just a little syntactic trick to get the + various following ifdef's to work out nicely */ + if (0) + { + (void) 0; /* Mute possible compiler warning */ + } + else +#ifdef HAVE_CLOCK_GETTIME +#ifdef CLOCK_MONOTONIC_COARSE + /* Linux-specific fast value-getting clock */ + /* Can be affected by frequency adjustment and don't count time in suspend, */ + /* but preferred since it's fast */ + if (0 == clock_gettime (CLOCK_MONOTONIC_COARSE, + &ts)) + { + mono_clock_id = CLOCK_MONOTONIC_COARSE; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_MONOTONIC_COARSE */ +#ifdef CLOCK_MONOTONIC_FAST + /* FreeBSD/DragonFly fast value-getting clock */ + /* Can be affected by frequency adjustment, but preferred since it's fast */ + if (0 == clock_gettime (CLOCK_MONOTONIC_FAST, + &ts)) + { + mono_clock_id = CLOCK_MONOTONIC_FAST; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_MONOTONIC_COARSE */ +#ifdef CLOCK_MONOTONIC_RAW_APPROX + /* Darwin-specific clock */ + /* Not affected by frequency adjustment, returns clock value cached at + * context switch. Can be "milliseconds old", but it's fast. */ + if (0 == clock_gettime (CLOCK_MONOTONIC_RAW_APPROX, + &ts)) + { + mono_clock_id = CLOCK_MONOTONIC_RAW_APPROX; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_MONOTONIC_RAW */ +#ifdef CLOCK_MONOTONIC_RAW + /* Linux and Darwin clock */ + /* Not affected by frequency adjustment, + * on Linux don't count time in suspend */ + if (0 == clock_gettime (CLOCK_MONOTONIC_RAW, + &ts)) + { + mono_clock_id = CLOCK_MONOTONIC_RAW; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_MONOTONIC_RAW */ +#ifdef CLOCK_BOOTTIME + /* Count time in suspend on Linux so it's real monotonic, */ + /* but can be slower value-getting than other clocks */ + if (0 == clock_gettime (CLOCK_BOOTTIME, + &ts)) + { + mono_clock_id = CLOCK_BOOTTIME; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_BOOTTIME */ +#ifdef CLOCK_MONOTONIC + /* Monotonic clock */ + /* Widely supported, may be affected by frequency adjustment */ + /* On Linux it's not truly monotonic as it doesn't count time in suspend */ + if (0 == clock_gettime (CLOCK_MONOTONIC, + &ts)) + { + mono_clock_id = CLOCK_MONOTONIC; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_MONOTONIC */ +#ifdef CLOCK_UPTIME + /* non-Linux clock */ + /* Doesn't count time in suspend */ + if (0 == clock_gettime (CLOCK_UPTIME, + &ts)) + { + mono_clock_id = CLOCK_UPTIME; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_BOOTTIME */ +#endif /* HAVE_CLOCK_GETTIME */ +#ifdef HAVE_CLOCK_GET_TIME + /* Darwin-specific monotonic clock */ + /* Should be monotonic as clock_set_time function always unconditionally */ + /* failed on latest kernels */ + if ( (KERN_SUCCESS == host_get_clock_service (mach_host_self (), + SYSTEM_CLOCK, + &mono_clock_service)) && + (KERN_SUCCESS == clock_get_time (mono_clock_service, + &cur_time)) ) + { + mono_clock_start = cur_time.tv_sec; + mono_clock_source = _MHD_CLOCK_GET_TIME; + } + else +#endif /* HAVE_CLOCK_GET_TIME */ +#ifdef _WIN32 +#if _WIN32_WINNT >= 0x0600 + /* W32 Vista or later specific monotonic clock */ + /* Available since Vista, ~15ms accuracy */ + if (1) + { + tick_start = GetTickCount64 (); + mono_clock_source = _MHD_CLOCK_GETTICKCOUNT64; + } + else +#else /* _WIN32_WINNT < 0x0600 */ + /* W32 specific monotonic clock */ + /* Available on Windows 2000 and later */ + if (1) + { + LARGE_INTEGER freq; + LARGE_INTEGER perf_counter; + + QueryPerformanceFrequency (&freq); /* never fail on XP and later */ + QueryPerformanceCounter (&perf_counter); /* never fail on XP and later */ + perf_freq = (uint64_t) freq.QuadPart; + perf_start = (uint64_t) perf_counter.QuadPart; + mono_clock_source = _MHD_CLOCK_PERFCOUNTER; + } + else +#endif /* _WIN32_WINNT < 0x0600 */ +#endif /* _WIN32 */ +#ifdef HAVE_CLOCK_GETTIME +#ifdef CLOCK_HIGHRES + /* Solaris-specific monotonic high-resolution clock */ + /* Not preferred due to be potentially resource-hungry */ + if (0 == clock_gettime (CLOCK_HIGHRES, + &ts)) + { + mono_clock_id = CLOCK_HIGHRES; + mono_clock_start = ts.tv_sec; + mono_clock_source = _MHD_CLOCK_GETTIME; + } + else +#endif /* CLOCK_HIGHRES */ +#endif /* HAVE_CLOCK_GETTIME */ +#ifdef HAVE_GETHRTIME + /* HP-UX and Solaris monotonic clock */ + /* Not preferred due to be potentially resource-hungry */ + if (1) + { + hrtime_start = gethrtime (); + mono_clock_source = _MHD_CLOCK_GETHRTIME; + } + else +#endif /* HAVE_GETHRTIME */ + { + /* no suitable clock source was found */ + mono_clock_source = _MHD_CLOCK_NO_SOURCE; + } + +#ifdef HAVE_CLOCK_GET_TIME + if ( (_MHD_CLOCK_GET_TIME != mono_clock_source) && + (_MHD_INVALID_CLOCK_SERV != mono_clock_service) ) + { + /* clock service was initialised but clock_get_time failed */ + mach_port_deallocate (mach_task_self (), + mono_clock_service); + mono_clock_service = _MHD_INVALID_CLOCK_SERV; + } +#else + (void) mono_clock_source; /* avoid compiler warning */ +#endif /* HAVE_CLOCK_GET_TIME */ + +#ifdef HAVE_TIMESPEC_GET + if (1) + { + struct timespec tsg; + if (TIME_UTC == timespec_get (&tsg, TIME_UTC)) + gettime_start = tsg.tv_sec; + else + gettime_start = 0; + } +#elif defined(HAVE_GETTIMEOFDAY) + if (1) + { + struct timeval tv; + if (0 == gettimeofday (&tv, NULL)) + gettime_start = tv.tv_sec; + else + gettime_start = 0; + } +#endif /* HAVE_GETTIMEOFDAY */ + sys_clock_start = time (NULL); +} + + +/** + * Deinitialise milliseconds counters by freeing any allocated resources + */ +void +MHD_monotonic_msec_counter_finish (void) +{ +#ifdef HAVE_CLOCK_GET_TIME + if (_MHD_INVALID_CLOCK_SERV != mono_clock_service) + { + mach_port_deallocate (mach_task_self (), + mono_clock_service); + mono_clock_service = _MHD_INVALID_CLOCK_SERV; + } +#endif /* HAVE_CLOCK_GET_TIME */ +} + + +/** + * Monotonic milliseconds counter, useful for timeout calculation. + * Tries to be not affected by manually setting the system real time + * clock or adjustments by NTP synchronization. + * + * @return number of microseconds from some fixed moment + */ +uint_fast64_t +MHD_monotonic_msec_counter (void) +{ +#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_TIMESPEC_GET) + struct timespec ts; +#endif /* HAVE_CLOCK_GETTIME || HAVE_TIMESPEC_GET */ + +#ifdef HAVE_CLOCK_GETTIME + if ( (_MHD_UNWANTED_CLOCK != mono_clock_id) && + (0 == clock_gettime (mono_clock_id, + &ts)) ) + return (uint_fast64_t) (((uint_fast64_t) (ts.tv_sec - mono_clock_start)) + * 1000 + + (uint_fast64_t) (ts.tv_nsec / 1000000)); +#endif /* HAVE_CLOCK_GETTIME */ +#ifdef HAVE_CLOCK_GET_TIME + if (_MHD_INVALID_CLOCK_SERV != mono_clock_service) + { + mach_timespec_t cur_time; + + if (KERN_SUCCESS == clock_get_time (mono_clock_service, + &cur_time)) + return (uint_fast64_t) (((uint_fast64_t) (cur_time.tv_sec + - mono_clock_start)) + * 1000 + (uint_fast64_t) (cur_time.tv_nsec + / 1000000)); + } +#endif /* HAVE_CLOCK_GET_TIME */ +#if defined(_WIN32) +#if _WIN32_WINNT >= 0x0600 + if (1) + return (uint_fast64_t) (GetTickCount64 () - tick_start); +#else /* _WIN32_WINNT < 0x0600 */ + if (0 != perf_freq) + { + LARGE_INTEGER perf_counter; + uint_fast64_t num_ticks; + + QueryPerformanceCounter (&perf_counter); /* never fail on XP and later */ + num_ticks = (uint_fast64_t) (perf_counter.QuadPart - perf_start); + return ((num_ticks / perf_freq) * 1000) + + ((num_ticks % perf_freq) / (perf_freq / 1000)); + } +#endif /* _WIN32_WINNT < 0x0600 */ +#endif /* _WIN32 */ +#ifdef HAVE_GETHRTIME + if (1) + return ((uint_fast64_t) (gethrtime () - hrtime_start)) / 1000000; +#endif /* HAVE_GETHRTIME */ + + /* Fallbacks, affected by system time change */ +#ifdef HAVE_TIMESPEC_GET + if (TIME_UTC == timespec_get (&ts, TIME_UTC)) + return (uint_fast64_t) (((uint_fast64_t) (ts.tv_sec - gettime_start)) * 1000 + + (uint_fast64_t) (ts.tv_nsec / 1000000)); +#elif defined(HAVE_GETTIMEOFDAY) + if (1) + { + struct timeval tv; + if (0 == gettimeofday (&tv, NULL)) + return (uint_fast64_t) (((uint_fast64_t) (tv.tv_sec - gettime_start)) + * 1000 + + (uint_fast64_t) (tv.tv_usec / 1000)); + } +#endif /* HAVE_GETTIMEOFDAY */ + + /* The last resort fallback with very low resolution */ + return (uint_fast64_t) (time (NULL) - sys_clock_start) * 1000; +} diff --git a/src/mhd2/mhd_mono_clock.h b/src/mhd2/mhd_mono_clock.h @@ -0,0 +1,56 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2015-2024 Karlson2k (Evgeny Grin) + + This library 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. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file src/mhd2/mhd_mono_clock.h + * @brief internal monotonic clock functions declarations + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_MONO_CLOCK_H +#define MHD_MONO_CLOCK_H 1 +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +/** + * Initialise milliseconds counters. + */ +void +MHD_monotonic_msec_counter_init (void); + + +/** + * Deinitialise milliseconds counters by freeing any allocated resources + */ +void +MHD_monotonic_msec_counter_finish (void); + + +/** + * Monotonic milliseconds counter, useful for timeout calculation. + * Tries to be not affected by manually setting the system real time + * clock or adjustments by NTP synchronization. + * + * @return number of microseconds from some fixed moment + */ +uint_fast64_t +MHD_monotonic_msec_counter (void); + +#endif /* MHD_MONO_CLOCK_H */ diff --git a/src/mhd2/mhd_panic.c b/src/mhd2/mhd_panic.c @@ -0,0 +1,110 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_panic.h + * @brief mhd_panic() and MHD_lib_set_panic_func() implementations + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include <stdio.h> +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#elif defined(HAVE_UNISTD_H) +# include <unistd.h> +#endif +#include "sys_null_macro.h" +#include "mhd_public_api.h" +#include "mhd_panic.h" + +/** + * The user handler for panic + */ +static MHD_PanicCallback user_panic_handler = (MHD_PanicCallback) NULL; + +/** + * The closure argument for the #user_panic_handler + */ +static void *user_panic_handler_cls = NULL; + +MHD_INTERNAL void +mhd_panic_init_default (void) +{ + user_panic_handler = (MHD_PanicCallback) NULL; +} + + +MHD_EXTERN_ void +MHD_lib_set_panic_func (MHD_PanicCallback cb, + void *cls) +{ + user_panic_handler = cb; + user_panic_handler_cls = cls; +} + + +MHD_NORETURN_ MHD_INTERNAL void +mhd_panic (const char *file, + const char *func, + unsigned int line, + const char *message) +{ + static const char empty_str[1] = ""; + if (NULL == file) + file = empty_str; + if (NULL == func) + func = empty_str; + if (NULL == message) + message = empty_str; + if (NULL != user_panic_handler) + user_panic_handler (user_panic_handler_cls, + file, func, line, message); +#ifdef HAVE_LOG_FUNCTIONALITY + if (0 == file[0]) + fprintf (stderr, + "Unrecoverable error detected in GNU libmicrohttpd%s%s\n", + (0 == message[0]) ? "" : ": ", + message); + else + { + if (0 != func[0]) + { + fprintf (stderr, + "Unrecoverable error detected in GNU libmicrohttpd, " \ + "file '%s' at %s:%u%s%s\n", + file, func, line, + (0 == message[0]) ? "" : ": ", + message); + } + else + { + fprintf (stderr, + "Unrecoverable error detected in GNU libmicrohttpd, " \ + "file '%s' at line %u%s%s\n", + file, line, + (0 == message[0]) ? "" : ": ", + message); + } + } +#endif /* HAVE_LOG_FUNCTIONALITY */ + abort (); +} diff --git a/src/mhd2/mhd_panic.h b/src/mhd2/mhd_panic.h @@ -0,0 +1,105 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_panic.h + * @brief MHD_PANIC() macro and declarations of the related functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_PANIC_H +#define MHD_PANIC_H 1 + +#include "mhd_sys_options.h" + +#ifndef BUILDING_MHD_LIB +/* Simplified implementation, utilised by unit tests that use some parts of + the library code directly. */ +#include <stdio.h> +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#elif defined(HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#define MHD_PANIC(msg) \ + do { fprintf (stderr,"Unrecoverable error: %s\n", msg); abort (); } \ + while (0) + +#else /* BUILDING_MHD_LIB */ +/* Fully functional implementation for the library */ + +/** + * Internal panic handler + * @param file the name of the file where the panic was triggered + * @param func the name of the function where the panic was triggered + * @param line the number of the line where the panic was triggered + * @param message the message with the description of the panic + */ +MHD_NORETURN_ MHD_INTERNAL void +mhd_panic (const char *file, + const char *func, + unsigned int line, + const char *message); + + +#ifdef MHD_PANIC +#error MHD_PANIC macro is already defined. Check other headers. +#endif /* MHD_PANIC */ + +#ifdef HAVE_LOG_FUNCTIONALITY +# ifdef MHD_HAVE_MHD_FUNC_ +/** + * Panic processing for unrecoverable errors. + * + * @param msg the error message string + */ +# define MHD_PANIC(msg) \ + mhd_panic (__FILE__, MHD_FUNC_, __LINE__, msg) +# else +# include "sys_null_macro.h" +/** + * Panic processing for unrecoverable errors. + * + * @param msg the error message string + */ +# define MHD_PANIC(msg) \ + mhd_panic (__FILE__, NULL, __LINE__, msg) +# endif +#else +# include "sys_null_macro.h" +/** + * Panic processing for unrecoverable errors. + * + * @param msg the error message string + */ +# define MHD_PANIC(msg) \ + mhd_panic (NULL, NULL, __LINE__, NULL) +#endif + +/** + * Initialise panic handler to default value + */ +MHD_INTERNAL void +mhd_panic_init_default (void); + +#endif /* BUILDING_MHD_LIB */ + +#endif /* ! MHD_PANIC_H */ diff --git a/src/mhd2/mhd_public_api.h b/src/mhd2/mhd_public_api.h @@ -0,0 +1,41 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_public_api.h + * @brief The header for the MHD public API + * @author Karlson2k (Evgeny Grin) + * + * This header acts as a wrapper for "microhttpd2.h", with correct system + * headers included for the types used in the public API. + */ + +#ifndef MHD_PUBLIC_API_H +#define MHD_PUBLIC_API_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "mhd_socket_type.h" +#include "sys_sockets_types.h" + +#include "microhttpd2.h" + +#endif /* ! MHD_PUBLIC_API_H */ diff --git a/src/mhd2/mhd_recv.c b/src/mhd2/mhd_recv.c @@ -0,0 +1,92 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_recv.c + * @brief The implementation of the mhd_recv() function + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "mhd_recv.h" + +#include "mhd_connection.h" + +#include "mhd_socket_type.h" +#include "sys_sockets_headers.h" +#include "mhd_sockets_macros.h" + +#include "mhd_limits.h" +#include "mhd_socket_error.h" + + +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_plain_recv (struct MHD_Connection *restrict c, + size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict received) +{ + /* Plain TCP connection */ + ssize_t res; + enum mhd_SocketError err; + + if (MHD_SCKT_SEND_MAX_SIZE_ < buf_size) + buf_size = MHD_SCKT_SEND_MAX_SIZE_; + + res = mhd_sys_recv (c->socket_fd, buf, buf_size); + if (0 <= res) + { + *received = (size_t) res; + if (buf_size > (size_t) res) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ + (((unsigned int) c->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_RECV_READY)); + return mhd_SOCKET_ERR_NO_ERROR; /* Success exit point */ + } + + err = mhd_socket_error_get_from_sys_err (mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ + (((unsigned int) c->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_RECV_READY)); + + return err; /* Failure exit point */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_recv (struct MHD_Connection *restrict c, + size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict received) +{ + mhd_assert (MHD_INVALID_SOCKET != c->socket_fd); + mhd_assert (MHD_CONNECTION_CLOSED != c->state); + + // TODO: implement TLS support + + return mhd_plain_recv (c, buf_size, buf, received); +} diff --git a/src/mhd2/mhd_recv.h b/src/mhd2/mhd_recv.h @@ -0,0 +1,57 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_recv.h + * @brief The definition of the mhd_recv() function + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RECV_H +#define MHD_RECV_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "mhd_socket_error.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Receive the data from the network socket. + * Clear #mhd_SOCKET_NET_STATE_RECV_READY in sk_ready if necessary. + * + * @param c the connection to use + * @param buf_size the size of the @a buf buffer + * @param[out] buf the buffer to fill with the received data + * @param[out] received the pointer to variable to get the size of the data + * actually put to the @a buffer + * @return mhd_SOCKET_ERR_NO_ERROR if receive succeed (the @a received gets + * the received size) or socket error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_recv (struct MHD_Connection *restrict c, + size_t buf_size, + char buffer[MHD_FN_PAR_DYN_ARR_SIZE_(buf_size)], + size_t *restrict received) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_(3,2) MHD_FN_PAR_OUT_ (4); + + +#endif /* ! MHD_RECV_H */ diff --git a/src/mhd2/mhd_reply.h b/src/mhd2/mhd_reply.h @@ -0,0 +1,151 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2021-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_reply.h + * @brief The definition of the working reply data + * @author Karlson2k (Evgeny Grin) + * + * Data structures in this header are used when responding to client's request. + * Do not be confused with terms "response" and "reply" using in MHD code. + * The "MHD_Response" is an connection-independent object that have all + * data required to form a respond. + * The "MHD_Reply" is working connection-specific data used to format + * the respond based on provided data in "MHD_Response". + */ + +#ifndef MHD_REPLY_H +#define MHD_REPLY_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "mhd_dcc_action.h" + +#include "mhd_iovec.h" + +struct MHD_Response; /* forward declaration */ + +/** + * Reply-specific properties. + */ +struct MHD_Reply_Properties +{ +#ifdef _DEBUG + bool set; /**< Indicates that other members are set and valid */ +#endif /* _DEBUG */ + bool use_reply_body_headers; /**< Use reply body-specific headers */ + bool send_reply_body; /**< Send reply body (can be zero-sized) */ + bool chunked; /**< Use chunked encoding for reply */ + bool end_by_closing; /**< Signal end of content (only) by closing connection */ +}; + +/** + * The location of the reply content + */ +enum MHD_FIXED_ENUM_ mhd_ReplyContentLocation +{ + /** + * Reply content is absent + */ + mhd_REPLY_CNTN_LOC_NOWHERE = 0 + , + /** + * Reply content is in the response buffer + */ + mhd_REPLY_CNTN_LOC_RESP_BUF + , + /** + * Reply content is in the connection buffer + */ + mhd_REPLY_CNTN_LOC_CONN_BUF + , + /** + * Reply content is in the vector data + */ + mhd_REPLY_CNTN_LOC_IOV + , + /** + * Reply content is in the file, to be used with sendfile() function + */ + mhd_REPLY_CNTN_LOC_FILE +}; + + +/** + * Reply-specific values. + * + * Meaningful for the current reply only. + */ +struct MHD_Reply +{ + /** + * The action provided by application when content is dynamically created. + * Used only when mhd_RESPONSE_CONTENT_DATA_CALLBACK == response->cntn_dtype + */ + struct MHD_DynamicContentCreatorAction app_act; + + /** + * The context provided for application callback for dynamic content. + * Used only when mhd_RESPONSE_CONTENT_DATA_CALLBACK == response->cntn_dtype + */ + struct MHD_DynamicContentCreatorContext app_act_ctx; + + /** + * Response to transmit (initially NULL). + */ + struct MHD_Response *response; + + /** + * The "ICY" response. + * Reply begins with the SHOUTcast "ICY" line instead of "HTTP". + */ + bool responseIcy; + + /** + * Current rest position in the actual content (should be 0 while + * sending headers). + * When sending buffers located in the connection buffers, it is updated + * when the data copied to the buffers. In other cases it is updated when + * data is actually sent. + */ + uint_fast64_t rsp_cntn_read_pos; + + /** + * The copy of iov response. + * Valid if iovec response is used. + * Updated during send. + * Members are allocated in the pool. + */ + struct mhd_iovec_track resp_iov; + + /** + * The location of the reply content + */ + enum mhd_ReplyContentLocation cntn_loc; + + /** + * Reply-specific properties + */ + struct MHD_Reply_Properties props; +}; + +#endif /* ! MHD_REPLY_H */ diff --git a/src/mhd2/mhd_request.h b/src/mhd2/mhd_request.h @@ -0,0 +1,375 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_request.h + * @brief The definition of the request data structures + * @author Karlson2k (Evgeny Grin) + * + * Data structures in this header are used when parsing client's request + */ + +#ifndef MHD_REQUEST_H +#define MHD_REQUEST_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" +#include "sys_bool_type.h" +#include "mhd_public_api.h" + +#include "mhd_dlinked_list.h" + +#include "http_prot_ver.h" +#include "http_method.h" +#include "mhd_action.h" +#include "mhd_buffer.h" + + +/** + * The action set by the application + */ +struct mhd_ApplicationAction +{ + /** + * The action after header reporting + */ + struct MHD_Action head_act; + /** + * The action during upload processing + */ + struct MHD_UploadAction upl_act; +}; + +/** + * The request line processing data + */ +struct MHD_RequestLineProcessing +{ + /** + * The position of the next character to be processed + */ + size_t proc_pos; + /** + * The number of empty lines skipped + */ + unsigned int skipped_empty_lines; + /** + * The position of the start of the current/last found whitespace block, + * zero if not found yet. + */ + size_t last_ws_start; + /** + * The position of the next character after the last known whitespace + * character in the current/last found whitespace block, + * zero if not found yet. + */ + size_t last_ws_end; + /** + * The pointer to the request target. + * The request URI will be formed based on it. + */ + char *rq_tgt; + /** + * The pointer to the first question mark in the @a rq_tgt. + */ + char *rq_tgt_qmark; + /** + * The number of whitespace characters in the request URI + */ + size_t num_ws_in_uri; +}; + +/** + * The request header processing data + */ +struct MHD_HeaderProcessing +{ + /** + * The position of the last processed character + */ + size_t proc_pos; + + /** + * The position of the first whitespace character in current contiguous + * whitespace block. + * Zero when no whitespace found or found non-whitespace character after + * whitespace. + * Must be zero, if the current character is not whitespace. + */ + size_t ws_start; + + /** + * Indicates that end of the header (field) name found. + * Must be false until the first colon in line is found. + */ + bool name_end_found; + + /** + * The length of the header name. + * Must be zero until the first colon in line is found. + * Name always starts at zero position. + */ + size_t name_len; + + /** + * The position of the first character of the header value. + * Zero when the first character has not been found yet. + */ + size_t value_start; + + /** + * Line starts with whitespace. + * It's meaningful only for the first line, as other lines should be handled + * as "folded". + */ + bool starts_with_ws; +}; + +/** + * The union of request line and header processing data + */ +union MHD_HeadersProcessing +{ + /** + * The request line processing data + */ + struct MHD_RequestLineProcessing rq_line; + + /** + * The request header processing data + */ + struct MHD_HeaderProcessing hdr; +}; + + +/** + * The union of text staring point and the size of the text + */ +union MHD_StartOrSize +{ + /** + * The starting point of the text. + * Valid when the text is being processed and the end of the text + * is not yet determined. + */ + const char *start; + /** + * The size of the text. + * Valid when the text has been processed and the end of the text + * is known. + */ + size_t size; +}; + +struct mhd_RequestField; /* forward declarations */ + +mhd_DLINKEDL_LINKS_DEF (mhd_RequestField); + +/** + * Header, footer, or cookie for HTTP request. + */ +struct mhd_RequestField +{ + /** + * The field data + */ + struct MHD_NameValueKind field; + + /** + * Headers are kept in a double-linked list. + */ + mhd_DLNKDL_LINKS (mhd_RequestField,fields); +}; + +mhd_DLINKEDL_LIST_DEF (mhd_RequestField); + + +/** + * The request content data + */ +struct mhd_ReqContentData +{ + /** + * The pointer to the large buffer + * Must be NULL if large buffer is not allocated. + */ + struct mhd_Buffer lbuf; + + /** + * The total size of the request content. + * #MHD_SIZE_UNKNOWN if the size is not yet known (chunked upload). + */ + uint_fast64_t cntn_size; + + /** + * The size of the received content + */ + uint_fast64_t recv_size; + + /** + * The size of the processed content + */ + uint_fast64_t proc_size; +}; + + +/** + * Request-specific values. + * + * Meaningful for the current request only. + */ +struct MHD_Request +{ + /** + * Linked list of parsed headers. + */ + mhd_DLNKDL_LIST (mhd_RequestField,fields); + + /** + * The action set by the application + */ + struct mhd_ApplicationAction app_act; + + /** + * The request content data + */ + struct mhd_ReqContentData cntn; + + /** + * Set to true if request is too large to be handled + */ + bool too_large; + + /** + * HTTP version string (i.e. http/1.1). Allocated + * in pool. + */ + const char *version; + + /** + * HTTP protocol version as enum. + */ + enum MHD_HTTP_ProtocolVersion http_ver; + + /** + * Request method. Should be GET/POST/etc. Allocated in pool. + */ + const char *method; + + /** + * The request method as enum. + */ + enum mhd_HTTP_Method http_mthd; + + /** + * Requested URL, the part before '?' (excluding parameters). Allocated + * in pool. + */ + const char *url; + + /** + * The length of the @a url in characters, not including the terminating zero. + */ + size_t url_len; + + /** + * The original length of the request target. + */ + size_t req_target_len; + + /** + * Number of bytes we had in the HTTP header, set once we + * pass #MHD_CONNECTION_HEADERS_RECEIVED. + * This includes the request line, all request headers, the header section + * terminating empty line, with all CRLF (or LF) characters. + */ + size_t header_size; + + /** + * The union of the size of all request field lines (headers) and + * the starting point of the first request field line (the first header). + * Until #MHD_CONNECTION_HEADERS_RECEIVED the @a start member is valid, + * staring with #MHD_CONNECTION_HEADERS_RECEIVED the @a size member is valid. + * The size includes CRLF (or LR) characters, but does not include + * the terminating empty line. + */ + union MHD_StartOrSize field_lines; + + /** + * Are we receiving with chunked encoding? + * This will be set to #MHD_YES after we parse the headers and + * are processing the body with chunks. + * After we are done with the body and we are processing the footers; + * once the footers are also done, this will be set to #MHD_NO again + * (before the final call to the handler). + * It is used only for requests, chunked encoding for response is + * indicated by @a rp_props. + */ + bool have_chunked_upload; + + /** + * If we are receiving with chunked encoding, where are we right + * now? + * Set to 0 if we are waiting to receive the chunk size; + * otherwise, this is the size of the current chunk. + * A value of zero is also used when we're at the end of the chunks. + */ + uint_fast64_t current_chunk_size; + + /** + * If we are receiving with chunked encoding, where are we currently + * with respect to the current chunk (at what offset / position)? + */ + uint_fast64_t current_chunk_offset; + + /** + * We allow the main application to associate some pointer with the + * HTTP request, which is passed to each #MHD_AccessHandlerCallback + * and some other API calls. Here is where we store it. (MHD does + * not know or care what it is). + */ + void *app_context; + + /** + * Did we ever call the "default_handler" on this request? + * This flag determines if we have called the #MHD_OPTION_NOTIFY_COMPLETED + * handler when the request finishes. + */ + bool app_aware; + + /** + * Number of bare CR characters that were replaced with space characters + * in the request line or in the headers (field lines). + */ + size_t num_cr_sp_replaced; + + /** + * The number of header lines skipped because they have no colon + */ + size_t skipped_broken_lines; + + /** + * The data of the request line / request headers processing + */ + union MHD_HeadersProcessing hdrs; +}; + + +#endif /* ! MHD_REQUEST_H */ diff --git a/src/mhd2/mhd_response.h b/src/mhd2/mhd_response.h @@ -0,0 +1,362 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_response.h + * @brief The definition of the MHD_Response type and related structures + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_H +#define MHD_RESPONSE_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_public_api.h" + +#include "mhd_dlinked_list.h" +#include "mhd_str_types.h" + +#include "mhd_iovec.h" + +#ifdef MHD_USE_THREADS +# include "mhd_locks.h" +#endif + +#include "mhd_atomic_counter.h" + + +struct ResponseOptions; /* forward declaration */ + +struct mhd_ResponseHeader; /* forward declaration */ + +mhd_DLINKEDL_LINKS_DEF (mhd_ResponseHeader); + +/** + * Response header / field + */ +struct mhd_ResponseHeader +{ + /** + * The name of the header / field + */ + struct MHD_String name; + + /** + * The value of the header / field + */ + struct MHD_String value; + + /** + * The links to other headers + */ + mhd_DLNKDL_LINKS (mhd_ResponseHeader,headers); +}; + +/** + * The type of content + */ +enum mhd_ResponseContentDataType +{ + mhd_RESPONSE_CONTENT_DATA_INVALID = 0 + , + mhd_RESPONSE_CONTENT_DATA_BUFFER + , + mhd_RESPONSE_CONTENT_DATA_IOVEC + , + mhd_RESPONSE_CONTENT_DATA_FILE + , + mhd_RESPONSE_CONTENT_DATA_CALLBACK +}; + +/** + * I/O vector response data + */ +struct mhd_ResponseIoVec +{ + /** + * The copy of array of iovec elements. + * Must be freed! + */ + mhd_iovec *iov; + + /** + * The number of elements in the @a iov array + */ + size_t cnt; +}; + +/** + * The file data for the the response + */ +struct mhd_ResponseFD +{ + /** + * The file description of the response + */ + int fd; + + /** + * The offset in the file of the response content + */ + uint_fast64_t offset; + + /** + * Indicate that @a fd is a pipe + */ + bool is_pipe; + +#ifdef MHD_USE_SENDFILE + /** + * Use 'sendfile()' function for the @a FD + * Initially 'true' (except for pipes) but can be flipped to 'false' if + * sendfile() cannot handle this file. + */ + volatile bool use_sf; +#endif +}; + +/** + * Dynamic response data + */ +struct mhd_ResponseDynamic +{ + /** + * The callback for the content data + */ + MHD_DynamicContentCreator cb; + /** + * The closure for the @a cb + */ + void *cls; +}; + +/** + * The response content data + */ +union mhd_ResponseContent +{ + /** + * The fixed unmodifiable data. + * 'unsigned char' pointer is used to simplify individual ranges addressing. + */ + const unsigned char *restrict buf; + + /** + * The I/O vector data + */ + struct mhd_ResponseIoVec iovec; + + /** + * The file data for the the response + */ + struct mhd_ResponseFD file; + + /** + * Dynamic response data + */ + struct mhd_ResponseDynamic dyn; +}; + +/** + * The data of the free/cleanup callback + */ +struct mhd_FreeCbData +{ + /** + * The Free/Cleanup callback + */ + MHD_FreeCallback cb; + + /** + * The closure for the @a cb + */ + void *cls; +}; + + +struct mhd_ResponseReuseData +{ + /** + * Indicate that response could be used more than one time + */ + volatile bool reusable; + + /** + * The number of active uses of the response. + * Used only when @a reusable is 'true'. + * When number reached zero, the response is destroyed. + */ + struct mhd_AtomicCounter counter; + +#ifdef MHD_USE_THREADS + /** + * The mutex for @a settings access. + * Used only when @a reusable is 'true'. + */ + mhd_mutex settings_lock; +#endif /* MHD_USE_THREADS */ +}; + +struct mhd_ResponseConfiguration +{ + /** + * Response have undefined content + * Must be used only when response content (even zero-size) is not allowed. + */ + bool head_only; + + /** + * If set to 'true' then the chunked encoding must be used (if allowed + * by HTTP version). + * If 'false' then chunked encoding must not be used. + */ + bool chunked; + + /** + * If 'true', "Connection: close" header must be always used + */ + bool close_forced; + + /** + * Use "HTTP/1.0" in the reply header + * @a chunked is 'false' if this flag set. + * @a close_forced is 'true' is this flag set. + */ + bool mode_1_0; + + /** + * The (possible incorrect) content length is provided by application + */ + bool cnt_len_by_app; + + /** + * Response has "Date:" header + */ + bool has_hdr_date; // TODO: set the member + + /** + * Response has "Connection:" header + */ + bool has_hdr_conn; // TODO: set the member + + /** + * Response is internal-only error response + */ + 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 +{ + bool is_internal; +}; +#endif + +mhd_DLINKEDL_LIST_DEF (mhd_ResponseHeader); + +// TODO: Group members in structs + +struct MHD_Response +{ + /** + * The response HTTP status code + */ + enum MHD_HTTP_StatusCode sc; + + /** + * The size of the response. + * #MHD_SIZE_UNKNOWN if size is undefined + */ + uint_fast64_t cntn_size; + + /** + * The type of the content data + */ + enum mhd_ResponseContentDataType cntn_dtype; + + /** + * The data of the content of the response + */ + union mhd_ResponseContent cntn; + + /** + * The data of the free/cleanup callback + */ + struct mhd_FreeCbData free; + + /** + * Configuration data of the response + */ + struct mhd_ResponseConfiguration cfg; + + /** + * If response is "frozen" then response data cannot be changed. + * The use counter for re-usable responses is the exception and can be + * changed when "frozen". + */ + volatile bool frozen; + + /** + * The re-use parameters + */ + struct mhd_ResponseReuseData reuse; + + /** + * The settings, before the response is @a frozen + */ + struct ResponseOptions *restrict settings; + + /** + * The double linked list of the response headers + */ + mhd_DLNKDL_LIST (mhd_ResponseHeader,headers); + + /** + * Special data for internal error responses + */ + struct mhd_ResponseInternalErrData special_resp; + + #ifndef NDEBUG + struct mhd_ResponseDebug dbg; +#endif +}; + +#endif /* ! MHD_RESPONSE_H */ diff --git a/src/mhd2/mhd_send.c b/src/mhd2/mhd_send.c @@ -0,0 +1,1621 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2017-2024 Karlson2k (Evgeny Grin), Full re-write of buffering + and pushing, many bugs fixes, optimisations, + sendfile() porting + Copyright (C) 2019 ng0 <ng0@n0.is>, Initial version of send() wrappers + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + */ + +/** + * @file src/mhd2/mhd_send.c + * @brief Implementation of send() wrappers and helper functions. + * @author Karlson2k (Evgeny Grin) + * @author ng0 (N. Gillmann) + * @author Christian Grothoff + */ + +/* Worth considering for future improvements and additions: + * NetBSD has no sendfile or sendfile64. The way to work + * with this seems to be to mmap the file and write(2) as + * large a chunk as possible to the socket. Alternatively, + * use madvise(..., MADV_SEQUENTIAL). */ + +#include "mhd_sys_options.h" + +#include <string.h> + +#include "mhd_send.h" +#include "sys_sockets_headers.h" +#include "sys_ip_headers.h" +#include "mhd_sockets_macros.h" +#include "daemon_logger.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" +#include "mhd_response.h" + +#include "mhd_iovec.h" +#ifdef HAVE_LINUX_SENDFILE +# include <sys/sendfile.h> +#endif /* HAVE_LINUX_SENDFILE */ + +#if defined(HAVE_FREEBSD_SENDFILE) || defined(HAVE_DARWIN_SENDFILE) +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/uio.h> +#endif /* HAVE_FREEBSD_SENDFILE || HAVE_DARWIN_SENDFILE */ +#ifdef HAVE_SYS_PARAM_H +/* For FreeBSD version identification */ +# include <sys/param.h> +#endif /* HAVE_SYS_PARAM_H */ +#ifdef HAVE_SYSCONF +# include <unistd.h> +#endif /* HAVE_SYSCONF */ +#include "mhd_assert.h" + +#include "mhd_limits.h" + +#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) || \ + defined(MHD_WINSOCK_SOCKETS) +# define mhd_USE_VECT_SEND 1 +#endif /* HAVE_SENDMSG || HAVE_WRITEV || MHD_WINSOCK_SOCKETS */ + + +#ifdef mhd_USE_VECT_SEND +# if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL)) && \ + defined(mhd_SEND_SPIPE_SUPPRESS_POSSIBLE) && \ + defined(mhd_SEND_SPIPE_SUPPRESS_NEEDED) +# define mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED 1 +# endif /* (!HAVE_SENDMSG || !MSG_NOSIGNAL) && + mhd_SEND_SPIPE_SUPPRESS_POSSIBLE && mhd_SEND_SPIPE_SUPPRESS_NEEDED */ +#endif /* mhd_USE_VECT_SEND */ + +/** + * sendfile() chuck size + */ +#define mhd_SENFILE_CHUNK_SIZE (0x20000) + +/** + * sendfile() chuck size for thread-per-connection + */ +#define mhd_SENFILE_CHUNK_SIZE_FOR_THR_P_C (0x200000) + +#if defined(HAVE_FREEBSD_SENDFILE) && defined(SF_FLAGS) +/** + * FreeBSD sendfile() flags + */ +static int freebsd_sendfile_flags_; + +/** + * FreeBSD sendfile() flags for thread-per-connection + */ +static int freebsd_sendfile_flags_thd_p_c_; + + +/** + * Initialises variables for FreeBSD's sendfile() + */ +static void +freebsd_sendfile_init_ (void) +{ + long sys_page_size = sysconf (_SC_PAGESIZE); + if (0 >= sys_page_size) + { /* Failed to get page size. */ + freebsd_sendfile_flags_ = SF_NODISKIO; + freebsd_sendfile_flags_thd_p_c_ = SF_NODISKIO; + } + else + { + freebsd_sendfile_flags_ = + SF_FLAGS ((uint_least16_t) \ + ((mhd_SENFILE_CHUNK_SIZE + sys_page_size - 1) / sys_page_size) \ + & 0xFFFFU, SF_NODISKIO); + freebsd_sendfile_flags_thd_p_c_ = + SF_FLAGS ((uint_least16_t) \ + ((mhd_SENFILE_CHUNK_SIZE_FOR_THR_P_C + sys_page_size - 1) \ + / sys_page_size) & 0xFFFFU, SF_NODISKIO); + } +} + + +#else /* ! HAVE_FREEBSD_SENDFILE || ! SF_FLAGS */ +# define freebsd_sendfile_init_() (void) 0 +#endif /* HAVE_FREEBSD_SENDFILE */ + + +#if defined(HAVE_SYSCONF) && defined(_SC_IOV_MAX) +/** + * Current IOV_MAX system value + */ +static unsigned long mhd_iov_max_ = 0; + +static void +iov_max_init_ (void) +{ + long res = sysconf (_SC_IOV_MAX); + if (res >= 0) + mhd_iov_max_ = (unsigned long) res; + else + { +# if defined(IOV_MAX) + mhd_iov_max_ = IOV_MAX; +# else /* ! IOV_MAX */ + mhd_iov_max_ = 8; /* Should be the safe limit */ +# endif /* ! IOV_MAX */ + } +} + + +/** + * IOV_MAX (run-time) value + */ +# define mhd_IOV_MAX mhd_iov_max_ +#else /* ! HAVE_SYSCONF || ! _SC_IOV_MAX */ +# define iov_max_init_() (void) 0 +# if defined(IOV_MAX) + +/** + * IOV_MAX (static) value + */ +# define mhd_IOV_MAX IOV_MAX +# endif /* IOV_MAX */ +#endif /* ! HAVE_SYSCONF || ! _SC_IOV_MAX */ + + +/** + * Initialises static variables + */ +void +mhd_send_init_static_vars (void) +{ + /* FreeBSD 11 and later allow to specify read-ahead size + * and handles SF_NODISKIO differently. + * SF_FLAGS defined only on FreeBSD 11 and later. */ + freebsd_sendfile_init_ (); + + iov_max_init_ (); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_connection_set_nodelay_state (struct MHD_Connection *connection, + bool nodelay_state) +{ +#ifdef TCP_NODELAY + static const mhd_SCKT_OPT_BOOL off_val = 0; + static const mhd_SCKT_OPT_BOOL on_val = 1; + int err_code; + + if (mhd_T_IS_YES (connection->is_nonip)) + return false; + + if (0 == setsockopt (connection->socket_fd, + IPPROTO_TCP, + TCP_NODELAY, + (const void *) (nodelay_state ? &on_val : &off_val), + sizeof (off_val))) + { + connection->sk_nodelay = nodelay_state ? mhd_T_YES : mhd_T_NO; + return true; + } + + err_code = mhd_SCKT_GET_LERR (); + if ((mhd_T_IS_NOT_YES (connection->is_nonip)) && + (mhd_SCKT_ERR_IS_EINVAL (err_code) || + mhd_SCKT_ERR_IS_NOPROTOOPT (err_code) || + mhd_SCKT_ERR_IS_NOTSOCK (err_code))) + { + connection->is_nonip = mhd_T_YES; + } + else + { + mhd_LOG_MSG (connection->daemon, MHD_SC_SOCKET_TCP_NODELAY_FAILED, \ + "Failed to set required TCP_NODELAY option for the socket."); + } +#else /* ! TCP_NODELAY */ + (void) nodelay_state; /* Mute compiler warnings */ + connection->sk_nodelay = mhd_T_NO; +#endif /* ! TCP_NODELAY */ + return false; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_connection_set_cork_state (struct MHD_Connection *connection, + bool cork_state) +{ +#if defined(mhd_TCP_CORK_NOPUSH) + static const mhd_SCKT_OPT_BOOL off_val = 0; + static const mhd_SCKT_OPT_BOOL on_val = 1; + int err_code; + + if (mhd_T_IS_YES (connection->is_nonip)) + return false; + + if (0 == setsockopt (connection->socket_fd, + IPPROTO_TCP, + mhd_TCP_CORK_NOPUSH, + (const void *) (cork_state ? &on_val : &off_val), + sizeof (off_val))) + { + connection->sk_corked = cork_state ? mhd_T_YES : mhd_T_NO; + return true; + } + + err_code = mhd_SCKT_GET_LERR (); + if ((mhd_T_IS_NOT_YES (connection->is_nonip)) && + (mhd_SCKT_ERR_IS_EINVAL (err_code) || + mhd_SCKT_ERR_IS_NOPROTOOPT (err_code) || + mhd_SCKT_ERR_IS_NOTSOCK (err_code))) + { + connection->is_nonip = mhd_T_YES; + } + else + { +# ifdef TCP_CORK + mhd_LOG_MSG (connection->daemon, MHD_SC_SOCKET_TCP_CORK_NOPUSH_FAILED, \ + "Failed to set required TCP_CORK option for the socket."); +# else + mhd_LOG_MSG (connection->daemon, MHD_SC_SOCKET_TCP_CORK_NOPUSH_FAILED, \ + "Failed to set required TCP_NOPUSH option for the socket."); +# endif + } + +#else /* ! mhd_TCP_CORK_NOPUSH */ + (void) cork_state; /* Mute compiler warnings. */ + connection->sk_corked = mhd_T_NO; +#endif /* ! mhd_TCP_CORK_NOPUSH */ + return false; +} + + +/** + * Handle pre-send setsockopt calls. + * + * @param connection the MHD_Connection structure + * @param plain_send set to true if plain send() or sendmsg() will be called, + * set to false if TLS socket send(), sendfile() or + * writev() will be called. + * @param push_data whether to push data to the network from buffers after + * the next call of send function. + */ +static void +pre_send_setopt (struct MHD_Connection *connection, + bool plain_send, + bool push_data) +{ + /* Try to buffer data if not sending the final piece. + * Final piece is indicated by push_data == true. */ + const bool buffer_data = (! push_data); + + if (mhd_T_IS_YES (connection->is_nonip)) + return; + + // TODO: support inheriting of TCP_NODELAY and TCP_NOPUSH + + /* The goal is to minimise the total number of additional sys-calls + * before and after send(). + * The following tricky (over-)complicated algorithm typically use zero, + * one or two additional sys-calls (depending on OS) for each response. */ + + if (buffer_data) + { + /* Need to buffer data if possible. */ +#ifdef mhd_USE_MSG_MORE + if (plain_send) + return; /* Data is buffered by send() with MSG_MORE flag. + * No need to check or change anything. */ +#else /* ! mhd_USE_MSG_MORE */ + (void) plain_send; /* Mute compiler warning. */ +#endif /* ! mhd_USE_MSG_MORE */ + +#ifdef mhd_TCP_CORK_NOPUSH + if (mhd_T_IS_YES (connection->sk_corked)) + return; /* The connection was already corked. */ + + /* Prefer 'cork' over 'no delay' as the 'cork' buffers better, regardless + * of the number of received ACKs. */ + if (mhd_connection_set_cork_state (connection, true)) + return; /* The connection has been corked. */ + + /* Failed to cork the connection. + * Really unlikely to happen on TCP connections. */ +#endif /* mhd_TCP_CORK_NOPUSH */ + if (mhd_T_IS_NO (connection->sk_nodelay)) + return; /* TCP_NODELAY was not set for the socket. + * Nagle's algorithm will buffer some data. */ + + /* Try to reset TCP_NODELAY state for the socket. + * Ignore possible error as no other options exist to + * buffer data. */ + mhd_connection_set_nodelay_state (connection, false); + /* TCP_NODELAY has been (hopefully) reset for the socket. + * Nagle's algorithm will buffer some data. */ + return; + } + + /* Need to push data after the next send() */ + /* If additional sys-call is required, prefer to make it only after the send() + * (if possible) as this send() may consume only part of the prepared data and + * more send() calls will be used. */ +#ifdef mhd_TCP_CORK_NOPUSH +# ifdef mhd_CORK_RESET_PUSH_DATA +# ifdef mhd_CORK_RESET_PUSH_DATA_ALWAYS + /* Data can be pushed immediately by uncorking socket regardless of + * cork state before. */ + /* This is typical for Linux, no other kernel with + * such behaviour are known so far. */ + + /* No need to check the current state of TCP_CORK / TCP_NOPUSH + * as reset of cork will push the data anyway. */ + return; /* Data may be pushed by resetting of + * TCP_CORK / TCP_NOPUSH after send() */ +# else /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ + /* Reset of TCP_CORK / TCP_NOPUSH will push the data + * only if socket is corked. */ + +# ifdef mhd_NODELAY_SET_PUSH_DATA_ALWAYS + /* Data can be pushed immediately by setting TCP_NODELAY regardless + * of TCP_NODDELAY or corking state before. */ + + /* Dead code currently, no known kernels with such behaviour and without + * pushing by uncorking. */ + return; /* Data may be pushed by setting of TCP_NODELAY after send(). + No need to make extra sys-calls before send().*/ +# else /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ + +/* These next comment blocks are just generic description for the possible + * choices for the code below. */ +# ifdef mhd_NODELAY_SET_PUSH_DATA + /* Setting of TCP_NODELAY will push the data only if + * both TCP_NODELAY and TCP_CORK / TCP_NOPUSH were not set. */ + + /* Data can be pushed immediately by uncorking socket if + * socket was corked before or by setting TCP_NODELAY if + * socket was not corked and TCP_NODELAY was not set before. */ + + /* This combination not possible currently as Linux is the only kernel that + * pushes data by setting of TCP_NODELAY and Linux pushes data always + * by TCP_NODELAY, regardless previous TCP_NODELAY state. */ +# else /* ! mhd_NODELAY_SET_PUSH_DATA */ + /* Data can be pushed immediately by uncorking socket or + * can be pushed by send() on uncorked socket if + * TCP_NODELAY was set *before*. */ + + /* This is typical modern FreeBSD and OpenBSD behaviour. */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA */ + + if (mhd_T_IS_YES (connection->sk_corked)) + return; /* Socket is corked. Data can be pushed by resetting of + * TCP_CORK / TCP_NOPUSH after send() */ + else if (mhd_T_IS_NO (connection->sk_corked)) + { + /* The socket is not corked. */ + if (mhd_T_IS_YES (connection->sk_nodelay)) + return; /* TCP_NODELAY was already set, + * data will be pushed automatically by the next send() */ +# ifdef mhd_NODELAY_SET_PUSH_DATA + else if (mhd_T_IS_MAYBE (connection->sk_nodelay)) + { + /* Setting TCP_NODELAY may push data NOW. + * Cork socket here and uncork after send(). */ + if (mhd_connection_set_cork_state (connection, true)) + return; /* The connection has been corked. + * Data can be pushed by resetting of + * TCP_CORK / TCP_NOPUSH after send() */ + else + { + /* The socket cannot be corked. + * Really unlikely to happen on TCP connections */ + /* Have to set TCP_NODELAY. + * If TCP_NODELAY real system state was OFF then + * already buffered data may be pushed NOW, but it is unlikely + * to happen as this is only a backup solution when corking has failed. + * Ignore possible error here as no other options exist to + * push data. */ + mhd_connection_set_nodelay_state (connection, true); + /* TCP_NODELAY has been (hopefully) set for the socket. + * The data will be pushed by the next send(). */ + return; + } + } +# endif /* mhd_NODELAY_SET_PUSH_DATA */ + else + { +# ifdef mhd_NODELAY_SET_PUSH_DATA + /* The socket is not corked and TCP_NODELAY is switched off. */ +# else /* ! mhd_NODELAY_SET_PUSH_DATA */ + /* The socket is not corked and TCP_NODELAY is not set or unknown. */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA */ + + /* At least one additional sys-call before send() is required. */ + /* Setting TCP_NODELAY is optimal here as data will be pushed + * automatically by the next send() and no additional + * sys-call are needed after the send(). */ + if (mhd_connection_set_nodelay_state (connection, true)) + return; + else + { + /* Failed to set TCP_NODELAY for the socket. + * Really unlikely to happen on TCP connections. */ + /* Cork the socket here and make additional sys-call + * to uncork the socket after send(). This will push the data. */ + /* Ignore possible error here as no other options exist to + * push data. */ + mhd_connection_set_cork_state (connection, true); + /* The connection has been (hopefully) corked. + * Data can be pushed by resetting of TCP_CORK / TCP_NOPUSH + * after send() */ + return; + } + } + } + /* Corked state is unknown. Need to make a sys-call here otherwise + * data may not be pushed. */ + if (mhd_connection_set_cork_state (connection, true)) + return; /* The connection has been corked. + * Data can be pushed by resetting of + * TCP_CORK / TCP_NOPUSH after send() */ + /* The socket cannot be corked. + * Really unlikely to happen on TCP connections */ + if (mhd_T_IS_YES (connection->sk_nodelay)) + return; /* TCP_NODELAY was already set, + * data will be pushed by the next send() */ + + /* Have to set TCP_NODELAY. */ +# ifdef mhd_NODELAY_SET_PUSH_DATA + /* If TCP_NODELAY state was unknown (external connection) then + * already buffered data may be pushed here, but this is unlikely + * to happen as it is only a backup solution when corking has failed. */ +# endif /* mhd_NODELAY_SET_PUSH_DATA */ + /* Ignore possible error here as no other options exist to + * push data. */ + mhd_connection_set_nodelay_state (connection, true); + /* TCP_NODELAY has been (hopefully) set for the socket. + * The data will be pushed by the next send(). */ + return; +# endif /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ +# endif /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ +# else /* ! mhd_CORK_RESET_PUSH_DATA */ + +# ifndef mhd_NODELAY_SET_PUSH_DATA + /* Neither uncorking the socket or setting TCP_NODELAY + * push the data immediately. */ + /* The only way to push the data is to use send() on uncorked + * socket with TCP_NODELAY switched on . */ + + /* This is old FreeBSD and Darwin behaviour. */ + + /* Uncork socket if socket wasn't uncorked. */ + if (mhd_T_IS_NOT_NO (connection->sk_corked)) + mhd_connection_set_cork_state (connection, false); + + /* Set TCP_NODELAY if it wasn't set. */ + if (mhd_T_IS_NOT_YES (connection->sk_nodelay)) + mhd_connection_set_nodelay_state (connection, true); + + return; +# else /* mhd_NODELAY_SET_PUSH_DATA */ + /* Setting TCP_NODELAY push the data immediately. */ + + /* Dead code currently as Linux kernel is only kernel which push by + * setting TCP_NODELAY. The same kernel push data by resetting TCP_CORK. */ +# ifdef mhd_NODELAY_SET_PUSH_DATA_ALWAYS + return; /* Data may be pushed by setting of TCP_NODELAY after send(). + No need to make extra sys-calls before send().*/ +# else /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ + /* Cannot set TCP_NODELAY here as it would push data NOW. + * Set TCP_NODELAY after the send(), together if uncorking if necessary. */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ +# endif /* mhd_NODELAY_SET_PUSH_DATA */ +# endif /* ! mhd_CORK_RESET_PUSH_DATA */ +#else /* ! mhd_TCP_CORK_NOPUSH */ + /* Buffering of data is controlled only by + * Nagel's algorithm. */ + /* Set TCP_NODELAY if it wasn't set. */ + if (mhd_T_IS_NOT_YES (connection->sk_nodelay)) + mhd_connection_set_nodelay_state (connection, true); +#endif /* ! mhd_TCP_CORK_NOPUSH */ +} + + +#ifndef mhd_CORK_RESET_PUSH_DATA_ALWAYS +/** + * Send zero-sized data + * + * This function use send of zero-sized data to kick data from the socket + * buffers to the network. The socket must not be corked and must have + * TCP_NODELAY switched on. + * Used only as last resort option, when other options are failed due to + * some errors. + * Should not be called on typical data processing. + * @return true if succeed, false if failed + */ +static bool +zero_send (struct MHD_Connection *connection) +{ + static const int dummy = 0; + + if (mhd_T_IS_YES (connection->is_nonip)) + return false; + mhd_assert (mhd_T_IS_NO (connection->sk_corked)); + mhd_assert (mhd_T_IS_YES (connection->sk_nodelay)); + if (0 == mhd_sys_send (connection->socket_fd, &dummy, 0)) + return true; + mhd_LOG_MSG (connection->daemon, MHD_SC_SOCKET_ZERO_SEND_FAILED, \ + "Failed to push the data by zero-sized send."); + return false; +} + + +#endif /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ + +/** + * Handle post-send setsockopt calls. + * + * @param connection the MHD_Connection structure + * @param plain_send_next set to true if plain send() or sendmsg() will be + * called next, + * set to false if TLS socket send(), sendfile() or + * writev() will be called next. + * @param push_data whether to push data to the network from buffers + */ +static void +post_send_setopt (struct MHD_Connection *connection, + bool plain_send_next, + bool push_data) +{ + /* Try to buffer data if not sending the final piece. + * Final piece is indicated by push_data == true. */ + const bool buffer_data = (! push_data); + + if (mhd_T_IS_YES (connection->is_nonip)) + return; + if (buffer_data) + return; /* Nothing to do after the send(). */ + +#ifndef mhd_USE_MSG_MORE + (void) plain_send_next; /* Mute compiler warning */ +#endif /* ! mhd_USE_MSG_MORE */ + + /* Need to push data. */ +#ifdef mhd_TCP_CORK_NOPUSH + if (mhd_T_IS_YES (connection->sk_nodelay) && \ + mhd_T_IS_NO (connection->sk_corked)) + return; /* Data has been already pushed by last send(). */ + +# ifdef mhd_CORK_RESET_PUSH_DATA_ALWAYS +# ifdef mhd_NODELAY_SET_PUSH_DATA_ALWAYS +# ifdef mhd_USE_MSG_MORE + /* This is Linux kernel. + * The socket is corked (or unknown) or 'no delay' is not set (or unknown). + * There are options: + * * Push the data by setting of TCP_NODELAY (without change + * of the cork on the socket), + * * Push the data by resetting of TCP_CORK. + * The optimal choice depends on the next final send functions + * used on the same socket. + * + * In general on Linux kernel TCP_NODELAY always enabled is preferred, + * as buffering is controlled by MSG_MORE or cork/uncork. + * + * If next send function will not support MSG_MORE (like sendfile() + * or TLS-connection) than push data by setting TCP_NODELAY + * so the socket may remain corked (no additional sys-call before + * next send()). + * + * If send()/sendmsg() will be used next than push data by + * resetting of TCP_CORK so next final send without MSG_MORE will push + * data to the network (without additional sys-call to push data). */ + + if (mhd_T_IS_NOT_YES (connection->sk_nodelay) || + (! plain_send_next)) + { + if (mhd_connection_set_nodelay_state (connection, true)) + return; /* Data has been pushed by TCP_NODELAY. */ + /* Failed to set TCP_NODELAY for the socket. + * Really unlikely to happen on TCP connections. */ + if (mhd_connection_set_cork_state (connection, false)) + return; /* Data has been pushed by uncorking the socket. */ + /* Failed to uncork the socket. + * Really unlikely to happen on TCP connections. */ + + /* The socket cannot be uncorked, no way to push data */ + } + else + { + if (mhd_connection_set_cork_state (connection, false)) + return; /* Data has been pushed by uncorking the socket. */ + /* Failed to uncork the socket. + * Really unlikely to happen on TCP connections. */ + if (mhd_connection_set_nodelay_state (connection, true)) + return; /* Data has been pushed by TCP_NODELAY. */ + /* Failed to set TCP_NODELAY for the socket. + * Really unlikely to happen on TCP connections. */ + + /* The socket cannot be uncorked, no way to push data */ + } +# else /* ! mhd_USE_MSG_MORE */ + /* Push data by setting TCP_NODELAY here as uncorking here + * would require corking the socket before sending the next response. */ + if (mhd_connection_set_nodelay_state (connection, true)) + return; /* Data was pushed by TCP_NODELAY. */ + /* Failed to set TCP_NODELAY for the socket. + * Really unlikely to happen on TCP connections. */ + if (mhd_connection_set_cork_state (connection, false)) + return; /* Data was pushed by uncorking the socket. */ + /* Failed to uncork the socket. + * Really unlikely to happen on TCP connections. */ + + /* The socket remains corked, no way to push data */ +# endif /* ! mhd_USE_MSG_MORE */ +# else /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ + if (mhd_connection_set_cork_state (connection, false)) + return; /* Data was pushed by uncorking the socket. */ + /* Failed to uncork the socket. + * Really unlikely to happen on TCP connections. */ + + /* Socket remains corked, no way to push data */ +# endif /* ! mhd_NODELAY_SET_PUSH_DATA_ALWAYS */ +# else /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ + /* This is old FreeBSD or Darwin kernel. */ + + if (mhd_T_IS_NO (connection->sk_corked)) + { + mhd_assert (mhd_T_IS_NOT_YES (connection->sk_nodelay)); + + /* Unlikely to reach this code. + * TCP_NODELAY should be turned on before send(). */ + if (mhd_connection_set_nodelay_state (connection, true)) + { + /* TCP_NODELAY has been set on uncorked socket. + * Use zero-send to push the data. */ + if (zero_send (connection)) + return; /* The data has been pushed by zero-send. */ + } + + /* Failed to push the data by all means. */ + /* There is nothing left to try. */ + } + else + { +#ifdef mhd_CORK_RESET_PUSH_DATA + enum mhd_Tristate old_cork_state = connection->sk_corked; +#endif /* mhd_CORK_RESET_PUSH_DATA */ + /* The socket is corked or cork state is unknown. */ + + if (mhd_connection_set_cork_state (connection, false)) + { +#ifdef mhd_CORK_RESET_PUSH_DATA + /* Modern FreeBSD or OpenBSD kernel */ + if (mhd_T_IS_YES (old_cork_state)) + return; /* Data has been pushed by uncorking the socket. */ +#endif /* mhd_CORK_RESET_PUSH_DATA */ + + /* Unlikely to reach this code. + * The data should be pushed by uncorking (FreeBSD) or + * the socket should be uncorked before send(). */ + if (mhd_T_IS_YES (connection->sk_nodelay) || + (mhd_connection_set_nodelay_state (connection, true))) + { + /* TCP_NODELAY is turned ON on uncorked socket. + * Use zero-send to push the data. */ + if (zero_send (connection)) + return; /* The data has been pushed by zero-send. */ + } + } + /* Data cannot be pushed. */ + } +#endif /* ! mhd_CORK_RESET_PUSH_DATA_ALWAYS */ +#else /* ! mhd_TCP_CORK_NOPUSH */ + /* Corking is not supported. Buffering is controlled + * by TCP_NODELAY only. */ + mhd_assert (mhd_T_IS_NOT_YES (connection->sk_corked)); + if (mhd_T_IS_YES (connection->sk_nodelay)) + return; /* Data was already pushed by send(). */ + + /* Unlikely to reach this code. + * TCP_NODELAY should be turned on before send(). */ + if (mhd_connection_set_nodelay_state (connection, true)) + { + /* TCP_NODELAY has been set. + * Use zero-send to try to push the data. */ + if (zero_send (connection)) + return; /* The data has been pushed by zero-send. */ + } + + /* Failed to push the data. */ +#endif /* ! mhd_TCP_CORK_NOPUSH */ + mhd_LOG_MSG (connection->daemon, MHD_SC_SOCKET_FLUSH_LAST_PART_FAILED, \ + "Failed to force flush the last part of the response header " \ + "or the response content that might have been buffered by " \ + "the kernel. The client may experience some delay (usually " \ + "in range 200ms - 5 sec)."); + return; +} + + +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (5) enum mhd_SocketError +mhd_plain_send (struct MHD_Connection *restrict c, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + bool push_data, + size_t *restrict sent) +{ + /* plaintext transmission */ + ssize_t res; + bool full_buf_sent; + + if (buf_size > MHD_SCKT_SEND_MAX_SIZE_) + { + buf_size = MHD_SCKT_SEND_MAX_SIZE_; /* send() return value limit */ + push_data = false; /* Incomplete send */ + } + + pre_send_setopt (c, true, push_data); +#ifdef mhd_USE_MSG_MORE + res = mhd_sys_send4 (c->socket_fd, + buf, + buf_size, + push_data ? 0 : MSG_MORE); +#else + res = mhd_sys_send4 (c->socket_fd, + buf, + buf_size, + 0); +#endif + + if (0 >= res) + { + enum mhd_SocketError err; + + err = mhd_socket_error_get_from_sys_err (mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + + return err; + } + *sent = (size_t) res; + + full_buf_sent = (buf_size == (size_t) res); + + if (! full_buf_sent) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + + /* If there is a need to push the data from network buffers + * call post_send_setopt(). */ + /* It's unknown whether sendfile() (or other send function without + * MSG_MORE support) will be used for the next reply so assume + * that next sending will be the same, like this call. */ + if (push_data && full_buf_sent) + post_send_setopt (c, false, push_data); + + return mhd_SOCKET_ERR_NO_ERROR; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (5) enum mhd_SocketError +mhd_send_data (struct MHD_Connection *restrict connection, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + bool push_data, + size_t *restrict sent) +{ + const bool tls_conn = false; // TODO: TLS support + + mhd_assert (MHD_INVALID_SOCKET != connection->socket_fd); + mhd_assert (MHD_CONNECTION_CLOSED != connection->state); + + if (tls_conn) + { + enum mhd_SocketError ret; + +#ifdef HTTPS_SUPPORT + pre_send_setopt (connection, + (! tls_conn), + push_data); + ret = mhd_SOCKET_ERR_OTHER; + mhd_assert (0 && "Not implemented yet"); +#else /* ! HTTPS_SUPPORT */ + ret = mhd_SOCKET_ERR_NOTCONN; +#endif /* ! HTTPS_SUPPORT */ + return ret; + } + + return mhd_plain_send (connection, + buf_size, + buf, + push_data, + sent); +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3) +MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (6,5) enum mhd_SocketError +mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, + size_t header_size, + const char *restrict header, + bool never_push_hdr, + size_t body_size, + const char *restrict body, + bool complete_response, + size_t *restrict sent) +{ + mhd_iov_ret_type res; + bool send_error; + bool push_hdr; + bool push_body; + MHD_Socket s = connection->socket_fd; +#ifdef mhd_USE_VECT_SEND +#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) + struct iovec vector[2]; +#ifdef HAVE_SENDMSG + struct msghdr msg; +#endif /* HAVE_SENDMSG */ +#endif /* HAVE_SENDMSG || HAVE_WRITEV */ +#ifdef _WIN32 + WSABUF vector[2]; + DWORD vec_sent; +#endif /* _WIN32 */ + bool no_vec; /* Is vector-send() disallowed? */ + + no_vec = false; +#ifdef HTTPS_SUPPORT + no_vec = no_vec || (false); // TODO: TLS support +#endif /* HTTPS_SUPPORT */ +#if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL) ) && \ + defined(mhd_SEND_SPIPE_SUPPRESS_POSSIBLE) && \ + defined(mhd_SEND_SPIPE_SUPPRESS_NEEDED) + no_vec = no_vec || (! connection->daemon->sigpipe_blocked && + ! connection->sk_spipe_suppress); +#endif /* (!HAVE_SENDMSG || ! MSG_NOSIGNAL) && + mhd_SEND_SPIPE_SUPPRESS_POSSIBLE && + mhd_SEND_SPIPE_SUPPRESS_NEEDED */ +#endif /* mhd_USE_VECT_SEND */ + + mhd_assert ( (NULL != body) || (0 == body_size) ); + + mhd_assert (MHD_INVALID_SOCKET != s); + mhd_assert (MHD_CONNECTION_CLOSED != connection->state); + + push_body = complete_response; + + if (! never_push_hdr) + { + if (! complete_response) + push_hdr = true; /* Push the header as the client may react + * on header alone while the body data is + * being prepared. */ + else + { + if (1400 > (header_size + body_size)) + push_hdr = false; /* Do not push the header as complete + * reply is already ready and the whole + * reply most probably will fit into + * the single IP packet. */ + else + push_hdr = true; /* Push header alone so client may react + * on it while reply body is being delivered. */ + } + } + else + push_hdr = false; + + if (complete_response && (0 == body_size)) + push_hdr = true; /* The header alone is equal to the whole response. */ + +#ifndef mhd_USE_VECT_SEND + no_vec = (no_vec || true); +#else /* mhd_USE_VECT_SEND */ + no_vec = (no_vec || (0 == body_size)); + no_vec = (no_vec || ((sizeof(mhd_iov_elmn_size) <= sizeof(size_t)) && + (((size_t) mhd_IOV_ELMN_MAX_SIZE) < header_size))); +#endif /* mhd_USE_VECT_SEND */ + + + if (no_vec) + { + enum mhd_SocketError ret; + ret = mhd_send_data (connection, + header_size, + header, + push_hdr, + sent); + + // TODO: check 'send-ready' + if ((mhd_SOCKET_ERR_NO_ERROR == ret) && + (header_size == *sent) && + (0 != body_size) && + (header_size < header_size + body_size) && + (connection->sk_nonblck)) + { + size_t sent_b; + /* The header has been sent completely. + * Try to send the reply body without waiting for + * the next round. */ + + ret = mhd_send_data (connection, + body_size, + body, + push_body, + &sent_b); + + if (mhd_SOCKET_ERR_NO_ERROR == ret) + *sent += sent_b; + else if (mhd_SOCKET_ERR_IS_HARD (ret)) + return ret; /* Unrecoverable error */ + + return mhd_SOCKET_ERR_NO_ERROR; /* The header has been sent successfully */ + } + return ret; + } +#ifdef mhd_USE_VECT_SEND + + if (header_size > (header_size + body_size)) + { + /* Return value limit */ + body_size = SIZE_MAX - header_size; + complete_response = false; + push_body = complete_response; + } + if (((mhd_iov_ret_type) (header_size + body_size)) < 0 || + ((size_t) (mhd_iov_ret_type) (header_size + body_size)) != + (header_size + body_size)) + { + /* Send sys-call total amount limit */ + body_size = mhd_IOV_RET_MAX_SIZE - header_size; + complete_response = false; + push_body = complete_response; + } + + pre_send_setopt (connection, +#ifdef HAVE_SENDMSG + true, +#else /* ! HAVE_SENDMSG */ + false, +#endif /* ! HAVE_SENDMSG */ + push_hdr || push_body); + send_error = false; +#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) + vector[0].iov_base = mhd_DROP_CONST (header); + vector[0].iov_len = header_size; + vector[1].iov_base = mhd_DROP_CONST (body); + vector[1].iov_len = body_size; + +#if defined(HAVE_SENDMSG) + memset (&msg, 0, sizeof(msg)); + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = vector; + msg.msg_iovlen = 2; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + res = sendmsg (s, &msg, mhd_MSG_NOSIGNAL + | ((push_hdr || push_body) ? 0 : mhd_MSG_MORE)); +#elif defined(HAVE_WRITEV) + res = writev (s, vector, 2); +#endif /* HAVE_WRITEV */ + if (0 < res) + *sent = (size_t) res; + else + send_error = true; +#endif /* HAVE_SENDMSG || HAVE_WRITEV */ +#ifdef _WIN32 + if (((mhd_iov_elmn_size) body_size) != body_size) + { + /* Send item size limit */ + body_size = mhd_IOV_ELMN_MAX_SIZE; + complete_response = false; + push_body = complete_response; + } + vector[0].buf = (char *) mhd_DROP_CONST (header); + vector[0].len = (unsigned long) header_size; + vector[1].buf = (char *) mhd_DROP_CONST (body); + vector[1].len = (unsigned long) body_size; + + res = WSASend (s, vector, 2, &vec_sent, 0, NULL, NULL); + if (0 == res) + *sent = (size_t) vec_sent; + else + send_error = true; +#endif /* _WIN32 */ + + if (send_error) + { + enum mhd_SocketError err; + + err = mhd_socket_error_get_from_sys_err (mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + + return err; + } + if ((header_size + body_size) > *sent) + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + + /* If there is a need to push the data from network buffers + * call post_send_setopt(). */ + if ( (push_body) && + ((header_size + body_size) == *sent) ) + { + /* Complete reply has been sent. */ + /* If TLS connection is used then next final send() will be + * without MSG_MORE support. If non-TLS connection is used + * it's unknown whether next 'send' will be plain send() / sendmsg() or + * sendfile() will be used so assume that next final send() will be + * the same, like for this response. */ + post_send_setopt (connection, +#ifdef HAVE_SENDMSG + true, /* Assume the same type of the send function */ +#else /* ! HAVE_SENDMSG */ + false, /* Assume the same type of the send function */ +#endif /* ! HAVE_SENDMSG */ + true); + } + else if ( (push_hdr) && + (header_size <= *sent)) + { + /* The header has been sent completely and there is a + * need to push the header data. */ + /* Luckily the type of send function will be used next is known. */ + post_send_setopt (connection, + true, + true); + } + + return mhd_SOCKET_ERR_NO_ERROR; +#else /* ! mhd_USE_VECT_SEND */ + mhd_assert (0 && "Should be unreachable"); + return mhd_SOCKET_ERR_INTERNAL; /* Unreachable. Mute warnings. */ +#endif /* ! mhd_USE_VECT_SEND */ +} + + +#if defined(MHD_USE_SENDFILE) + +#if defined(HAVE_LINUX_SENDFILE) && defined(HAVE_SENDFILE64) +# define mhd_off_t off64_t +#else +# define mhd_off_t off_t +#endif + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (2) enum mhd_SocketError +mhd_send_sendfile (struct MHD_Connection *restrict c, + size_t *restrict sent) +{ + enum mhd_SocketError ret; + const bool used_thr_p_c = + mhd_D_HAS_THR_PER_CONN (c->daemon); + const size_t chunk_size = + used_thr_p_c ? + mhd_SENFILE_CHUNK_SIZE_FOR_THR_P_C : mhd_SENFILE_CHUNK_SIZE; + const int file_fd = c->rp.response->cntn.file.fd; + mhd_off_t offset; + size_t send_size; + size_t sent_bytes; + bool push_data; + bool fallback_to_filereader; + mhd_assert (mhd_REPLY_CNTN_LOC_FILE == c->rp.cntn_loc); + mhd_assert (MHD_SIZE_UNKNOWN != c->rp.response->cntn_size); + mhd_assert (chunk_size <= (size_t) SSIZE_MAX); + // mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS)); // TODO: TLS support + + send_size = 0; + push_data = true; + if (1) + { + bool too_large; + uint_fast64_t left; + + offset = (mhd_off_t) + (c->rp.rsp_cntn_read_pos + c->rp.response->cntn.file.offset); + too_large = (((uint_fast64_t) offset) < c->rp.rsp_cntn_read_pos); + too_large = too_large || + (((uint_fast64_t) offset) != + (c->rp.rsp_cntn_read_pos + c->rp.response->cntn.file.offset)); + too_large = too_large || (0 > offset); + if (too_large) + { /* Retry to send with file reader and standard 'send()'. */ + c->rp.response->cntn.file.use_sf = false; + return mhd_SOCKET_ERR_INTR; + } + + left = c->rp.response->cntn_size - c->rp.rsp_cntn_read_pos; + + /* Do not allow system to stick sending on single fast connection: + * use 128KiB chunks (2MiB for thread-per-connection). */ + if (chunk_size < left) /* This also limit to SSIZE_MAX automatically */ + { + send_size = chunk_size; + push_data = false; /* No need to push data, there is more to send. */ + } + else + send_size = (size_t) left; + } + mhd_assert (0 != send_size); + + pre_send_setopt (c, false, push_data); + + sent_bytes = 0; + ret = mhd_SOCKET_ERR_NO_ERROR; + fallback_to_filereader = false; +#if defined(HAVE_LINUX_SENDFILE) + if (1) + { + ssize_t res; +#ifndef HAVE_SENDFILE64 + ret = sendfile (c->socket_fd, + file_fd, + &offset, + send_size); +#else /* HAVE_SENDFILE64 */ + res = sendfile64 (c->socket_fd, + file_fd, + &offset, + send_size); +#endif /* HAVE_SENDFILE64 */ + if (0 > res) + { + const int sk_err = mhd_SCKT_GET_LERR (); + + if ((EINVAL == sk_err) || + (EOVERFLOW == sk_err) || +#ifdef EIO + (EIO == sk_err) || +#endif +#ifdef EAFNOSUPPORT + (EAFNOSUPPORT == sk_err) || +#endif + (EOPNOTSUPP == sk_err)) + fallback_to_filereader = true; + else + ret = mhd_socket_error_get_from_sys_err (sk_err); + } + else + sent_bytes = (size_t) res; + } +#elif defined(HAVE_FREEBSD_SENDFILE) + if (1) + { + off_t sent_bytes_offt = 0; + int flags = 0; + bool sent_something = false; +#ifdef SF_FLAGS + flags = used_thr_p_c ? + freebsd_sendfile_flags_thd_p_c_ : freebsd_sendfile_flags_; +#endif /* SF_FLAGS */ + if (0 != sendfile (file_fd, + c->socket_fd, + offset, + send_size, + NULL, + &sent_bytes_offt, + flags)) + { + const int sk_err = mhd_SCKT_GET_LERR (); + + sent_something = + (((EAGAIN == sk_err) || (EBUSY == sk_err) || (EINTR == sk_err)) && + (0 != sent_bytes_offt)); + + if (! sent_something) + { + enum mhd_SocketError err; + if ((EINVAL == sk_err) || + (EIO == sk_err) || + (EOPNOTSUPP == sk_err)) + fallback_to_filereader = true; + else + ret = mhd_socket_error_get_from_sys_err (sk_err); + } + } + else + sent_something = true; + + if (sent_something) + { + mhd_assert (0 <= sent_bytes_offt); + mhd_assert (SIZE_MAX >= sent_bytes_offt); + sent_bytes = (size_t) sent_bytes_offt; + } + } +#elif defined(HAVE_DARWIN_SENDFILE) + if (1) + { + off_t len; + bool sent_something; + + sent_something = false; + len = (off_t) send_size; /* chunk always fit */ + + if (0 != sendfile (file_fd, + c->socket_fd, + offset, + &len, + NULL, + 0)) + { + const int sk_err = mhd_SCKT_GET_LERR (); + + sent_something = + ((EAGAIN == sk_err) || (EINTR == sk_err)) && + (0 != len); + + if (! sent_something) + { + enum mhd_SocketError err; + if ((ENOTSUP == sk_err) || + (EOPNOTSUPP == sk_err)) + fallback_to_filereader = true; + else + ret = mhd_socket_error_get_from_sys_err (sk_err); + } + } + else + sent_something = true; + + if (sent_something) + { + mhd_assert (0 <= len); + mhd_assert (SIZE_MAX >= len); + sent_bytes = (size_t) len; + } + } +#else +#error No sendfile() function +#endif + + mhd_assert (send_size >= sent_bytes); + + /* Some platforms indicate "beyond of the end of the file" by returning + * success with zero bytes. Let filereader to re-detect this kind of error. */ + if ((fallback_to_filereader) || + ((mhd_SOCKET_ERR_NO_ERROR == ret) && (0 == sent_bytes))) + { /* Retry to send with file reader and standard 'send()'. */ + c->rp.response->cntn.file.use_sf = false; + return mhd_SOCKET_ERR_INTR; + } + + if ((mhd_SOCKET_ERR_AGAIN == ret) || + ((mhd_SOCKET_ERR_NO_ERROR == ret) && (send_size > sent_bytes))) + c->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + + if (mhd_SOCKET_ERR_NO_ERROR != ret) + return ret; + + /* If there is a need to push the data from network buffers + * call post_send_setopt(). */ + /* It's unknown whether sendfile() will be used in the next + * response so assume that next response will be the same. */ + if ((push_data) && + (send_size == sent_bytes)) + post_send_setopt (c, true, push_data); + + *sent = sent_bytes; + return ret; +} + + +#endif /* MHD_USE_SENDFILE */ + +#if defined(mhd_USE_VECT_SEND) + + +/** + * Function sends iov data by system sendmsg or writev function. + * + * Connection must be in non-TLS (non-HTTPS) mode. + * + * @param connection the MHD connection structure + * @param r_iov the pointer to iov data structure with tracking + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +send_iov_nontls (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) +{ + bool send_error; + size_t items_to_send; +#ifndef MHD_WINSOCK_SOCKETS + ssize_t res; +#endif +#ifdef HAVE_SENDMSG + struct msghdr msg; +#elif defined(MHD_WINSOCK_SOCKETS) + DWORD bytes_sent; + DWORD cnt_w; +#endif /* MHD_WINSOCK_SOCKETS */ + + // TODO: assert for non-TLS + + mhd_assert (MHD_INVALID_SOCKET != connection->socket_fd); + mhd_assert (MHD_CONNECTION_CLOSED != connection->state); + + send_error = false; + items_to_send = r_iov->cnt - r_iov->sent; +#ifdef mhd_IOV_MAX + if (mhd_IOV_MAX < items_to_send) + { + mhd_assert (0 < mhd_IOV_MAX); + if (0 == mhd_IOV_MAX) + return mhd_SOCKET_ERR_INTERNAL; /* Should never happen */ + items_to_send = mhd_IOV_MAX; + push_data = false; /* Incomplete response */ + } +#endif /* mhd_IOV_MAX */ +#ifdef HAVE_SENDMSG + memset (&msg, 0, sizeof(struct msghdr)); + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = r_iov->iov + r_iov->sent; + msg.msg_iovlen = items_to_send; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + pre_send_setopt (connection, true, push_data); + res = sendmsg (connection->socket_fd, &msg, + mhd_MSG_NOSIGNAL | (push_data ? 0 : mhd_MSG_MORE)); + if (0 < res) + *sent = (size_t) res; + else + send_error = true; +#elif defined(HAVE_WRITEV) + pre_send_setopt (connection, false, push_data); + res = writev (connection->socket_fd, r_iov->iov + r_iov->sent, + items_to_send); + if (0 < res) + *sent = (size_t) res; + else + send_error = true; +#elif defined(MHD_WINSOCK_SOCKETS) +#ifdef _WIN64 + if (items_to_send > ULONG_MAX) + { + cnt_w = ULONG_MAX; + push_data = false; /* Incomplete response */ + } + else + cnt_w = (DWORD) items_to_send; +#else /* ! _WIN64 */ + cnt_w = (DWORD) items_to_send; +#endif /* ! _WIN64 */ + pre_send_setopt (connection, true, push_data); + if (0 == WSASend (connection->socket_fd, + (LPWSABUF) (r_iov->iov + r_iov->sent), + cnt_w, + &bytes_sent, 0, NULL, NULL)) + *sent = (size_t) bytes_sent; + else + send_error = true; +#else /* !HAVE_SENDMSG && !HAVE_WRITEV && !MHD_WINSOCK_SOCKETS */ +#error No vector-send function available +#endif + + if (send_error) + { + enum mhd_SocketError err; + + err = mhd_socket_error_get_from_sys_err (mhd_SCKT_GET_LERR ()); + + if (mhd_SOCKET_ERR_AGAIN == err) + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + + return err; + } + + /* Some data has been sent */ + if (1) + { + size_t track_sent = (size_t) *sent; + /* Adjust the internal tracking information for the iovec to + * take this last send into account. */ + while ((0 != track_sent) && (r_iov->iov[r_iov->sent].iov_len <= track_sent)) + { + track_sent -= r_iov->iov[r_iov->sent].iov_len; + r_iov->sent++; /* The iov element has been completely sent */ + mhd_assert ((r_iov->cnt > r_iov->sent) || (0 == track_sent)); + } + + if (r_iov->cnt == r_iov->sent) + post_send_setopt (connection, true, push_data); + else + { + connection->sk_ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk_ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + if (0 != track_sent) + { + mhd_assert (r_iov->cnt > r_iov->sent); + /* The last iov element has been partially sent */ + r_iov->iov[r_iov->sent].iov_base = + (void *) ((uint8_t *) r_iov->iov[r_iov->sent].iov_base + track_sent); + r_iov->iov[r_iov->sent].iov_len -= (mhd_iov_elmn_size) track_sent; + } + } + } + + return mhd_SOCKET_ERR_NO_ERROR; +} + + +#endif /* mhd_USE_VECT_SEND */ + +#if ! defined(mhd_USE_VECT_SEND) || defined(HTTPS_SUPPORT) || \ + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + + +/** + * Function sends iov data by sending buffers one-by-one by standard + * data send function. + * + * Connection could be in HTTPS or non-HTTPS mode. + * + * @param connection the MHD connection structure + * @param r_iov the pointer to iov data structure with tracking + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +send_iov_emu (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) +{ + const bool non_blk = connection->sk_nonblck; + size_t total_sent; + size_t max_elelements_to_sent; + + mhd_assert (NULL != r_iov->iov); + total_sent = 0; + max_elelements_to_sent = 8; /* Do not make too many sys-calls for just one connection */ + do + { + enum mhd_SocketError res; + size_t sent_el_size; + + if (total_sent > (size_t) (r_iov->iov[r_iov->sent].iov_len + total_sent)) + break; /* return value would overflow */ + + res = mhd_send_data (connection, + r_iov->iov[r_iov->sent].iov_len, + r_iov->iov[r_iov->sent].iov_base, + push_data && (r_iov->cnt == r_iov->sent + 1), + &sent_el_size); + if (mhd_SOCKET_ERR_NO_ERROR == res) + { + /* Result is an error */ + if (0 == total_sent) + return res; /* Nothing was sent, return error as is */ + + if (mhd_SOCKET_ERR_IS_HARD (res)) + return res; /* Any kind of a hard error */ + + break; /* Return the amount of the sent data */ + } + + total_sent += sent_el_size; + + if (r_iov->iov[r_iov->sent].iov_len != sent_el_size) + { + /* Incomplete buffer has been sent. + * Adjust buffer of the last element. */ + r_iov->iov[r_iov->sent].iov_base = + (void *) ((uint8_t *) r_iov->iov[r_iov->sent].iov_base + sent_el_size); + r_iov->iov[r_iov->sent].iov_len -= (mhd_iov_elmn_size) sent_el_size; + + break; /* Return the amount of the sent data */ + } + /* The iov element has been completely sent */ + r_iov->sent++; + } while ((r_iov->cnt > r_iov->sent) && 0 != (--max_elelements_to_sent) && + (non_blk)); + + mhd_assert (0 != total_sent); + *sent = total_sent; + return mhd_SOCKET_ERR_NO_ERROR; +} + + +#endif /* !mhd_USE_VECT_SEND || HTTPS_SUPPORT + || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_send_iovec (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) +{ +#ifdef mhd_USE_VECT_SEND +#if defined(HTTPS_SUPPORT) || \ + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + bool use_iov_send = true; +#endif /* HTTPS_SUPPORT || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* mhd_USE_VECT_SEND */ + + mhd_assert (NULL != connection->rp.resp_iov.iov); + mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == \ + connection->rp.response->cntn_dtype); + mhd_assert (connection->rp.resp_iov.cnt > connection->rp.resp_iov.sent); +#ifdef mhd_USE_VECT_SEND +#if defined(HTTPS_SUPPORT) || \ + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) +#ifdef HTTPS_SUPPORT + use_iov_send = use_iov_send && + (true); // TODO: TLS support +#endif /* HTTPS_SUPPORT */ +#ifdef mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED + use_iov_send = use_iov_send && (connection->daemon->sigpipe_blocked || + connection->sk_spipe_suppress); +#endif /* mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ + if (use_iov_send) +#endif /* HTTPS_SUPPORT || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ + return send_iov_nontls (connection, r_iov, push_data, sent); +#endif /* mhd_USE_VECT_SEND */ + +#if ! defined(mhd_USE_VECT_SEND) || defined(HTTPS_SUPPORT) || \ + defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + return send_iov_emu (connection, r_iov, push_data, sent); +#endif /* !mhd_USE_VECT_SEND || HTTPS_SUPPORT + || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +} diff --git a/src/mhd2/mhd_send.h b/src/mhd2/mhd_send.h @@ -0,0 +1,174 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2017-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2019 ng0 + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_send.h + * @brief Declarations of send() wrappers. + * @author Karlson2k (Evgeny Grin) + * @author ng0 (N. Gillmann) + */ + +#ifndef MHD_SEND_H +#define MHD_SEND_H + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "sys_bool_type.h" +#include "mhd_socket_error.h" + +struct MHD_Connection; /* forward declaration */ +struct mhd_iovec_track; /* forward declaration */ + +/** + * Initialises static variables + */ +void +mhd_send_init_static_vars (void); + + +/** + * Send buffer to the client, push data from network buffer if requested + * and full buffer is sent. + * + * @param connection the MHD_Connection structure + * @param buffer_size the size of the @a buffer (in bytes) + * @param buffer content of the buffer to send + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_send_data (struct MHD_Connection *restrict connection, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + bool push_data, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_ (5); + + +/** + * Send reply header with optional reply body. + * + * @param connection the MHD_Connection structure + * @param header content of header to send + * @param header_size the size of the @a header (in bytes) + * @param never_push_hdr set to true to disable internal algorithm + * that can push automatically header data + * alone to the network + * @param body content of the body to send (optional, may be NULL) + * @param body_size the size of the @a body (in bytes) + * @param complete_response set to true if complete response + * is provided by @a header and @a body, + * set to false if additional body data + * will be sent later + * @param[out] sent the pointer to get amount of actually sent bytes + * in total (from both buffers combined) + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, + size_t header_size, + const char *restrict header, + bool never_push_hdr, + size_t body_size, + const char *restrict body, + bool complete_response, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_(1) MHD_FN_PAR_NONNULL_(3) +MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (6,5) MHD_FN_PAR_OUT_ (8); + +#if defined(MHD_USE_SENDFILE) +/** + * Function for sending responses backed by file FD. + * + * @param connection the MHD connection structure + * @param[out] sent the pointer to get amount of actually sent bytes + * in total (from both buffers combined) + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2) enum mhd_SocketError +mhd_send_sendfile (struct MHD_Connection *restrict c, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_ALL_; + +#endif + + +/** + * Function for sending responses backed by a an array of memory buffers. + * + * @param connection the MHD connection structure + * @param r_iov the pointer to iov response structure with tracking + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @param[out] sent the pointer to get amount of actually sent bytes + * in total (from both buffers combined) + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_send_iovec (struct MHD_Connection *restrict connection, + struct mhd_iovec_track *const restrict r_iov, + bool push_data, + size_t *restrict sent) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (4); + +/** + * Set required TCP_NODELAY state for connection socket + * + * The function automatically updates sk_nodelay state. + * @param connection the connection to manipulate + * @param nodelay_state the requested new state of socket + * @return true if succeed, false if failed or not supported + * by the current platform / kernel. + */ +MHD_INTERNAL bool +mhd_connection_set_nodelay_state (struct MHD_Connection *connection, + bool nodelay_state) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Set required cork state for connection socket + * + * The function automatically updates sk_corked state. + * + * @param connection the connection to manipulate + * @param cork_state the requested new state of socket + * @return true if succeed, false if failed or not supported + * by the current platform / kernel. + */ +MHD_INTERNAL bool +mhd_connection_set_cork_state (struct MHD_Connection *connection, + bool cork_state) +MHD_FN_PAR_NONNULL_ALL_; + + +#endif /* MHD_SEND_H */ diff --git a/src/mhd2/mhd_socket_error.c b/src/mhd2/mhd_socket_error.c @@ -0,0 +1,86 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_socket_error.c + * @brief The definition of mhd_SocketError-related functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "mhd_socket_error.h" +#include "sys_sockets_headers.h" +#include "mhd_sockets_macros.h" +#include "sys_sockets_types.h" + +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_sys_err (int socket_err) +{ + if (mhd_SCKT_ERR_IS_EAGAIN (socket_err)) + return mhd_SOCKET_ERR_AGAIN; + else if (mhd_SCKT_ERR_IS_CONNRESET (socket_err)) + return mhd_SOCKET_ERR_CONNRESET; + else if (mhd_SCKT_ERR_IS_EINTR (socket_err)) + return mhd_SOCKET_ERR_INTR; + else if (mhd_SCKT_ERR_IS_CONN_BROKEN (socket_err)) + return mhd_SOCKET_ERR_CONN_BROKEN; + else if (mhd_SCKT_ERR_IS_PIPE (socket_err)) + return mhd_SOCKET_ERR_PIPE; + else if (mhd_SCKT_ERR_IS_NOTCONN (socket_err)) + return mhd_SOCKET_ERR_NOTCONN; + else if (mhd_SCKT_ERR_IS_LOW_MEM (socket_err)) + return mhd_SOCKET_ERR_NOMEM; + else if (mhd_SCKT_ERR_IS_BADF (socket_err)) + return mhd_SOCKET_ERR_BADF; + else if (mhd_SCKT_ERR_IS_EINVAL (socket_err)) + return mhd_SOCKET_ERR_INVAL; + else if (mhd_SCKT_ERR_IS_OPNOTSUPP (socket_err)) + return mhd_SOCKET_ERR_OPNOTSUPP; + else if (mhd_SCKT_ERR_IS_NOTSOCK (socket_err)) + return mhd_SOCKET_ERR_NOTSOCK; + + return mhd_SOCKET_ERR_OTHER; +} + + +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_socket (MHD_Socket fd) +{ +#if defined(SOL_SOCKET) && defined(SO_ERROR) + enum mhd_SocketError err; + int sock_err; + socklen_t optlen = sizeof (sock_err); + + sock_err = 0; + if ((0 == getsockopt (fd, + SOL_SOCKET, + SO_ERROR, + (void *) &sock_err, + &optlen)) + && (sizeof(sock_err) == optlen)) + return mhd_socket_error_get_from_sys_err (sock_err); + + err = mhd_socket_error_get_from_sys_err (mhd_SCKT_GET_LERR ()); + if ((mhd_SOCKET_ERR_NOTSOCK == err) || + (mhd_SOCKET_ERR_BADF == err)) + return err; +#endif /* SOL_SOCKET && SO_ERROR */ + return mhd_SOCKET_ERR_NOT_CHECKED; +} diff --git a/src/mhd2/mhd_socket_error.h b/src/mhd2/mhd_socket_error.h @@ -0,0 +1,161 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_socket_error.h + * @brief The definition of the mhd_SocketError enum and related macros and + * declarations of related functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SOCKET_ERROR_H +#define MHD_SOCKET_ERROR_H 1 + +#include "mhd_sys_options.h" +#include "mhd_socket_type.h" + +// TODO: better classification, when clearer local closing / network aborts +/** + * Recognised socket errors for recv() and send() + */ +enum MHD_FIXED_ENUM_ mhd_SocketError +{ + /** + * No error. + */ + mhd_SOCKET_ERR_NO_ERROR = 0 + , + /** + * No more data to get / no more space to put the data. + */ + mhd_SOCKET_ERR_AGAIN + , + /** + * The process has been interrupted by external factors. + */ + mhd_SOCKET_ERR_INTR + , + /** + * "Not enough memory" / "not enough system resources" + */ + 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. + */ + mhd_SOCKET_ERR_CONNRESET + , + /** + * Meta-error for any other errors indicating a broken connection. + * It can be keep-alive ping failure or timeout to get ACK for the + * transmitted data. + */ + mhd_SOCKET_ERR_CONN_BROKEN + , + /** + * Connection is not connected anymore due to network error or + * any other reason. + */ + mhd_SOCKET_ERR_NOTCONN + , + /** + * General TLS encryption or decryption error + */ + mhd_SOCKET_ERR_TLS + , + /** + * The socket has been shut down for writing or no longer connected + * Only for 'send()'. + */ + mhd_SOCKET_ERR_PIPE + , + /** + * The error status reported, but concrete code error has not been + * checked by MHD + */ + mhd_SOCKET_ERR_NOT_CHECKED + , + /** + * The socket FD is invalid + */ + mhd_SOCKET_ERR_BADF + , + /** + * Socket function parameters are invalid + */ + mhd_SOCKET_ERR_INVAL + , + /** + * Socket function parameters are not supported + */ + mhd_SOCKET_ERR_OPNOTSUPP + , + /** + * Used FD is not a socket + */ + mhd_SOCKET_ERR_NOTSOCK + , + /** + * Other socket error + */ + mhd_SOCKET_ERR_OTHER + , + /** + * Internal (MHD) error + * Not actually reported by the OS + */ + mhd_SOCKET_ERR_INTERNAL + +}; + +/** + * Check whether the socket error is unrecoverable + */ +#define mhd_SOCKET_ERR_IS_HARD(err) (mhd_SOCKET_ERR_REMT_DISCONN <= (err)) + +/** + * Check whether the socket error is unexpected + */ +#define mhd_SOCKET_ERR_IS_BAD(err) (mhd_SOCKET_ERR_BADF <= (err)) + +/** + * Map recv() / send() system socket error to the enum value + * @param socket_err the system socket error + * @return the enum value for the @a socket_err + */ +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_sys_err (int socket_err); + +/** + * Get the last socket error recoded for the given socket + * @param fd the socket to check for the error + * @return the recorded error @a fd, + * #mhd_SOCKET_ERR_NOT_CHECKED if not possible to check @a fd for + * the error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_socket_error_get_from_socket (MHD_Socket fd); + +#endif /* ! MHD_SOCKET_ERROR_H */ diff --git a/src/mhd2/mhd_socket_type.h b/src/mhd2/mhd_socket_type.h @@ -0,0 +1,61 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_sockets_types.h + * @brief The header for MHD_Socket types and relevant macros + * @author Karlson2k (Evgeny Grin) + * + * This header provides 'MHD_Socket' type and 'MHD_INVALID_SOCKET' value. + */ + +#ifndef MHD_SOCKET_TYPE_H +#define MHD_SOCKET_TYPE_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_INVALID_SOCKET +# if ! defined(_WIN32) || defined(_SYS_TYPES_FD_SET) +# define MHD_POSIX_SOCKETS 1 /* The POSIX-style sockets are used */ +/** + * MHD_Socket is type for socket FDs + * + * This type is always 'int' on POSIX platforms. + */ +typedef int MHD_Socket; +/** + * Invalid value for MHD_Socket + */ +# define MHD_INVALID_SOCKET (-1) +# else /* !defined(_WIN32) || defined(_SYS_TYPES_FD_SET) */ +# define MHD_WINSOCK_SOCKETS 1 /* The WinSock-style sockets are used */ +# include <winsock2.h> +/** + * MHD_Socket is type for socket FDs + */ +typedef SOCKET MHD_Socket; +/** + * Invalid value for MHD_Socket + */ +# define MHD_INVALID_SOCKET (INVALID_SOCKET) +# endif /* !defined(_WIN32) || defined(_SYS_TYPES_FD_SET) */ +#endif /* MHD_INVALID_SOCKET */ + +#endif /* ! MHD_SOCKET_TYPE_H */ diff --git a/src/mhd2/mhd_sockets_funcs.c b/src/mhd2/mhd_sockets_funcs.c @@ -0,0 +1,285 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Karlson2k (Evgeny Grin) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_sockets_funcs.c + * @brief Implementations of sockets manipulating functions + * @author Karlson2k (Evgeny Grin) + */ +#include "mhd_sys_options.h" +#include "sys_sockets_types.h" +#include "mhd_sockets_funcs.h" +#include "sys_sockets_headers.h" +#include "sys_ip_headers.h" +#ifdef MHD_POSIX_SOCKETS +# ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +# ifdef HAVE_UNISTD_H +# include <unistd.h> +# else +# include <stdlib.h> +# endif +# include <fcntl.h> +#elif defined(MHD_WINSOCK_SOCKETS) +# include <windows.h> +#endif +#ifndef INADDR_LOOPBACK +# include <string.h> /* For memcpy() */ +#endif + +#include "mhd_sockets_macros.h" + + +MHD_INTERNAL bool +mhd_socket_nonblocking (MHD_Socket sckt) +{ +#if defined(MHD_POSIX_SOCKETS) + // TODO: detect constants in configure +#if defined(F_GETFL) && defined(O_NONBLOCK) && defined(F_SETFL) + int get_flags; + int set_flags; + + get_flags = fcntl (sckt, F_GETFL); + if (0 > get_flags) + return false; + + set_flags = (get_flags | O_NONBLOCK); + if (get_flags == set_flags) + return true; + + if (-1 != fcntl (sckt, F_SETFL, set_flags)) + return true; +#endif /* F_GETFL && O_NONBLOCK && F_SETFL */ +#elif defined(MHD_WINSOCK_SOCKETS) + unsigned long set_flag = 1; + + if (0 == ioctlsocket (sckt, (long) FIONBIO, &set_flag)) + return true; +#endif /* MHD_WINSOCK_SOCKETS */ + + return false; +} + + +MHD_INTERNAL bool +mhd_socket_noninheritable (MHD_Socket sckt) +{ +#if defined(MHD_POSIX_SOCKETS) + // TODO: detect constants in configure +#if defined(F_GETFD) && defined(FD_CLOEXEC) && defined(F_SETFD) + int get_flags; + int set_flags; + + get_flags = fcntl (sckt, F_GETFD); + if (0 > get_flags) + return false; + + set_flags = (get_flags | FD_CLOEXEC); + if (get_flags == set_flags) + return true; + + if (-1 != fcntl (sckt, F_SETFD, set_flags)) + return true; +#endif /* F_GETFD && FD_CLOEXEC && F_SETFD */ +#elif defined(MHD_WINSOCK_SOCKETS) + if (SetHandleInformation ((HANDLE) sckt, HANDLE_FLAG_INHERIT, 0)) + return true; +#endif /* MHD_WINSOCK_SOCKETS */ + return false; +} + + +MHD_INTERNAL bool +mhd_socket_set_nodelay (MHD_Socket sckt, + bool on) +{ + // TODO: detect constants in configure +#ifdef TCP_NODELAY + mhd_SCKT_OPT_BOOL value; + + value = on ? 1 : 0; + + return 0 == setsockopt (sckt, IPPROTO_TCP, TCP_NODELAY, + (const void *) &value, sizeof (value)); +#else /* ! TCP_NODELAY */ + (void) sock; (void) on; + return false; +#endif /* ! TCP_NODELAY */ +} + + +MHD_INTERNAL bool +mhd_socket_set_hard_close (MHD_Socket sckt) +{ + // TODO: detect constants in configure +#if defined(SOL_SOCKET) && defined(SO_LINGER) + struct linger par; + + par.l_onoff = 1; + par.l_linger = 0; + + return 0 == setsockopt (sckt, SOL_SOCKET, SO_LINGER, + (const void *) &par, sizeof (par)); +#else /* ! TCP_NODELAY */ + (void) sock; + return false; +#endif /* ! TCP_NODELAY */ +} + + +MHD_INTERNAL bool +mhd_socket_shut_wr (MHD_Socket sckt) +{ +#if defined(SHUT_WR) // TODO: detect constants in configure + return 0 == shutdown (sckt, SHUT_WR); +#elif defined(SD_SEND) // TODO: detect constants in configure + return 0 == shutdown (sckt, SD_SEND); +#else + return false; +#endif +} + + +#ifndef HAVE_SOCKETPAIR + +static bool +mhd_socket_blocking (MHD_Socket sckt) +{ +#if defined(MHD_POSIX_SOCKETS) + // TODO: detect constants in configure +#if defined(F_GETFL) && defined(O_NONBLOCK) && defined(F_SETFL) + int get_flags; + int set_flags; + + get_flags = fcntl (sckt, F_GETFL); + if (0 > get_flags) + return false; + + set_flags = (flags & ~O_NONBLOCK); + if (get_flags == set_flags) + return true; + + if (-1 != fcntl (sckt, F_SETFL, set_flags)) + return true; +#endif /* F_GETFL && O_NONBLOCK && F_SETFL */ +#elif defined(MHD_WINSOCK_SOCKETS) + unsigned long set_flag = 0; + + if (0 == ioctlsocket (sckt, (long) FIONBIO, &set_flag)) + return true; +#endif /* MHD_WINSOCK_SOCKETS */ + + return false; +} + + +MHD_INTERNAL bool +mhd_socket_pair_func (MHD_Socket sckt[2], bool non_blk) +{ + int i; + +#define PAIR_MAX_TRIES 511 + for (i = 0; i < PAIR_MAX_TRIES; i++) + { + struct sockaddr_in listen_addr; + MHD_Socket listen_s; + static const socklen_t c_addinlen = sizeof(struct sockaddr_in); /* Try to help compiler to optimise */ + socklen_t addr_len = c_addinlen; + + listen_s = socket (AF_INET, + SOCK_STREAM, + IPPROTO_TCP); + if (INVALID_SOCKET == listen_s) + break; /* can't create even single socket */ + + listen_addr.sin_family = AF_INET; + listen_addr.sin_port = 0; /* same as htons(0) */ +#ifdef INADDR_LOOPBACK + listen_addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); +#else + memcpy (&(listen_addr.sin_addr.s_addr), "\x7F\x00\x00\x01", 4); +#endif + if ( ((0 == bind (listen_s, + (struct sockaddr *) &listen_addr, + c_addinlen)) && + (0 == listen (listen_s, + 1) ) && + (0 == getsockname (listen_s, + (struct sockaddr *) &listen_addr, + &addr_len))) ) + { + MHD_Socket client_s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + struct sockaddr_in accepted_from_addr; + struct sockaddr_in client_addr; + + if (INVALID_SOCKET != client_s) + { + if (mhd_socket_nonblocking (client_s) && + ( (0 == connect (client_s, + (struct sockaddr *) &listen_addr, + c_addinlen)) || + mhd_SCKT_LERR_IS_EAGAIN () )) + { + MHD_Socket server_s; + + addr_len = c_addinlen; + server_s = accept (listen_s, + (struct sockaddr *) &accepted_from_addr, + &addr_len); + if (MHD_INVALID_SOCKET != server_s) + { + addr_len = c_addinlen; + if ( (0 == getsockname (client_s, + (struct sockaddr *) &client_addr, + &addr_len)) && + (accepted_from_addr.sin_port == client_addr.sin_port) && + (accepted_from_addr.sin_addr.s_addr == + client_addr.sin_addr.s_addr) ) + { + (void) mhd_socket_set_nodelay (server_s, true); + (void) mhd_socket_set_nodelay (client_s, true); + if (non_blk ? + mhd_socket_nonblocking (server_s) : + mhd_socket_blocking (client_s)) + { + mhd_socket_close (listen_s); + sckt[0] = server_s; + sckt[1] = client_s; + return true; + } + } + mhd_socket_close (server_s); + } + } + mhd_socket_close (client_s); + } + } + mhd_socket_close (listen_s); + } + + sckt[0] = INVALID_SOCKET; + sckt[1] = INVALID_SOCKET; + + return false; +} + + +#endif /* ! HAVE_SOCKETPAIR */ diff --git a/src/mhd2/mhd_sockets_funcs.h b/src/mhd2/mhd_sockets_funcs.h @@ -0,0 +1,104 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Karlson2k (Evgeny Grin) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_sockets_funcs.h + * @brief Declarations for sockets manipulating functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SOCKETS_FUNCS_H +#define MHD_SOCKETS_FUNCS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "mhd_socket_type.h" + + +/** + * Change socket options to be non-blocking. + * + * @param sckt the socket to manipulate + * @return true on success, false otherwise + */ +MHD_INTERNAL bool +mhd_socket_nonblocking (MHD_Socket sckt); + +/** + * Change socket options to be non-inheritable. + * + * @param sock the socket to manipulate + * @return true on success, false otherwise + */ +MHD_INTERNAL bool +mhd_socket_noninheritable (MHD_Socket sckt); + +/** + * Change socket options to be closed "hard". + * + * @param sock the socket to manipulate + * @return true on success, false otherwise + */ +MHD_INTERNAL bool +mhd_socket_set_hard_close (MHD_Socket sckt); + +/** + * Shutdown sending on socket + * + * @param sock the socket to manipulate + * @return true on success, false otherwise + */ +MHD_INTERNAL bool +mhd_socket_shut_wr (MHD_Socket sckt); + +/** + * Control Nagle's algorithm on @a sock. + * + * @param sckt the socket to manipulate + * @param on the value to use: true to set "no delay" (disable Nagle's + * algorithm), false to clear "no delay" (enable Nagle's algorithm) + * @return true on success, false otherwise + */ +MHD_INTERNAL bool +mhd_socket_set_nodelay (MHD_Socket sckt, + bool on); + + +#ifndef HAVE_SOCKETPAIR + +# define mhd_socket_pair(fdarr_ptr) mhd_socket_pair_func (fdarr_ptr, false) +# define mhd_socket_pair_nblk(fdarr_ptr) mhd_socket_pair_func (fdarr_ptr, true) + + +/** + * Create pair of mutually connected sockets on loopback address + * @param sockets_pair the array to receive resulted sockets + * @param non_blk if set to true, sockets created in non-blocking mode + * otherwise sockets will be in blocking mode + * @return true if succeeded, false otherwise + */ +MHD_INTERNAL bool +mhd_socket_pair_func (MHD_Socket sckt[2], bool non_blk); + +#endif /* ! HAVE_SOCKETPAIR */ + + +#endif /* ! MHD_SOCKETS_FUNCS_H */ diff --git a/src/mhd2/mhd_sockets_macros.h b/src/mhd2/mhd_sockets_macros.h @@ -0,0 +1,339 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Karlson2k (Evgeny Grin) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_sockets_macros.h + * @brief Various helper macros functions related to sockets + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SOCKETS_MACROS_H +#define MHD_SOCKETS_MACROS_H 1 + +#include "mhd_sys_options.h" + +#include "mhd_socket_type.h" +#include "sys_base_types.h" +#include "sys_sockets_headers.h" + +#if defined(MHD_POSIX_SOCKETS) +# include "sys_errno.h" +# ifdef HAVE_UNISTD_H +# include <unistd.h> +# else +# include <stdlib.h> +# endif +# include "sys_errno.h" +#elif defined(MHD_WINSOCK_SOCKETS) +# include <winsock2.h> +#endif + +/** + * Close the socket FD + * @param sckt the socket to close + */ +#if defined(MHD_POSIX_SOCKETS) +# define mhd_socket_close(sckt) close (sckt) +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_socket_close(sckt) closesocket (sckt) +#endif + +/** + * mhd_sys_send4 is a wrapper for system's send() + * @param s the socket to use + * @param b the buffer with data to send + * @param l the length of data in @a b + * @param f the additional flags + * @return ssize_t type value + */ +#define mhd_sys_send4(s,b,l,f) \ + ((ssize_t) send ((s),(const void*) (b),(mhd_SCKT_SEND_SIZE) (l), \ + ((mhd_MSG_NOSIGNAL) | (f)))) + + +/** + * mhd_sys_send is a simple wrapper for system's send() + * @param s the socket to use + * @param b the buffer with data to send + * @param l the length of data in @a b + * @return ssize_t type value + */ +#define mhd_sys_send(s,b,l) mhd_sys_send4 ((s),(b),(l), 0) + + +/** + * mhd_recv is wrapper for system's recv() + * @param s the socket to use + * @param b the buffer for data to receive + * @param l the length of @a b + * @return ssize_t type value + */ +#define mhd_sys_recv(s,b,l) \ + ((ssize_t) recv ((s),(void*) (b),(mhd_SCKT_SEND_SIZE) (l), 0)) + +/** + * Last socket error + */ +#if defined(MHD_POSIX_SOCKETS) +# define mhd_SCKT_GET_LERR() errno +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_GET_LERR() (WSAGetLastError ()) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# if defined(EAGAIN) && defined(EWOULDBLOCK) && \ + ((EWOULDBLOCK + 0) != (EAGAIN + 0)) +# define mhd_SCKT_ERR_IS_EAGAIN(err) \ + ((EAGAIN == (err)) || (EWOULDBLOCK == (err))) +# elif defined(EAGAIN) +# define mhd_SCKT_ERR_IS_EAGAIN(err) (EAGAIN == (err)) +# elif defined(EWOULDBLOCK) +# define mhd_SCKT_ERR_IS_EAGAIN(err) (EWOULDBLOCK == (err)) +# else +# define mhd_SCKT_ERR_IS_EAGAIN(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_EAGAIN(err) (WSAEWOULDBLOCK == (err)) +#endif + +#define mhd_SCKT_LERR_IS_EAGAIN() mhd_SCKT_ERR_IS_EAGAIN (mhd_SCKT_GET_LERR ()) + +#if defined(MHD_POSIX_SOCKETS) +# ifdef EAFNOSUPPORT +# define mhd_SCKT_ERR_IS_AF(err) (EAFNOSUPPORT == (err)) +# else +# define mhd_SCKT_ERR_IS_AF(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_AF(err) (WSAEAFNOSUPPORT == (err)) +#endif + +#define mhd_SCKT_LERR_IS_AF() mhd_SCKT_ERR_IS_AF (mhd_SCKT_GET_LERR ()) + +#if defined(MHD_POSIX_SOCKETS) +# ifdef EINVAL +# define mhd_SCKT_ERR_IS_EINVAL(err) (EINVAL == (err)) +# else +# define mhd_SCKT_ERR_IS_EINVAL(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_EINVAL(err) (WSAEINVAL == (err)) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef EINTR +# define mhd_SCKT_ERR_IS_EINTR(err) (EINTR == (err)) +# else +# define mhd_SCKT_ERR_IS_EINTR(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_EINTR(err) (WSAEINTR == (err)) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef ECONNRESET +# define mhd_SCKT_ERR_IS_CONNRESET(err) (ECONNRESET == (err)) +# else +# define mhd_SCKT_ERR_IS_CONNRESET(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_CONNRESET(err) (WSAECONNRESET == (err)) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef ENOTCONN +# define mhd_SCKT_ERR_IS_NOTCONN(err) (ENOTCONN == (err)) +# else +# define mhd_SCKT_ERR_IS_NOTCONNT(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_NOTCONN(err) (WSAENOTCONN == (err)) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef EOPNOTSUPP +# define mhd_SCKT_ERR_IS_OPNOTSUPP(err) (EOPNOTSUPP == (err)) +# else +# define mhd_SCKT_ERR_IS_OPNOTSUPP(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_OPNOTSUPP(err) (WSAEOPNOTSUPP == (err)) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef ENOPROTOOPT +# define mhd_SCKT_ERR_IS_NOPROTOOPT(err) (ENOPROTOOPT == (err)) +# else +# define mhd_SCKT_ERR_IS_NOPROTOOPT(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_NOPROTOOPT(err) (WSAENOPROTOOPT == (err)) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef EBADF +# define mhd_SCKT_ERR_IS_BADF(err) (EBADF == (err)) +# else +# define mhd_SCKT_ERR_IS_BADF(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_BADF(err) ((void) (err), ! ! 0) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef ENOTSOCK +# define mhd_SCKT_ERR_IS_NOTSOCK(err) (ENOTSOCK == (err)) +# else +# define mhd_SCKT_ERR_IS_NOTSOCK(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_NOTSOCK(err) (WSAENOTSOCK == (err)) +#endif + +#if defined(MHD_POSIX_SOCKETS) +# ifdef EPIPE +# define mhd_SCKT_ERR_IS_PIPE(err) (EPIPE == (err)) +# else +# define mhd_SCKT_ERR_IS_PIPE(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_PIPE(err) (WSAESHUTDOWN == (err)) +#endif + +/** + * Check whether is given socket error is type of "incoming connection + * was disconnected before 'accept()' is called". + * @return boolean true is @a err match described socket error code, + * boolean false otherwise. + */ +#if defined(MHD_POSIX_SOCKETS) +# ifdef ECONNABORTED +# define mhd_SCKT_ERR_IS_DISCNN_BEFORE_ACCEPT(err) (ECONNABORTED == (err)) +# else +# define mhd_SCKT_ERR_IS_DISCNN_BEFORE_ACCEPT(err) ((void) (err), ! ! 0) +# endif +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_DISCNN_BEFORE_ACCEPT(err) (WSAECONNRESET == (err)) +#endif + +/** + * Error for any reason when the system detects connection broke, but not + * because of the peer. + * It can be keep-alive ping failure or timeout to get ACK for the + * transmitted data. + */ +#if defined(MHD_POSIX_SOCKETS) +/* + EHOSTUNREACH: probably reported by intermediate + + ETIMEDOUT: probably keep-alive ping failure + + ENETUNREACH: probably cable physically disconnected or similar */ +# define mhd_SCKT_ERR_IS_CONN_BROKEN(err) \ + ((0 != (err)) && \ + ((mhd_EHOSTUNREACH_OR_ZERO == (err)) || \ + (mhd_ETIMEDOUT_OR_ZERO == (err)) || \ + (mhd_ENETUNREACH_OR_ZERO == (err)))) +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_CONN_BROKEN(err) \ + ( (WSAENETRESET == (err)) || (WSAECONNABORTED == (err)) || \ + (WSAETIMEDOUT == (err)) ) +#endif + +/** + * Check whether given socket error is any kind of "low resource" error. + * @return boolean true if @a err is any kind of "low resource" error, + * boolean false otherwise. + */ +#if defined(MHD_POSIX_SOCKETS) +# define mhd_SCKT_ERR_IS_LOW_RESOURCES(err) \ + ((0 != (err)) && \ + ((mhd_EMFILE_OR_ZERO == (err)) || (mhd_ENFILE_OR_ZERO == (err)) || \ + (mhd_ENOMEM_OR_ZERO == (err)) || (mhd_ENOBUFS_OR_ZERO == (err)))) +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_LOW_RESOURCES(err) \ + ( (WSAEMFILE == (err)) || (WSAENOBUFS == (err)) ) +#endif + +/** + * Check whether given socket error is any kind of "low memory" error. + * This is subset of #mhd_SCKT_ERR_IS_LOW_RESOURCES() + * @return boolean true if @a err is any kind of "low memory" error, + * boolean false otherwise. + */ +#if defined(MHD_POSIX_SOCKETS) +# define mhd_SCKT_ERR_IS_LOW_MEM(err) \ + ((0 != (err)) && \ + ((mhd_ENOMEM_OR_ZERO == (err)) || (mhd_ENOBUFS_OR_ZERO == (err)))) +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_ERR_IS_LOW_MEM(err) (WSAENOBUFS == (err)) +#endif + + +#if defined(MHD_POSIX_SOCKETS) +# ifdef HAVE_SOCKETPAIR +# ifdef MHD_AF_UNIX +# define mhd_socket_pair(fdarr_ptr) \ + (0 != socketpair (MHD_AF_UNIX, SOCK_STREAM, 0, (fdarr_ptr))) +# else +# define mhd_socket_pair(fdarr_ptr) \ + (0 != socketpair (AF_INET, SOCK_STREAM, 0, (fdarr_ptr))) /* Fallback, could be broken on many platforms */ +# endif +# if defined(HAVE_SOCK_NONBLOCK) +# ifdef MHD_AF_UNIX +# define mhd_socket_pair_nblk(fdarr_ptr) \ + (0 != socketpair (MHD_AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, \ + (fdarr_ptr))) +# else +# define mhd_socket_pair_nblk(fdarr_ptr) \ + (0 != socketpair (AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0, (fdarr_ptr))) /* Fallback, could be broken on many platforms */ +# endif +# endif /* HAVE_SOCK_NONBLOCK*/ +# endif /* HAVE_SOCKETPAIR */ +#endif + +#ifndef mhd_socket_pair +/* mhd_socket_pair() implemented in "mhd_sockets_funcs.h" based on local function */ +#endif + +#if defined(SOL_SOCKET) && defined(SO_NOSIGPIPE) +/** + * Helper for mhd_socket_nosignal() + */ +# ifdef HAVE_COMPOUND_LITERALS_LVALUES +# define mhd_socket_nosig_helper_int_one ((int){1}) +# else +/** + * Internal static const helper for mhd_socket_nosignal() + */ +static const int mhd_socket_nosig_helper_int_one = 1; +# endif + + +/** + * Change socket options to no signal on remote disconnect / broken connection. + * + * @param sock socket to manipulate + * @return non-zero if succeeded, zero otherwise + */ +# define mhd_socket_nosignal(sock) \ + (! setsockopt ((sock),SOL_SOCKET,SO_NOSIGPIPE, \ + &mhd_socket_nosig_helper_int_one, sizeof(int))) +#endif /* SOL_SOCKET && SO_NOSIGPIPE */ + + +#endif /* ! MHD_SOCKETS_MACROS_H */ diff --git a/src/mhd2/mhd_str.c b/src/mhd2/mhd_str.c @@ -0,0 +1,2317 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2015-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 microhttpd/mhd_str.c + * @brief Functions implementations for string manipulating + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_str.h" + +#include <string.h> + +#include "mhd_assert.h" +#include "mhd_limits.h" +#include "mhd_assert.h" + +#ifdef MHD_FAVOR_SMALL_CODE +# ifdef MHD_static_inline_ +# undef MHD_static_inline_ +# endif /* MHD_static_inline_ */ +/* Do not force inlining and do not use macro functions, use normal static + functions instead. + This may give more flexibility for size optimizations. */ +# define MHD_static_inline_ static +# ifndef HAVE_INLINE_FUNCS +# define HAVE_INLINE_FUNCS 1 +# endif /* !HAVE_INLINE_FUNCS */ +#endif /* MHD_FAVOR_SMALL_CODE */ + +/* + * Block of functions/macros that use US-ASCII charset as required by HTTP + * standards. Not affected by current locale settings. + */ + +#ifdef HAVE_INLINE_FUNCS + +#if 0 /* Disable unused functions. */ +/** + * Check whether character is lower case letter in US-ASCII + * + * @param c character to check + * @return non-zero if character is lower case letter, zero otherwise + */ +MHD_static_inline_ bool +isasciilower (char c) +{ + return (c >= 'a') && (c <= 'z'); +} + + +#endif /* Disable unused functions. */ + + +/** + * Check whether character is upper case letter in US-ASCII + * + * @param c character to check + * @return non-zero if character is upper case letter, zero otherwise + */ +MHD_static_inline_ bool +isasciiupper (char c) +{ + return (c <= 'Z') && (c >= 'A'); +} + + +#if 0 /* Disable unused functions. */ +/** + * Check whether character is letter in US-ASCII + * + * @param c character to check + * @return non-zero if character is letter in US-ASCII, zero otherwise + */ +MHD_static_inline_ bool +isasciialpha (char c) +{ + return isasciilower (c) || isasciiupper (c); +} + + +#endif /* Disable unused functions. */ + + +/** + * Check whether character is decimal digit in US-ASCII + * + * @param c character to check + * @return non-zero if character is decimal digit, zero otherwise + */ +MHD_static_inline_ bool +isasciidigit (char c) +{ + return (c <= '9') && (c >= '0'); +} + + +#if 0 /* Disable unused functions. */ +/** + * Check whether character is hexadecimal digit in US-ASCII + * + * @param c character to check + * @return non-zero if character is decimal digit, zero otherwise + */ +MHD_static_inline_ bool +isasciixdigit (char c) +{ + return isasciidigit (c) || + ( (c <= 'F') && (c >= 'A') ) || + ( (c <= 'f') && (c >= 'a') ); +} + + +/** + * Check whether character is decimal digit or letter in US-ASCII + * + * @param c character to check + * @return non-zero if character is decimal digit or letter, zero otherwise + */ +MHD_static_inline_ bool +isasciialnum (char c) +{ + return isasciialpha (c) || isasciidigit (c); +} + + +#endif /* Disable unused functions. */ + + +#if 0 /* Disable unused functions. */ +/** + * Convert US-ASCII character to lower case. + * If character is upper case letter in US-ASCII than it's converted to lower + * case analog. If character is NOT upper case letter than it's returned + * unmodified. + * + * @param c character to convert + * @return converted to lower case character + */ +MHD_static_inline_ char +toasciilower (char c) +{ + return isasciiupper (c) ? (c - 'A' + 'a') : c; +} + + +/** + * Convert US-ASCII character to upper case. + * If character is lower case letter in US-ASCII than it's converted to upper + * case analog. If character is NOT lower case letter than it's returned + * unmodified. + * + * @param c character to convert + * @return converted to upper case character + */ +MHD_static_inline_ char +toasciiupper (char c) +{ + return isasciilower (c) ? (c - 'a' + 'A') : c; +} + + +#endif /* Disable unused functions. */ + + +#if defined(MHD_FAVOR_SMALL_CODE) /* Used only in mhd_str_to_uvalue_n() */ +/** + * Convert US-ASCII decimal digit to its value. + * + * @param c character to convert + * @return value of decimal digit or -1 if @ c is not decimal digit + */ +MHD_static_inline_ int +todigitvalue (char c) +{ + if (isasciidigit (c)) + return (unsigned char) (c - '0'); + + return -1; +} + + +#endif /* MHD_FAVOR_SMALL_CODE */ + + +/** + * Convert US-ASCII hexadecimal digit to its value. + * + * @param c character to convert + * @return value of hexadecimal digit or -1 if @ c is not hexadecimal digit + */ +MHD_static_inline_ int +toxdigitvalue (char c) +{ +#if ! defined(MHD_FAVOR_SMALL_CODE) + switch ((unsigned char) c) + { +#if 0 /* Disabled to give the compiler a hint about low probability */ + case 0x00U: /* NUL */ + case 0x01U: /* SOH */ + case 0x02U: /* STX */ + case 0x03U: /* ETX */ + case 0x04U: /* EOT */ + case 0x05U: /* ENQ */ + case 0x06U: /* ACK */ + case 0x07U: /* BEL */ + case 0x08U: /* BS */ + case 0x09U: /* HT */ + case 0x0AU: /* LF */ + case 0x0BU: /* VT */ + case 0x0CU: /* FF */ + case 0x0DU: /* CR */ + case 0x0EU: /* SO */ + case 0x0FU: /* SI */ + case 0x10U: /* DLE */ + case 0x11U: /* DC1 */ + case 0x12U: /* DC2 */ + case 0x13U: /* DC3 */ + case 0x14U: /* DC4 */ + case 0x15U: /* NAK */ + case 0x16U: /* SYN */ + case 0x17U: /* ETB */ + case 0x18U: /* CAN */ + case 0x19U: /* EM */ + case 0x1AU: /* SUB */ + case 0x1BU: /* ESC */ + case 0x1CU: /* FS */ + case 0x1DU: /* GS */ + case 0x1EU: /* RS */ + case 0x1FU: /* US */ + case 0x20U: /* ' ' */ + case 0x21U: /* '!' */ + case 0x22U: /* '"' */ + case 0x23U: /* '#' */ + case 0x24U: /* '$' */ + case 0x25U: /* '%' */ + case 0x26U: /* '&' */ + case 0x27U: /* '\'' */ + case 0x28U: /* '(' */ + case 0x29U: /* ')' */ + case 0x2AU: /* '*' */ + case 0x2BU: /* '+' */ + case 0x2CU: /* ',' */ + case 0x2DU: /* '-' */ + case 0x2EU: /* '.' */ + case 0x2FU: /* '/' */ + return -1; +#endif + case 0x30U: /* '0' */ + return 0; + case 0x31U: /* '1' */ + return 1; + case 0x32U: /* '2' */ + return 2; + case 0x33U: /* '3' */ + return 3; + case 0x34U: /* '4' */ + return 4; + case 0x35U: /* '5' */ + return 5; + case 0x36U: /* '6' */ + return 6; + case 0x37U: /* '7' */ + return 7; + case 0x38U: /* '8' */ + return 8; + case 0x39U: /* '9' */ + return 9; +#if 0 /* Disabled to give the compiler a hint about low probability */ + case 0x3AU: /* ':' */ + case 0x3BU: /* ';' */ + case 0x3CU: /* '<' */ + case 0x3DU: /* '=' */ + case 0x3EU: /* '>' */ + case 0x3FU: /* '?' */ + case 0x40U: /* '@' */ + return -1; +#endif + case 0x41U: /* 'A' */ + return 0xAU; + case 0x42U: /* 'B' */ + return 0xBU; + case 0x43U: /* 'C' */ + return 0xCU; + case 0x44U: /* 'D' */ + return 0xDU; + case 0x45U: /* 'E' */ + return 0xEU; + case 0x46U: /* 'F' */ + return 0xFU; +#if 0 /* Disabled to give the compiler a hint about low probability */ + case 0x47U: /* 'G' */ + case 0x48U: /* 'H' */ + case 0x49U: /* 'I' */ + case 0x4AU: /* 'J' */ + case 0x4BU: /* 'K' */ + case 0x4CU: /* 'L' */ + case 0x4DU: /* 'M' */ + case 0x4EU: /* 'N' */ + case 0x4FU: /* 'O' */ + case 0x50U: /* 'P' */ + case 0x51U: /* 'Q' */ + case 0x52U: /* 'R' */ + case 0x53U: /* 'S' */ + case 0x54U: /* 'T' */ + case 0x55U: /* 'U' */ + case 0x56U: /* 'V' */ + case 0x57U: /* 'W' */ + case 0x58U: /* 'X' */ + case 0x59U: /* 'Y' */ + case 0x5AU: /* 'Z' */ + case 0x5BU: /* '[' */ + case 0x5CU: /* '\' */ + case 0x5DU: /* ']' */ + case 0x5EU: /* '^' */ + case 0x5FU: /* '_' */ + case 0x60U: /* '`' */ + return -1; +#endif + case 0x61U: /* 'a' */ + return 0xAU; + case 0x62U: /* 'b' */ + return 0xBU; + case 0x63U: /* 'c' */ + return 0xCU; + case 0x64U: /* 'd' */ + return 0xDU; + case 0x65U: /* 'e' */ + return 0xEU; + case 0x66U: /* 'f' */ + return 0xFU; +#if 0 /* Disabled to give the compiler a hint about low probability */ + case 0x67U: /* 'g' */ + case 0x68U: /* 'h' */ + case 0x69U: /* 'i' */ + case 0x6AU: /* 'j' */ + case 0x6BU: /* 'k' */ + case 0x6CU: /* 'l' */ + case 0x6DU: /* 'm' */ + case 0x6EU: /* 'n' */ + case 0x6FU: /* 'o' */ + case 0x70U: /* 'p' */ + case 0x71U: /* 'q' */ + case 0x72U: /* 'r' */ + case 0x73U: /* 's' */ + case 0x74U: /* 't' */ + case 0x75U: /* 'u' */ + case 0x76U: /* 'v' */ + case 0x77U: /* 'w' */ + case 0x78U: /* 'x' */ + case 0x79U: /* 'y' */ + case 0x7AU: /* 'z' */ + case 0x7BU: /* '{' */ + case 0x7CU: /* '|' */ + case 0x7DU: /* '}' */ + case 0x7EU: /* '~' */ + case 0x7FU: /* DEL */ + case 0x80U: /* EXT */ + case 0x81U: /* EXT */ + case 0x82U: /* EXT */ + case 0x83U: /* EXT */ + case 0x84U: /* EXT */ + case 0x85U: /* EXT */ + case 0x86U: /* EXT */ + case 0x87U: /* EXT */ + case 0x88U: /* EXT */ + case 0x89U: /* EXT */ + case 0x8AU: /* EXT */ + case 0x8BU: /* EXT */ + case 0x8CU: /* EXT */ + case 0x8DU: /* EXT */ + case 0x8EU: /* EXT */ + case 0x8FU: /* EXT */ + case 0x90U: /* EXT */ + case 0x91U: /* EXT */ + case 0x92U: /* EXT */ + case 0x93U: /* EXT */ + case 0x94U: /* EXT */ + case 0x95U: /* EXT */ + case 0x96U: /* EXT */ + case 0x97U: /* EXT */ + case 0x98U: /* EXT */ + case 0x99U: /* EXT */ + case 0x9AU: /* EXT */ + case 0x9BU: /* EXT */ + case 0x9CU: /* EXT */ + case 0x9DU: /* EXT */ + case 0x9EU: /* EXT */ + case 0x9FU: /* EXT */ + case 0xA0U: /* EXT */ + case 0xA1U: /* EXT */ + case 0xA2U: /* EXT */ + case 0xA3U: /* EXT */ + case 0xA4U: /* EXT */ + case 0xA5U: /* EXT */ + case 0xA6U: /* EXT */ + case 0xA7U: /* EXT */ + case 0xA8U: /* EXT */ + case 0xA9U: /* EXT */ + case 0xAAU: /* EXT */ + case 0xABU: /* EXT */ + case 0xACU: /* EXT */ + case 0xADU: /* EXT */ + case 0xAEU: /* EXT */ + case 0xAFU: /* EXT */ + case 0xB0U: /* EXT */ + case 0xB1U: /* EXT */ + case 0xB2U: /* EXT */ + case 0xB3U: /* EXT */ + case 0xB4U: /* EXT */ + case 0xB5U: /* EXT */ + case 0xB6U: /* EXT */ + case 0xB7U: /* EXT */ + case 0xB8U: /* EXT */ + case 0xB9U: /* EXT */ + case 0xBAU: /* EXT */ + case 0xBBU: /* EXT */ + case 0xBCU: /* EXT */ + case 0xBDU: /* EXT */ + case 0xBEU: /* EXT */ + case 0xBFU: /* EXT */ + case 0xC0U: /* EXT */ + case 0xC1U: /* EXT */ + case 0xC2U: /* EXT */ + case 0xC3U: /* EXT */ + case 0xC4U: /* EXT */ + case 0xC5U: /* EXT */ + case 0xC6U: /* EXT */ + case 0xC7U: /* EXT */ + case 0xC8U: /* EXT */ + case 0xC9U: /* EXT */ + case 0xCAU: /* EXT */ + case 0xCBU: /* EXT */ + case 0xCCU: /* EXT */ + case 0xCDU: /* EXT */ + case 0xCEU: /* EXT */ + case 0xCFU: /* EXT */ + case 0xD0U: /* EXT */ + case 0xD1U: /* EXT */ + case 0xD2U: /* EXT */ + case 0xD3U: /* EXT */ + case 0xD4U: /* EXT */ + case 0xD5U: /* EXT */ + case 0xD6U: /* EXT */ + case 0xD7U: /* EXT */ + case 0xD8U: /* EXT */ + case 0xD9U: /* EXT */ + case 0xDAU: /* EXT */ + case 0xDBU: /* EXT */ + case 0xDCU: /* EXT */ + case 0xDDU: /* EXT */ + case 0xDEU: /* EXT */ + case 0xDFU: /* EXT */ + case 0xE0U: /* EXT */ + case 0xE1U: /* EXT */ + case 0xE2U: /* EXT */ + case 0xE3U: /* EXT */ + case 0xE4U: /* EXT */ + case 0xE5U: /* EXT */ + case 0xE6U: /* EXT */ + case 0xE7U: /* EXT */ + case 0xE8U: /* EXT */ + case 0xE9U: /* EXT */ + case 0xEAU: /* EXT */ + case 0xEBU: /* EXT */ + case 0xECU: /* EXT */ + case 0xEDU: /* EXT */ + case 0xEEU: /* EXT */ + case 0xEFU: /* EXT */ + case 0xF0U: /* EXT */ + case 0xF1U: /* EXT */ + case 0xF2U: /* EXT */ + case 0xF3U: /* EXT */ + case 0xF4U: /* EXT */ + case 0xF5U: /* EXT */ + case 0xF6U: /* EXT */ + case 0xF7U: /* EXT */ + case 0xF8U: /* EXT */ + case 0xF9U: /* EXT */ + case 0xFAU: /* EXT */ + case 0xFBU: /* EXT */ + case 0xFCU: /* EXT */ + case 0xFDU: /* EXT */ + case 0xFEU: /* EXT */ + case 0xFFU: /* EXT */ + return -1; + default: + mhd_assert (0); + break; /* Should be unreachable */ +#else + default: + break; +#endif + } + return -1; +#else /* MHD_FAVOR_SMALL_CODE */ + if (c <= 9) + { + if (c >= 0) + return (unsigned char) (c - '0'); + } + else if (c <= 'F') + { + if (c >= 'A') + return (unsigned char) (c - 'A' + 10); + } + else if (c <= 'F') + { + if (c >= 'a') + return (unsigned char) (c - 'a' + 10); + } + + return -1; +#endif /* MHD_FAVOR_SMALL_CODE */ +} + + +/** + * Caseless compare two characters. + * + * @param c1 the first char to compare + * @param c2 the second char to compare + * @return boolean 'true' if chars are caseless equal, false otherwise + */ +MHD_static_inline_ bool +charsequalcaseless (const char c1, const char c2) +{ + return ( (c1 == c2) || + (isasciiupper (c1) ? + ((c1 - 'A' + 'a') == c2) : + ((c1 == (c2 - 'A' + 'a')) && isasciiupper (c2))) ); +} + + +#else /* !HAVE_INLINE_FUNCS */ + + +/** + * Checks whether character is lower case letter in US-ASCII + * + * @param c character to check + * @return boolean true if character is lower case letter, + * boolean false otherwise + */ +#define isasciilower(c) ((((char) (c)) >= 'a') && (((char) (c)) <= 'z')) + + +/** + * Checks whether character is upper case letter in US-ASCII + * + * @param c character to check + * @return boolean true if character is upper case letter, + * boolean false otherwise + */ +#define isasciiupper(c) ((((char) (c)) <= 'Z') && (((char) (c)) >= 'A')) + + +/** + * Checks whether character is letter in US-ASCII + * + * @param c character to check + * @return boolean true if character is letter, boolean false + * otherwise + */ +#define isasciialpha(c) (isasciilower (c) || isasciiupper (c)) + + +/** + * Check whether character is decimal digit in US-ASCII + * + * @param c character to check + * @return boolean true if character is decimal digit, boolean false + * otherwise + */ +#define isasciidigit(c) ((((char) (c)) <= '9') && (((char) (c)) >= '0')) + + +/** + * Check whether character is hexadecimal digit in US-ASCII + * + * @param c character to check + * @return boolean true if character is hexadecimal digit, + * boolean false otherwise + */ +#define isasciixdigit(c) (isasciidigit ((c)) || \ + (((char) (c)) <= 'F' && ((char) (c)) >= 'A') || \ + (((char) (c)) <= 'f' && ((char) (c)) >= 'a')) + + +/** + * Check whether character is decimal digit or letter in US-ASCII + * + * @param c character to check + * @return boolean true if character is decimal digit or letter, + * boolean false otherwise + */ +#define isasciialnum(c) (isasciialpha (c) || isasciidigit (c)) + + +/** + * Convert US-ASCII character to lower case. + * If character is upper case letter in US-ASCII than it's converted to lower + * case analog. If character is NOT upper case letter than it's returned + * unmodified. + * + * @param c character to convert + * @return converted to lower case character + */ +#define toasciilower(c) ((isasciiupper (c)) ? (((char) (c)) - 'A' + 'a') : \ + ((char) (c))) + + +/** + * Convert US-ASCII character to upper case. + * If character is lower case letter in US-ASCII than it's converted to upper + * case analog. If character is NOT lower case letter than it's returned + * unmodified. + * + * @param c character to convert + * @return converted to upper case character + */ +#define toasciiupper(c) ((isasciilower (c)) ? (((char) (c)) - 'a' + 'A') : \ + ((char) (c))) + + +/** + * Convert US-ASCII decimal digit to its value. + * + * @param c character to convert + * @return value of hexadecimal digit or -1 if @ c is not hexadecimal digit + */ +#define todigitvalue(c) (isasciidigit (c) ? (int) (((char) (c)) - '0') : \ + (int) (-1)) + + +/** + * Convert US-ASCII hexadecimal digit to its value. + * @param c character to convert + * @return value of hexadecimal digit or -1 if @ c is not hexadecimal digit + */ +#define toxdigitvalue(c) (isasciidigit (c) ? (int) (((char) (c)) - '0') : \ + ( (((char) (c)) >= 'A' && ((char) (c)) <= 'F') ? \ + (int) (((unsigned char) (c)) - 'A' + 10) : \ + ( (((char) (c)) >= 'a' && ((char) (c)) <= 'f') ? \ + (int) (((unsigned char) (c)) - 'a' + 10) : \ + (int) (-1) ))) + +/** + * Caseless compare two characters. + * + * @param c1 the first char to compare + * @param c2 the second char to compare + * @return boolean 'true' if chars are caseless equal, false otherwise + */ +#define charsequalcaseless(c1, c2) \ + ( ((c1) == (c2)) || \ + (isasciiupper (c1) ? \ + (((c1) - 'A' + 'a') == (c2)) : \ + (((c1) == ((c2) - 'A' + 'a')) && isasciiupper (c2))) ) + +#endif /* !HAVE_INLINE_FUNCS */ + + +#ifndef MHD_FAVOR_SMALL_CODE +MHD_INTERNAL bool +mhd_str_equal_caseless (const char *str1, + const char *str2) +{ + while (0 != (*str1)) + { + const char c1 = *str1; + const char c2 = *str2; + if (charsequalcaseless (c1, c2)) + { + str1++; + str2++; + } + else + return false; + } + return 0 == (*str2); +} + + +#endif /* ! MHD_FAVOR_SMALL_CODE */ + + +MHD_INTERNAL bool +mhd_str_equal_caseless_n (const char *const str1, + const char *const str2, + size_t maxlen) +{ + size_t i; + + for (i = 0; i < maxlen; ++i) + { + const char c1 = str1[i]; + const char c2 = str2[i]; + if (0 == c2) + return 0 == c1; + if (charsequalcaseless (c1, c2)) + continue; + else + return false; + } + return true; +} + + +MHD_INTERNAL bool +mhd_str_equal_caseless_bin_n (const char *const str1, + const char *const str2, + size_t len) +{ + size_t i; + + for (i = 0; i < len; ++i) + { + const char c1 = str1[i]; + const char c2 = str2[i]; + if (charsequalcaseless (c1, c2)) + continue; + else + return 0; + } + return ! 0; +} + + +MHD_INTERNAL bool +mhd_str_has_token_caseless (const char *str, + const char *const token, + size_t token_len) +{ + if (0 == token_len) + return false; + + while (0 != *str) + { + size_t i; + /* Skip all whitespaces and empty tokens. */ + while (' ' == *str || '\t' == *str || ',' == *str) + str++; + + /* Check for token match. */ + i = 0; + while (1) + { + const char sc = *(str++); + const char tc = token[i++]; + + if (0 == sc) + return false; + if (! charsequalcaseless (sc, tc)) + break; + if (i >= token_len) + { + /* Check whether substring match token fully or + * has additional unmatched chars at tail. */ + while (' ' == *str || '\t' == *str) + str++; + /* End of (sub)string? */ + if ((0 == *str) || (',' == *str) ) + return true; + /* Unmatched chars at end of substring. */ + break; + } + } + /* Find next substring. */ + while (0 != *str && ',' != *str) + str++; + } + return false; +} + + +MHD_INTERNAL bool +mhd_str_remove_token_caseless (const char *restrict str, + size_t str_len, + const char *const restrict token, + const size_t token_len, + char *restrict buf, + ssize_t *restrict buf_size) +{ + const char *s1; /**< the "input" string / character */ + char *s2; /**< the "output" string / character */ + size_t t_pos; /**< position of matched character in the token */ + bool token_removed; + + mhd_assert (NULL == memchr (token, 0, token_len)); + mhd_assert (NULL == memchr (token, ' ', token_len)); + mhd_assert (NULL == memchr (token, '\t', token_len)); + mhd_assert (NULL == memchr (token, ',', token_len)); + mhd_assert (0 <= *buf_size); + + if (SSIZE_MAX <= ((str_len / 2) * 3 + 3)) + { + /* The return value may overflow, refuse */ + *buf_size = (ssize_t) -1; + return false; + } + s1 = str; + s2 = buf; + token_removed = false; + + while ((size_t) (s1 - str) < str_len) + { + const char *cur_token; /**< the first char of current token */ + size_t copy_size; + + /* Skip any initial whitespaces and empty tokens */ + while ( ((size_t) (s1 - str) < str_len) && + ((' ' == *s1) || ('\t' == *s1) || (',' == *s1)) ) + s1++; + + /* 's1' points to the first char of token in the input string or + * points just beyond the end of the input string */ + + if ((size_t) (s1 - str) >= str_len) + break; /* Nothing to copy, end of the input string */ + + /* 's1' points to the first char of token in the input string */ + + cur_token = s1; /* the first char of input token */ + + /* Check the token with case-insensetive match */ + t_pos = 0; + while ( ((size_t) (s1 - str) < str_len) && (token_len > t_pos) && + (charsequalcaseless (*s1, token[t_pos])) ) + { + s1++; + t_pos++; + } + /* s1 may point just beyond the end of the input string */ + if ( (token_len == t_pos) && (0 != token_len) ) + { + /* 'token' matched, check that current input token does not have + * any suffixes */ + while ( ((size_t) (s1 - str) < str_len) && + ((' ' == *s1) || ('\t' == *s1)) ) + s1++; + /* 's1' points to the first non-whitespace char after the token matched + * requested token or points just beyond the end of the input string after + * the requested token */ + if (((size_t) (s1 - str) == str_len) || (',' == *s1)) + {/* full token match, do not copy current token to the output */ + token_removed = true; + continue; + } + } + + /* 's1' points to first non-whitespace char, to some char after + * first non-whitespace char in the token in the input string, to + * the ',', or just beyond the end of the input string */ + /* The current token in the input string does not match the token + * to exclude, it must be copied to the output string */ + /* the current token size excluding leading whitespaces and current char */ + copy_size = (size_t) (s1 - cur_token); + if (buf == s2) + { /* The first token to copy to the output */ + if ((size_t) *buf_size < copy_size) + { /* Not enough space in the output buffer */ + *buf_size = (ssize_t) -1; + return false; + } + } + else + { /* Some token was already copied to the output buffer */ + mhd_assert (s2 > buf); + if ((size_t) *buf_size < ((size_t) (s2 - buf)) + copy_size + 2) + { /* Not enough space in the output buffer */ + *buf_size = (ssize_t) -1; + return false; + } + *(s2++) = ','; + *(s2++) = ' '; + } + /* Copy non-matched token to the output */ + if (0 != copy_size) + { + memcpy (s2, cur_token, copy_size); + s2 += copy_size; + } + + while ( ((size_t) (s1 - str) < str_len) && (',' != *s1)) + { + /* 's1' points to first non-whitespace char, to some char after + * first non-whitespace char in the token in the input string */ + /* Copy all non-whitespace chars from the current token in + * the input string */ + while ( ((size_t) (s1 - str) < str_len) && + (',' != *s1) && (' ' != *s1) && ('\t' != *s1) ) + { + mhd_assert (s2 >= buf); + if ((size_t) *buf_size <= (size_t) (s2 - buf)) /* '<= s2' equals '< s2 + 1' */ + { /* Not enough space in the output buffer */ + *buf_size = (ssize_t) -1; + return false; + } + *(s2++) = *(s1++); + } + /* 's1' points to some whitespace char in the token in the input + * string, to the ',', or just beyond the end of the input string */ + /* Skip all whitespaces */ + while ( ((size_t) (s1 - str) < str_len) && + ((' ' == *s1) || ('\t' == *s1)) ) + s1++; + + /* 's1' points to the first non-whitespace char in the input string + * after whitespace chars, to the ',', or just beyond the end of + * the input string */ + if (((size_t) (s1 - str) < str_len) && (',' != *s1)) + { /* Not the end of the current token */ + mhd_assert (s2 >= buf); + if ((size_t) *buf_size <= (size_t) (s2 - buf)) /* '<= s2' equals '< s2 + 1' */ + { /* Not enough space in the output buffer */ + *buf_size = (ssize_t) -1; + return false; + } + *(s2++) = ' '; + } + } + } + mhd_assert (((ssize_t) (s2 - buf)) <= *buf_size); + *buf_size = (ssize_t) (s2 - buf); + return token_removed; +} + + +MHD_INTERNAL bool +mhd_str_remove_tokens_caseless (char *restrict str, + size_t *restrict str_len, + const char *const restrict tkns, + const size_t tokens_len) +{ + size_t pt; /**< position in @a tokens */ + bool token_removed; + + mhd_assert (NULL == memchr (tkns, 0, tokens_len)); + + token_removed = false; + pt = 0; + + while (pt < tokens_len && *str_len != 0) + { + const char *tkn; /**< the current token */ + size_t tkn_len; + + /* Skip any initial whitespaces and empty tokens in 'tokens' */ + while ( (pt < tokens_len) && + ((' ' == tkns[pt]) || ('\t' == tkns[pt]) || (',' == tkns[pt])) ) + pt++; + + if (pt >= tokens_len) + break; /* No more tokens, nothing to remove */ + + /* Found non-whitespace char which is not a comma */ + tkn = tkns + pt; + do + { + do + { + pt++; + } while (pt < tokens_len && + (' ' != tkns[pt] && '\t' != tkns[pt] && ',' != tkns[pt])); + /* Found end of the token string, space, tab, or comma */ + tkn_len = pt - (size_t) (tkn - tkns); + + /* Skip all spaces and tabs */ + while (pt < tokens_len && (' ' == tkns[pt] || '\t' == tkns[pt])) + pt++; + /* Found end of the token string or non-whitespace char */ + } while (pt < tokens_len && ',' != tkns[pt]); + + /* 'tkn' is the input token with 'tkn_len' chars */ + mhd_assert (0 != tkn_len); + + if (*str_len == tkn_len) + { + if (mhd_str_equal_caseless_bin_n (str, tkn, tkn_len)) + { + *str_len = 0; + token_removed = true; + } + continue; + } + /* 'tkn' cannot match part of 'str' if length of 'tkn' is larger + * than length of 'str'. + * It's know that 'tkn' is not equal to the 'str' (was checked previously). + * As 'str' is normalized when 'tkn' is not equal to the 'str' + * it is required that 'str' to be at least 3 chars larger then 'tkn' + * (the comma, the space and at least one additional character for the next + * token) to remove 'tkn' from the 'str'. */ + if (*str_len > tkn_len + 2) + { /* Remove 'tkn' from the input string */ + size_t pr; /**< the 'read' position in the @a str */ + size_t pw; /**< the 'write' position in the @a str */ + + pr = 0; + pw = 0; + + do + { + mhd_assert (pr >= pw); + mhd_assert ((*str_len) >= (pr + tkn_len)); + if ( ( ((*str_len) == (pr + tkn_len)) || (',' == str[pr + tkn_len]) ) && + mhd_str_equal_caseless_bin_n (str + pr, tkn, tkn_len) ) + { + /* current token in the input string matches the 'tkn', skip it */ + mhd_assert ((*str_len == pr + tkn_len) || \ + (' ' == str[pr + tkn_len + 1])); /* 'str' must be normalized */ + token_removed = true; + /* Advance to the next token in the input string or beyond + * the end of the input string. */ + pr += tkn_len + 2; + } + else + { + /* current token in the input string does not match the 'tkn', + * copy to the output */ + if (0 != pw) + { /* not the first output token, add ", " to separate */ + if (pr != pw + 2) + { + str[pw++] = ','; + str[pw++] = ' '; + } + else + pw += 2; /* 'str' is not yet modified in this round */ + } + do + { + if (pr != pw) + str[pw] = str[pr]; + pr++; + pw++; + } while (pr < *str_len && ',' != str[pr]); + /* Advance to the next token in the input string or beyond + * the end of the input string. */ + pr += 2; + } + /* 'pr' should point to the next token in the input string or beyond + * the end of the input string */ + if ((*str_len) < (pr + tkn_len)) + { /* The rest of the 'str + pr' is too small to match 'tkn' */ + if ((*str_len) > pr) + { /* Copy the rest of the string */ + size_t copy_size; + copy_size = *str_len - pr; + if (0 != pw) + { /* not the first output token, add ", " to separate */ + if (pr != pw + 2) + { + str[pw++] = ','; + str[pw++] = ' '; + } + else + pw += 2; /* 'str' is not yet modified in this round */ + } + if (pr != pw) + memmove (str + pw, str + pr, copy_size); + pw += copy_size; + } + *str_len = pw; + break; + } + mhd_assert ((' ' != str[0]) && ('\t' != str[0])); + mhd_assert ((0 == pr) || (3 <= pr)); + mhd_assert ((0 == pr) || (' ' == str[pr - 1])); + mhd_assert ((0 == pr) || (',' == str[pr - 2])); + } while (1); + } + } + + return token_removed; +} + + +#ifndef MHD_FAVOR_SMALL_CODE +/* Use individual function for each case */ + +MHD_INTERNAL size_t +mhd_str_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val) +{ + const char *const start = str; + uint_fast64_t res; + + if (! str || ! out_val || ! isasciidigit (str[0])) + return 0; + + res = 0; + do + { + const int digit = (unsigned char) (*str) - '0'; + uint_fast64_t prev_res = res; + + res *= 10; + if (res / 10 != prev_res) + return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + + str++; + } while (isasciidigit (*str)); + + *out_val = res; + return (size_t) (str - start); +} + + +MHD_INTERNAL size_t +mhd_str_to_uint64_n (const char *restrict str, + size_t maxlen, + uint_fast64_t *restrict out_val) +{ + uint_fast64_t res; + size_t i; + + if (! str || ! maxlen || ! out_val || ! isasciidigit (str[0])) + return 0; + + res = 0; + i = 0; + do + { + const int digit = (unsigned char) str[i] - '0'; + uint_fast64_t prev_res = res; + + res *= 10; + if (res / 10 != prev_res) + return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + i++; + } while ( (i < maxlen) && + isasciidigit (str[i]) ); + + *out_val = res; + return i; +} + + +MHD_INTERNAL size_t +mhd_strx_to_uint32 (const char *restrict str, + uint_fast32_t *restrict out_val) +{ + const char *const start = str; + uint_fast32_t res; + int digit; + + if (! str || ! out_val) + return 0; + + res = 0; + digit = toxdigitvalue (*str); + while (digit >= 0) + { + uint_fast32_t prev_res = res; + + res *= 16; + if (res / 16 != prev_res) + return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + + str++; + digit = toxdigitvalue (*str); + } + + if (str - start > 0) + *out_val = res; + return (size_t) (str - start); +} + + +MHD_INTERNAL size_t +mhd_strx_to_uint32_n (const char *restrict str, + size_t maxlen, + uint_fast32_t *restrict out_val) +{ + size_t i; + uint_fast32_t res; + int digit; + if (! str || ! out_val) + return 0; + + res = 0; + i = 0; + while (i < maxlen && (digit = toxdigitvalue (str[i])) >= 0) + { + uint_fast32_t prev_res = res; + + res *= 16; + if (res / 16 != prev_res) + return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + + res *= 16; + res += (unsigned int) digit; + i++; + } + + if (i) + *out_val = res; + return i; +} + + +/** + * Convert hexadecimal US-ASCII digits in string to number in uint_fast64_t. + * Conversion stopped at first non-digit character. + * + * @param str string to convert + * @param[out] out_val pointer to uint_fast64_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast64_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_strx_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val) +{ + const char *const start = str; + uint_fast64_t res; + int digit; + if (! str || ! out_val) + return 0; + + res = 0; + digit = toxdigitvalue (*str); + while (digit >= 0) + { + uint_fast64_t prev_res = res; + + res *= 16; + if (res / 16 != prev_res) + return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + + str++; + digit = toxdigitvalue (*str); + } + + if (str - start > 0) + *out_val = res; + return (size_t) (str - start); +} + + +/** + * Convert not more then @a maxlen hexadecimal US-ASCII digits in string + * to number in uint_fast64_t. + * Conversion stopped at first non-digit character or after @a maxlen + * digits. + * + * @param str string to convert + * @param maxlen maximum number of characters to process + * @param[out] out_val pointer to uint_fast64_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast64_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_strx_to_uint64_n (const char *restrict str, + size_t maxlen, + uint_fast64_t *restrict out_val) +{ + size_t i; + uint_fast64_t res; + int digit; + if (! str || ! out_val) + return 0; + + res = 0; + i = 0; + while (i < maxlen && (digit = toxdigitvalue (str[i])) >= 0) + { + uint_fast64_t prev_res = res; + + res *= 16; + if (res / 16 != prev_res) + return 0; + res += (unsigned int) digit; + if (res < (unsigned int) digit) + return 0; + i++; + } + + if (i) + *out_val = res; + return i; +} + + +#else /* MHD_FAVOR_SMALL_CODE */ + +/** + * Generic function for converting not more then @a maxlen + * hexadecimal or decimal US-ASCII digits in string to number. + * Conversion stopped at first non-digit character or after @a maxlen + * digits. + * To be used only within macro. + * + * @param str the string to convert + * @param maxlen the maximum number of characters to process + * @param out_val the pointer to variable to store result of conversion + * @param val_size the size of variable pointed by @a out_val, in bytes, 4 or 8 + * @param max_val the maximum decoded number + * @param base the numeric base, 10 or 16 + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then @a max_val, @a val_size is not 4/8 or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_str_to_uvalue_n (const char *restrict str, + size_t maxlen, + void *restrict out_val, + size_t val_size, + uint_fast64_t max_val, + unsigned int base) +{ + size_t i; + uint_fast64_t res; + const uint_fast64_t max_v_div_b = max_val / base; + const uint_fast64_t max_v_mod_b = max_val % base; + + if (! str || ! out_val || + ((base != 16) && (base != 10)) ) + return 0; + + res = 0; + i = 0; + while (maxlen > i) + { + const int digit = (base == 16) ? + toxdigitvalue (str[i]) : todigitvalue (str[i]); + + if (0 > digit) + break; + if ( ((max_v_div_b) < res) || + (( (max_v_div_b) == res) && + ( (max_v_mod_b) < (uint_fast64_t) digit) ) ) + return 0; + + res *= base; + res += (unsigned int) digit; + i++; + } + + if (i) + { + if (8 == val_size) + *(uint_fast64_t *) out_val = res; + else if (4 == val_size) + *(uint_fast32_t *) out_val = (uint_fast32_t) res; + else + return 0; + } + return i; +} + + +#endif /* MHD_FAVOR_SMALL_CODE */ + + +MHD_INTERNAL size_t +mhd_uint32_to_strx (uint_fast32_t val, + char *buf, + size_t buf_size) +{ + size_t o_pos = 0; /**< position of the output character */ + int digit_pos = 8; /** zero-based, digit position in @a 'val' */ + int digit; + + /* Skip leading zeros */ + do + { + digit_pos--; + digit = (int) (val >> 28); + val <<= 4; + } while ((0 == digit) && (0 != digit_pos)); + + while (o_pos < buf_size) + { + buf[o_pos++] = + (char) ((digit <= 9) ? + ('0' + (char) digit) : + ('A' + (char) digit - 10)); + if (0 == digit_pos) + return o_pos; + digit_pos--; + digit = (int) (val >> 28); + val <<= 4; + } + return 0; /* The buffer is too small */ +} + + +#ifndef MHD_FAVOR_SMALL_CODE +MHD_INTERNAL size_t +mhd_uint16_to_str (uint_least16_t val, + char *buf, + size_t buf_size) +{ + char *chr; /**< pointer to the current printed digit */ + /* The biggest printable number is 65535 */ + uint_least16_t divisor = UINT16_C (10000); + int digit; + + chr = buf; + digit = (int) (val / divisor); + mhd_assert (digit < 10); + + /* Do not print leading zeros */ + while ((0 == digit) && (1 < divisor)) + { + divisor /= 10; + digit = (int) (val / divisor); + mhd_assert (digit < 10); + } + + while (0 != buf_size) + { + *chr = (char) ((char) digit + '0'); + chr++; + buf_size--; + if (1 == divisor) + return (size_t) (chr - buf); + val = (uint_least16_t) (val % divisor); + divisor /= 10; + digit = (int) (val / divisor); + mhd_assert (digit < 10); + } + return 0; /* The buffer is too small */ +} + + +#endif /* !MHD_FAVOR_SMALL_CODE */ + + +MHD_INTERNAL size_t +mhd_uint64_to_str (uint_fast64_t val, + char *buf, + size_t buf_size) +{ + char *chr; /**< pointer to the current printed digit */ + /* The biggest printable number is 18446744073709551615 */ + uint_fast64_t divisor = (uint_fast64_t) 10000000000000000000U; + int digit; + + chr = buf; + digit = (int) (val / divisor); + mhd_assert (digit < 10); + + /* Do not print leading zeros */ + while ((0 == digit) && (1 < divisor)) + { + divisor /= 10; + digit = (int) (val / divisor); + mhd_assert (digit < 10); + } + + while (0 != buf_size) + { + *chr = (char) ((char) digit + '0'); + chr++; + buf_size--; + if (1 == divisor) + return (size_t) (chr - buf); + val %= divisor; + divisor /= 10; + digit = (int) (val / divisor); + mhd_assert (digit < 10); + } + return 0; /* The buffer is too small */ +} + + +MHD_INTERNAL size_t +mhd_uint8_to_str_pad (uint8_t val, + uint8_t min_digits, + char *buf, + size_t buf_size) +{ + size_t pos; /**< the position of the current printed digit */ + int digit; + mhd_assert (3 >= min_digits); + if (0 == buf_size) + return 0; + + pos = 0; + digit = val / 100; + if (0 == digit) + { + if (3 <= min_digits) + buf[pos++] = '0'; + } + else + { + buf[pos++] = (char) ('0' + (char) digit); + val %= 100; + min_digits = 2; + } + + if (buf_size <= pos) + return 0; + digit = val / 10; + if (0 == digit) + { + if (2 <= min_digits) + buf[pos++] = '0'; + } + else + { + buf[pos++] = (char) ('0' + (char) digit); + val %= 10; + } + + if (buf_size <= pos) + return 0; + buf[pos++] = (char) ('0' + (char) val); + return pos; +} + + +MHD_INTERNAL size_t +mhd_bin_to_hex (const void *restrict bin, + size_t size, + char *restrict hex) +{ + size_t i; + + for (i = 0; i < size; ++i) + { + uint8_t j; + const uint8_t b = ((const uint8_t *) bin)[i]; + j = b >> 4; + hex[i * 2] = (char) ((j < 10) ? (j + '0') : (j - 10 + 'a')); + j = b & 0x0f; + hex[i * 2 + 1] = (char) ((j < 10) ? (j + '0') : (j - 10 + 'a')); + } + return i * 2; +} + + +MHD_INTERNAL size_t +mhd_bin_to_hex_z (const void *restrict bin, + size_t size, + char *restrict hex) +{ + size_t res; + + res = mhd_bin_to_hex (bin, size, hex); + hex[res] = 0; + + return res; +} + + +MHD_INTERNAL size_t +mhd_hex_to_bin (const char *restrict hex, + size_t len, + void *restrict bin) +{ + size_t r; + size_t w; + + if (0 == len) + return 0; + r = 0; + w = 0; + if (0 != len % 2) + { + /* Assume the first byte is encoded with single digit */ + const int l = toxdigitvalue (hex[r++]); + if (0 > l) + return 0; + ((uint8_t *) bin)[w++] = (uint8_t) ((unsigned int) l); + } + while (r < len) + { + const int h = toxdigitvalue (hex[r++]); + const int l = toxdigitvalue (hex[r++]); + if ((0 > h) || (0 > l)) + return 0; + ((uint8_t *) bin)[w++] = (uint8_t) ( ((uint8_t) (((uint8_t) + ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l)) ); + } + mhd_assert (len == r); + mhd_assert ((len + 1) / 2 == w); + return w; +} + + +MHD_INTERNAL size_t +mhd_str_pct_decode_strict_n (const char *pct_encoded, + size_t pct_encoded_len, + char *decoded, + size_t buf_size) +{ +#ifdef MHD_FAVOR_SMALL_CODE + bool broken; + size_t res; + + res = mhd_str_pct_decode_lenient_n (pct_encoded, pct_encoded_len, decoded, + buf_size, &broken); + if (broken) + return 0; + return res; +#else /* ! MHD_FAVOR_SMALL_CODE */ + size_t r; + size_t w; + r = 0; + w = 0; + + if (buf_size >= pct_encoded_len) + { + while (r < pct_encoded_len) + { + const char chr = pct_encoded[r]; + if ('%' == chr) + { + if (2 > pct_encoded_len - r) + return 0; + else + { + const int h = toxdigitvalue (pct_encoded[++r]); + const int l = toxdigitvalue (pct_encoded[++r]); + unsigned char out; + if ((0 > h) || (0 > l)) + return 0; + out = + (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l))); + decoded[w] = (char) out; + } + } + else + decoded[w] = chr; + ++r; + ++w; + } + return w; + } + + while (r < pct_encoded_len) + { + const char chr = pct_encoded[r]; + if (w >= buf_size) + return 0; + if ('%' == chr) + { + if (2 > pct_encoded_len - r) + return 0; + else + { + const int h = toxdigitvalue (pct_encoded[++r]); + const int l = toxdigitvalue (pct_encoded[++r]); + unsigned char out; + if ((0 > h) || (0 > l)) + return 0; + out = + (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l))); + decoded[w] = (char) out; + } + } + else + decoded[w] = chr; + ++r; + ++w; + } + return w; +#endif /* ! MHD_FAVOR_SMALL_CODE */ +} + + +MHD_INTERNAL size_t +mhd_str_pct_decode_lenient_n (const char *pct_encoded, + size_t pct_encoded_len, + char *decoded, + size_t buf_size, + bool *broken_encoding) +{ + size_t r; + size_t w; + r = 0; + w = 0; + if (NULL != broken_encoding) + *broken_encoding = false; +#ifndef MHD_FAVOR_SMALL_CODE + if (buf_size >= pct_encoded_len) + { + while (r < pct_encoded_len) + { + const char chr = pct_encoded[r]; + if ('%' == chr) + { + if (2 > pct_encoded_len - r) + { + if (NULL != broken_encoding) + *broken_encoding = true; + decoded[w] = chr; /* Copy "as is" */ + } + else + { + const int h = toxdigitvalue (pct_encoded[++r]); + const int l = toxdigitvalue (pct_encoded[++r]); + unsigned char out; + if ((0 > h) || (0 > l)) + { + r -= 2; + if (NULL != broken_encoding) + *broken_encoding = true; + decoded[w] = chr; /* Copy "as is" */ + } + else + { + out = + (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l))); + decoded[w] = (char) out; + } + } + } + else + decoded[w] = chr; + ++r; + ++w; + } + return w; + } +#endif /* ! MHD_FAVOR_SMALL_CODE */ + while (r < pct_encoded_len) + { + const char chr = pct_encoded[r]; + if (w >= buf_size) + return 0; + if ('%' == chr) + { + if (2 > pct_encoded_len - r) + { + if (NULL != broken_encoding) + *broken_encoding = true; + decoded[w] = chr; /* Copy "as is" */ + } + else + { + const int h = toxdigitvalue (pct_encoded[++r]); + const int l = toxdigitvalue (pct_encoded[++r]); + if ((0 > h) || (0 > l)) + { + r -= 2; + if (NULL != broken_encoding) + *broken_encoding = true; + decoded[w] = chr; /* Copy "as is" */ + } + else + { + unsigned char out; + out = + (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l))); + decoded[w] = (char) out; + } + } + } + else + decoded[w] = chr; + ++r; + ++w; + } + return w; +} + + +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_strict (char *str) +{ +#ifdef MHD_FAVOR_SMALL_CODE + size_t res; + bool broken; + + res = mhd_str_pct_decode_in_place_lenient (str, &broken); + if (broken) + { + res = 0; + str[0] = 0; + } + return res; +#else /* ! MHD_FAVOR_SMALL_CODE */ + size_t r; + size_t w; + r = 0; + w = 0; + + while (0 != str[r]) + { + const char chr = str[r++]; + if ('%' == chr) + { + const char d1 = str[r++]; + if (0 == d1) + return 0; + else + { + const char d2 = str[r++]; + if (0 == d2) + return 0; + else + { + const int h = toxdigitvalue (d1); + const int l = toxdigitvalue (d2); + unsigned char out; + if ((0 > h) || (0 > l)) + return 0; + out = + (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l))); + str[w++] = (char) out; + } + } + } + else + str[w++] = chr; + } + str[w] = 0; + return w; +#endif /* ! MHD_FAVOR_SMALL_CODE */ +} + + +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_lenient (char *str, + bool *broken_encoding) +{ +#ifdef MHD_FAVOR_SMALL_CODE + size_t len; + size_t res; + + len = strlen (str); + res = mhd_str_pct_decode_lenient_n (str, len, str, len, broken_encoding); + str[res] = 0; + + return res; +#else /* ! MHD_FAVOR_SMALL_CODE */ + size_t r; + size_t w; + if (NULL != broken_encoding) + *broken_encoding = false; + r = 0; + w = 0; + while (0 != str[r]) + { + const char chr = str[r++]; + if ('%' == chr) + { + const char d1 = str[r++]; + if (0 == d1) + { + if (NULL != broken_encoding) + *broken_encoding = true; + str[w++] = chr; /* Copy "as is" */ + str[w] = 0; + return w; + } + else + { + const char d2 = str[r++]; + if (0 == d2) + { + if (NULL != broken_encoding) + *broken_encoding = true; + str[w++] = chr; /* Copy "as is" */ + str[w++] = d1; /* Copy "as is" */ + str[w] = 0; + return w; + } + else + { + const int h = toxdigitvalue (d1); + const int l = toxdigitvalue (d2); + unsigned char out; + if ((0 > h) || (0 > l)) + { + if (NULL != broken_encoding) + *broken_encoding = true; + str[w++] = chr; /* Copy "as is" */ + str[w++] = d1; + str[w++] = d2; + continue; + } + out = + (unsigned char) (((uint8_t) (((uint8_t) ((unsigned int) h)) << 4)) + | ((uint8_t) ((unsigned int) l))); + str[w++] = (char) out; + continue; + } + } + } + str[w++] = chr; + } + str[w] = 0; + return w; +#endif /* ! MHD_FAVOR_SMALL_CODE */ +} + + +#ifdef DAUTH_SUPPORT +MHD_INTERNAL bool +mhd_str_equal_quoted_bin_n (const char *quoted, + size_t quoted_len, + const char *unquoted, + size_t unquoted_len) +{ + size_t i; + size_t j; + if (unquoted_len < quoted_len / 2) + return false; + + j = 0; + for (i = 0; quoted_len > i && unquoted_len > j; ++i, ++j) + { + if ('\\' == quoted[i]) + { + i++; /* Advance to the next character */ + if (quoted_len == i) + return false; /* No character after escaping backslash */ + } + if (quoted[i] != unquoted[j]) + return false; /* Different characters */ + } + if ((quoted_len != i) || (unquoted_len != j)) + return false; /* The strings have different length */ + + return true; +} + + +MHD_INTERNAL bool +mhd_str_equal_caseless_quoted_bin_n (const char *quoted, + size_t quoted_len, + const char *unquoted, + size_t unquoted_len) +{ + size_t i; + size_t j; + if (unquoted_len < quoted_len / 2) + return false; + + j = 0; + for (i = 0; quoted_len > i && unquoted_len > j; ++i, ++j) + { + if ('\\' == quoted[i]) + { + i++; /* Advance to the next character */ + if (quoted_len == i) + return false; /* No character after escaping backslash */ + } + if (! charsequalcaseless (quoted[i], unquoted[j])) + return false; /* Different characters */ + } + if ((quoted_len != i) || (unquoted_len != j)) + return false; /* The strings have different length */ + + return true; +} + + +MHD_INTERNAL size_t +mhd_str_unquote (const char *quoted, + size_t quoted_len, + char *result) +{ + size_t r; + size_t w; + + r = 0; + w = 0; + + while (quoted_len > r) + { + if ('\\' == quoted[r]) + { + ++r; + if (quoted_len == r) + return 0; /* Last backslash is not followed by char to unescape */ + } + result[w++] = quoted[r++]; + } + return w; +} + + +#endif /* DAUTH_SUPPORT */ + +#if defined(DAUTH_SUPPORT) || defined(BAUTH_SUPPORT) + +MHD_INTERNAL size_t +mhd_str_quote (const char *unquoted, + size_t unquoted_len, + char *result, + size_t buf_size) +{ + size_t r; + size_t w; + + r = 0; + w = 0; + +#ifndef MHD_FAVOR_SMALL_CODE + if (unquoted_len * 2 <= buf_size) + { + /* Fast loop: the output will fit the buffer with any input string content */ + while (unquoted_len > r) + { + const char chr = unquoted[r++]; + if (('\\' == chr) || ('\"' == chr)) + result[w++] = '\\'; /* Escape current char */ + result[w++] = chr; + } + } + else + { + if (unquoted_len > buf_size) + return 0; /* Quick fail: the output buffer is too small */ +#else /* MHD_FAVOR_SMALL_CODE */ + if (1) + { +#endif /* MHD_FAVOR_SMALL_CODE */ + + while (unquoted_len > r) + { + if (buf_size <= w) + return 0; /* The output buffer is too small */ + else + { + const char chr = unquoted[r++]; + if (('\\' == chr) || ('\"' == chr)) + { + result[w++] = '\\'; /* Escape current char */ + if (buf_size <= w) + return 0; /* The output buffer is too small */ + } + result[w++] = chr; + } + } + } + + mhd_assert (w >= r); + mhd_assert (w <= r * 2); + return w; +} + + +#endif /* DAUTH_SUPPORT || BAUTH_SUPPORT */ + +#ifdef BAUTH_SUPPORT + +/* + * MHD_BASE64_FUNC_VERSION + * 1 = smallest, + * 2 = medium, + * 3 = fastest + */ +#ifndef MHD_BASE64_FUNC_VERSION +#ifdef MHD_FAVOR_SMALL_CODE +#define MHD_BASE64_FUNC_VERSION 1 +#else /* ! MHD_FAVOR_SMALL_CODE */ +#define MHD_BASE64_FUNC_VERSION 3 +#endif /* ! MHD_FAVOR_SMALL_CODE */ +#endif /* ! MHD_BASE64_FUNC_VERSION */ + +#if MHD_BASE64_FUNC_VERSION < 1 || MHD_BASE64_FUNC_VERSION > 3 +#error Wrong MHD_BASE64_FUNC_VERSION value +#endif /* MHD_BASE64_FUNC_VERSION < 1 || MHD_BASE64_FUNC_VERSION > 3 */ + +#if MHD_BASE64_FUNC_VERSION == 3 +#define mhd_base64_map_type int +#else /* MHD_BASE64_FUNC_VERSION < 3 */ +#define mhd_base64_map_type int8_t +#endif /* MHD_BASE64_FUNC_VERSION < 3 */ + +#if MHD_BASE64_FUNC_VERSION == 1 +static mhd_base64_map_type +base64_char_to_value_ (uint8_t c) +{ + if ('Z' >= c) + { + if ('A' <= c) + return (mhd_base64_map_type) ((c - 'A') + 0); + if ('0' <= c) + { + if ('9' >= c) + return (mhd_base64_map_type) ((c - '0') + 52); + if ('=' == c) + return -2; + return -1; + } + if ('+' == c) + return 62; + if ('/' == c) + return 63; + return -1; + } + if (('z' >= c) && ('a' <= c)) + return (mhd_base64_map_type) ((c - 'a') + 26); + return -1; +} + + +#endif /* MHD_BASE64_FUNC_VERSION == 1 */ + + +MHD_DATA_TRUNCATION_RUNTIME_CHECK_DISABLE_ + + +MHD_INTERNAL size_t +mhd_base64_to_bin_n (const char *base64, + size_t base64_len, + void *bin, + size_t bin_size) +{ +#if MHD_BASE64_FUNC_VERSION >= 2 + static const mhd_base64_map_type map[] = { + /* -1 = invalid char, -2 = padding + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, */ + -1, -1, -1, -1, -1, -1, -1, -1, + /* + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + BS, HT, LF, VT, FF, CR, SO, SI, */ + -1, -1, -1, -1, -1, -1, -1, -1, + /* + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, */ + -1, -1, -1, -1, -1, -1, -1, -1, + /* + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + CAN, EM, SUB, ESC, FS, GS, RS, US, */ + -1, -1, -1, -1, -1, -1, -1, -1, + /* + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + ' ', '!', '"', '#', '$', '%', '&', '\'', */ + -1, -1, -1, -1, -1, -1, -1, -1, + /* + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + '(', ')', '*', '+', ',', '-', '.', '/', */ + -1, -1, -1, 62, -1, -1, -1, 63, + /* + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + '0', '1', '2', '3', '4', '5', '6', '7', */ + 52, 53, 54, 55, 56, 57, 58, 59, + /* + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + '8', '9', ':', ';', '<', '=', '>', '?', */ + 60, 61, -1, -1, -1, -2, -1, -1, + /* + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', */ + -1, 0, 1, 2, 3, 4, 5, 6, + /* + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', */ + 7, 8, 9, 10, 11, 12, 13, 14, + /* + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', */ + 15, 16, 17, 18, 19, 20, 21, 22, + /* + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 'X', 'Y', 'Z', '[', '\', ']', '^', '_', */ + 23, 24, 25, -1, -1, -1, -1, -1, + /* + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', */ + -1, 26, 27, 28, 29, 30, 31, 32, + /* + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', */ + 33, 34, 35, 36, 37, 38, 39, 40, + /* + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', */ + 41, 42, 43, 44, 45, 46, 47, 48, + /* + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 'x', 'y', 'z', '{', '|', '}', '~', DEL, */ + 49, 50, 51, -1, -1, -1, -1, -1 + +#if MHD_BASE64_FUNC_VERSION == 3 + , + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80..8F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90..9F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A0..AF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* B0..BF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* C0..CF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* D0..DF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* E0..EF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* F0..FF */ +#endif /* ! MHD_BASE64_FUNC_VERSION == 3 */ + }; +#define base64_char_to_value_(c) map[(c)] +#endif /* MHD_BASE64_FUNC_VERSION >= 2 */ + const uint8_t *const in = (const uint8_t *) base64; + uint8_t *const out = (uint8_t *) bin; + size_t i; + size_t j; + if (0 == base64_len) + return 0; /* Nothing to decode */ + if (0 != base64_len % 4) + return 0; /* Wrong input length */ + if (base64_len / 4 * 3 - 2 > bin_size) + return 0; + + j = 0; + for (i = 0; i < (base64_len - 4); i += 4) + { +#if MHD_BASE64_FUNC_VERSION == 2 + if (0 != (0x80 & (in[i] | in[i + 1] | in[i + 2] | in[i + 3]))) + return 0; +#endif /* MHD_BASE64_FUNC_VERSION == 2 */ + if (1) + { + const mhd_base64_map_type v1 = base64_char_to_value_ (in[i + 0]); + const mhd_base64_map_type v2 = base64_char_to_value_ (in[i + 1]); + const mhd_base64_map_type v3 = base64_char_to_value_ (in[i + 2]); + const mhd_base64_map_type v4 = base64_char_to_value_ (in[i + 3]); + if ((0 > v1) || (0 > v2) || (0 > v3) || (0 > v4)) + return 0; + out[j + 0] = (uint8_t) (((uint8_t) (((uint8_t) v1) << 2)) + | ((uint8_t) (((uint8_t) v2) >> 4))); + out[j + 1] = (uint8_t) (((uint8_t) (((uint8_t) v2) << 4)) + | ((uint8_t) (((uint8_t) v3) >> 2))); + out[j + 2] = (uint8_t) (((uint8_t) (((uint8_t) v3) << 6)) + | ((uint8_t) v4)); + } + j += 3; + } +#if MHD_BASE64_FUNC_VERSION == 2 + if (0 != (0x80 & (in[i] | in[i + 1] | in[i + 2] | in[i + 3]))) + return 0; +#endif /* MHD_BASE64_FUNC_VERSION == 2 */ + if (1) + { /* The last four chars block */ + const mhd_base64_map_type v1 = base64_char_to_value_ (in[i + 0]); + const mhd_base64_map_type v2 = base64_char_to_value_ (in[i + 1]); + const mhd_base64_map_type v3 = base64_char_to_value_ (in[i + 2]); + const mhd_base64_map_type v4 = base64_char_to_value_ (in[i + 3]); + if ((0 > v1) || (0 > v2)) + return 0; /* Invalid char or padding at first two positions */ + mhd_assert (j < bin_size); + out[j++] = (uint8_t) (((uint8_t) (((uint8_t) v1) << 2)) + | ((uint8_t) (((uint8_t) v2) >> 4))); + if (0 > v3) + { /* Third char is either padding or invalid */ + if ((-2 != v3) || (-2 != v4)) + return 0; /* Both two last chars must be padding */ + if (0 != (uint8_t) (((uint8_t) v2) << 4)) + return 0; /* Wrong last char */ + return j; + } + if (j >= bin_size) + return 0; /* Not enough space */ + out[j++] = (uint8_t) (((uint8_t) (((uint8_t) v2) << 4)) + | ((uint8_t) (((uint8_t) v3) >> 2))); + if (0 > v4) + { /* Fourth char is either padding or invalid */ + if (-2 != v4) + return 0; /* The char must be padding */ + if (0 != (uint8_t) (((uint8_t) v3) << 6)) + return 0; /* Wrong last char */ + return j; + } + if (j >= bin_size) + return 0; /* Not enough space */ + out[j++] = (uint8_t) (((uint8_t) (((uint8_t) v3) << 6)) + | ((uint8_t) v4)); + } + return j; +#if MHD_BASE64_FUNC_VERSION >= 2 +#undef base64_char_to_value_ +#endif /* MHD_BASE64_FUNC_VERSION >= 2 */ +} + + +MHD_DATA_TRUNCATION_RUNTIME_CHECK_RESTORE_ + + +#undef mhd_base64_map_type + +#endif /* BAUTH_SUPPORT */ diff --git a/src/mhd2/mhd_str.h b/src/mhd2/mhd_str.h @@ -0,0 +1,769 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2015-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file src/mhd2/mhd_str.h + * @brief Header for string manipulating helpers + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STR_H +#define MHD_STR_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" +#include "sys_bool_type.h" +#include "mhd_str_macros.h" +#ifdef MHD_FAVOR_SMALL_CODE +# include "mhd_limits.h" +#endif +/* + * Block of functions/macros that use US-ASCII charset as required by HTTP + * standards. Not affected by current locale settings. + */ + +#ifndef MHD_FAVOR_SMALL_CODE +/** + * Check two strings for equality, ignoring case of US-ASCII letters. + * + * @param str1 first string to compare + * @param str2 second string to compare + * @return 'true' if two strings are equal, 'false' otherwise. + */ +MHD_INTERNAL bool +mhd_str_equal_caseless (const char *str1, + const char *str2); + +#else /* MHD_FAVOR_SMALL_CODE */ +/* Reuse mhd_str_equal_caseless_n() to reduce size */ +#define mhd_str_equal_caseless(s1,s2) \ + mhd_str_equal_caseless_n ((s1),(s2), SIZE_MAX) +#endif /* MHD_FAVOR_SMALL_CODE */ + + +/** + * Check two string for equality, ignoring case of US-ASCII letters and + * checking not more than @a maxlen characters. + * Compares up to first terminating null character, but not more than + * first @a maxlen characters. + * @param str1 first string to compare + * @param str2 second string to compare + * @param maxlen maximum number of characters to compare + * @return 'true' if two strings are equal, 'false' otherwise. + */ +MHD_INTERNAL bool +mhd_str_equal_caseless_n (const char *const str1, + const char *const str2, + size_t maxlen); + + +/** + * Check two string for equality, ignoring case of US-ASCII letters and + * checking not more than @a len bytes. + * Compares not more first than @a len bytes, including binary zero characters. + * Comparison stops at first unmatched byte. + * @param str1 first string to compare + * @param str2 second string to compare + * @param len number of characters to compare + * @return 'true' if two strings are equal, 'false' otherwise. + */ +MHD_INTERNAL bool +mhd_str_equal_caseless_bin_n (const char *const str1, + const char *const str2, + size_t len); + + +/** + * Check whether string is equal statically allocated another string, + * ignoring case of US-ASCII letters and checking not more than @a len bytes. + * + * If strings have different sizes (lengths) then macro returns boolean false + * without checking the content. + * + * Compares not more first than @a len bytes, including binary zero characters. + * Comparison stops at first unmatched byte. + * @param a the statically allocated string to compare + * @param s the string to compare + * @param l the number of characters in the @a s string + * @return 'true' if two strings are equal, 'false' otherwise. + */ +#define mhd_str_equal_caseless_n_st(a,s,l) \ + ((mhd_SSTR_LEN (a) == (l)) \ + && mhd_str_equal_caseless_bin_n (a,s,l)) + +/** + * Check whether @a str has case-insensitive @a token. + * Token could be surrounded by spaces and tabs and delimited by comma. + * Match succeed if substring between start, end (of string) or comma + * contains only case-insensitive token and optional spaces and tabs. + * @warning token must not contain null-characters except optional + * terminating null-character. + * @param str the string to check + * @param token the token to find + * @param token_len length of token, not including optional terminating + * null-character. + * @return non-zero if two strings are equal, zero otherwise. + */ +MHD_INTERNAL bool +mhd_str_has_token_caseless (const char *restrict str, + const char *const token, + size_t token_len); + +/** + * Check whether @a str has case-insensitive static @a tkn. + * Token could be surrounded by spaces and tabs and delimited by comma. + * Match succeed if substring between start, end of string or comma + * contains only case-insensitive token and optional spaces and tabs. + * @warning tkn must be static string + * @param str the string to check + * @param tkn the static string of token to find + * @return non-zero if two strings are equal, zero otherwise. + */ +#define mhd_str_has_s_token_caseless(str,tkn) \ + mhd_str_has_token_caseless ((str),(tkn),mhd_SSTR_LEN (tkn)) + + +/** + * Remove case-insensitive @a token from the @a str and put result + * to the output @a buf. + * + * Tokens in @a str could be surrounded by spaces and tabs and delimited by + * comma. The token match succeed if substring between start, end (of string) + * or comma contains only case-insensitive token and optional spaces and tabs. + * The quoted strings and comments are not supported by this function. + * + * The output string is normalised: empty tokens and repeated whitespaces + * are removed, no whitespaces before commas, exactly one space is used after + * each comma. + * + * @param str the string to process + * @param str_len the length of the @a str, not including optional + * terminating null-character. + * @param token the token to find + * @param token_len the length of @a token, not including optional + * terminating null-character. + * @param[out] buf the output buffer, not null-terminated. + * @param[in,out] buf_size pointer to the size variable, at input it + * is the size of allocated buffer, at output + * it is the size of the resulting string (can + * be up to 50% larger than input) or negative value + * if there is not enough space for the result + * @return 'true' if token has been removed, + * 'false' otherwise. + */ +MHD_INTERNAL bool +mhd_str_remove_token_caseless (const char *restrict str, + size_t str_len, + const char *const restrict token, + const size_t token_len, + char *restrict buf, + ssize_t *restrict buf_size); + + +/** + * Perform in-place case-insensitive removal of @a tokens from the @a str. + * + * Token could be surrounded by spaces and tabs and delimited by comma. + * The token match succeed if substring between start, end (of the string), or + * comma contains only case-insensitive token and optional spaces and tabs. + * The quoted strings and comments are not supported by this function. + * + * The input string must be normalised: empty tokens and repeated whitespaces + * are removed, no whitespaces before commas, exactly one space is used after + * each comma. The string is updated in-place. + * + * Behavior is undefined is the input string in not normalised. + * + * @param[in,out] str the string to update + * @param[in,out] str_len the length of the @a str, not including optional + * terminating null-character, not null-terminated + * @param tokens the token to find + * @param tokens_len the length of @a tokens, not including optional + * terminating null-character. + * @return 'true' if any token has been removed, + * 'false' otherwise. + */ +MHD_INTERNAL bool +mhd_str_remove_tokens_caseless (char *restrict str, + size_t *restrict str_len, + const char *const restrict tkns, + const size_t tokens_len); + + +#ifndef MHD_FAVOR_SMALL_CODE +/* Use individual function for each case to improve speed */ + +/** + * Convert decimal US-ASCII digits in string to number in uint_fast64_t. + * Conversion stopped at first non-digit character. + * + * @param str string to convert + * @param[out] out_val pointer to uint_fast64_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast64_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_str_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val); + +/** + * Convert not more then @a maxlen decimal US-ASCII digits in string to + * number in uint_fast64_t. + * Conversion stopped at first non-digit character or after @a maxlen + * digits. + * + * @param str string to convert + * @param maxlen maximum number of characters to process + * @param[out] out_val pointer to uint_fast64_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast64_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_str_to_uint64_n (const char *restrict str, + size_t maxlen, + uint_fast64_t *restrict out_val); + + +/** + * Convert hexadecimal US-ASCII digits in string to number in uint_fast32_t. + * Conversion stopped at first non-digit character. + * + * @param str string to convert + * @param[out] out_val pointer to uint_fast32_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast32_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_strx_to_uint32 (const char *restrict str, + uint_fast32_t *restrict out_val); + + +/** + * Convert not more then @a maxlen hexadecimal US-ASCII digits in string + * to number in uint_fast32_t. + * Conversion stopped at first non-digit character or after @a maxlen + * digits. + * + * @param str string to convert + * @param maxlen maximum number of characters to process + * @param[out] out_val pointer to uint_fast32_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast32_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_strx_to_uint32_n (const char *restrict str, + size_t maxlen, + uint_fast32_t *restrict out_val); + + +/** + * Convert hexadecimal US-ASCII digits in string to number in uint_fast64_t. + * Conversion stopped at first non-digit character. + * + * @param str string to convert + * @param[out] out_val pointer to uint_fast64_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast64_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_strx_to_uint64 (const char *restrict str, + uint_fast64_t *restrict out_val); + + +/** + * Convert not more then @a maxlen hexadecimal US-ASCII digits in string + * to number in uint_fast64_t. + * Conversion stopped at first non-digit character or after @a maxlen + * digits. + * + * @param str string to convert + * @param maxlen maximum number of characters to process + * @param[out] out_val pointer to uint_fast64_t to store result of conversion + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then possible to store in uint_fast64_t or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_strx_to_uint64_n (const char *restrict str, + size_t maxlen, + uint_fast64_t *restrict out_val); + +#else /* MHD_FAVOR_SMALL_CODE */ +/* Use one universal function and macros to reduce size */ + +/** + * Generic function for converting not more then @a maxlen + * hexadecimal or decimal US-ASCII digits in string to number. + * Conversion stopped at first non-digit character or after @a maxlen + * digits. + * To be used only within macro. + * + * @param str the string to convert + * @param maxlen the maximum number of characters to process + * @param out_val the pointer to variable to store result of conversion + * @param val_size the size of variable pointed by @a out_val, in bytes, 4 or 8 + * @param max_val the maximum decoded number + * @param base the numeric base, 10 or 16 + * @return non-zero number of characters processed on succeed, + * zero if no digit is found, resulting value is larger + * then @a max_val, @a val_size is not 4/8 or @a out_val is NULL + */ +MHD_INTERNAL size_t +mhd_str_to_uvalue_n (const char *restrict str, + size_t maxlen, + void *restrict out_val, + size_t val_size, + uint_fast64_t max_val, + unsigned int base); + +#define mhd_str_to_uint64(s,ov) \ + mhd_str_to_uvalue_n ((s),SIZE_MAX,(ov), \ + sizeof(uint_fast64_t), \ + UINT64_MAX,10) + +#define mhd_str_to_uint64_n(s,ml,ov) \ + mhd_str_to_uvalue_n ((s),(ml),(ov), \ + sizeof(uint_fast64_t), \ + UINT64_MAX,10) + +#define mhd_strx_to_sizet(s,ov) \ + mhd_str_to_uvalue_n ((s),SIZE_MAX,(ov), \ + sizeof(size_t),SIZE_MAX, \ + 16) + +#define mhd_strx_to_sizet_n(s,ml,ov) \ + mhd_str_to_uvalue_n ((s),(ml),(ov), \ + sizeof(size_t), \ + SIZE_MAX,16) + +#define mhd_strx_to_uint32(s,ov) \ + mhd_str_to_uvalue_n ((s),SIZE_MAX,(ov), \ + sizeof(uint_fast32_t), \ + UINT32_MAX,16) + +#define mhd_strx_to_uint32_n(s,ml,ov) \ + mhd_str_to_uvalue_n ((s),(ml),(ov), \ + sizeof(uint_fast32_t), \ + UINT32_MAX,16) + +#define mhd_strx_to_uint64(s,ov) \ + mhd_str_to_uvalue_n ((s),SIZE_MAX,(ov), \ + sizeof(uint_fast64_t), \ + UINT64_MAX,16) + +#define mhd_strx_to_uint64_n(s,ml,ov) \ + mhd_str_to_uvalue_n ((s),(ml),(ov), \ + sizeof(uint_fast64_t), \ + UINT64_MAX,16) + +#endif /* MHD_FAVOR_SMALL_CODE */ + + +/** + * Convert uint_fast32_t value to hexdecimal US-ASCII string. + * @note: result is NOT zero-terminated. + * @param val the value to convert + * @param buf the buffer to result to + * @param buf_size size of the @a buffer + * @return number of characters has been put to the @a buf, + * zero if buffer is too small (buffer may be modified). + */ +MHD_INTERNAL size_t +mhd_uint32_to_strx (uint_fast32_t val, + char *buf, + size_t buf_size); + + +#ifndef MHD_FAVOR_SMALL_CODE +/** + * Convert uint_least16_t value to decimal US-ASCII string. + * @note: result is NOT zero-terminated. + * @param val the value to convert + * @param buf the buffer to result to + * @param buf_size size of the @a buffer + * @return number of characters has been put to the @a buf, + * zero if buffer is too small (buffer may be modified). + */ +MHD_INTERNAL size_t +mhd_uint16_to_str (uint_least16_t val, + char *buf, + size_t buf_size); + +#else /* MHD_FAVOR_SMALL_CODE */ +#define mhd_uint16_to_str(v,b,s) mhd_uint64_to_str (v,b,s) +#endif /* MHD_FAVOR_SMALL_CODE */ + + +/** + * Convert uint_fast64_t value to decimal US-ASCII string. + * @note: result is NOT zero-terminated. + * @param val the value to convert + * @param buf the buffer to result to + * @param buf_size size of the @a buffer + * @return number of characters has been put to the @a buf, + * zero if buffer is too small (buffer may be modified). + */ +MHD_INTERNAL size_t +mhd_uint64_to_str (uint_fast64_t val, + char *buf, + size_t buf_size); + + +/** + * Convert uint_least16_t value to decimal US-ASCII string padded with + * zeros on the left side. + * + * @note: result is NOT zero-terminated. + * @param val the value to convert + * @param min_digits the minimal number of digits to print, + * output padded with zeros on the left side, + * 'zero' value is interpreted as 'one', + * valid values are 3, 2, 1, 0 + * @param buf the buffer to result to + * @param buf_size size of the @a buffer + * @return number of characters has been put to the @a buf, + * zero if buffer is too small (buffer may be modified). + */ +MHD_INTERNAL size_t +mhd_uint8_to_str_pad (uint8_t val, + uint8_t min_digits, + char *buf, + size_t buf_size); + + +/** + * Convert @a size bytes from input binary data to lower case + * hexadecimal digits. + * Result is NOT zero-terminated + * @param bin the pointer to the binary data to convert + * @param size the size in bytes of the binary data to convert + * @param[out] hex the output buffer, should be at least 2 * @a size + * @return The number of characters written to the output buffer. + */ +MHD_INTERNAL size_t +mhd_bin_to_hex (const void *restrict bin, + size_t size, + char *restrict hex); + +/** + * Convert @a size bytes from input binary data to lower case + * hexadecimal digits, zero-terminate the result. + * @param bin the pointer to the binary data to convert + * @param size the size in bytes of the binary data to convert + * @param[out] hex the output buffer, should be at least 2 * @a size + 1 + * @return The number of characters written to the output buffer, + * not including terminating zero. + */ +MHD_INTERNAL size_t +mhd_bin_to_hex_z (const void *restrict bin, + size_t size, + char *restrict hex); + +/** + * Convert hexadecimal digits to binary data. + * + * The input decoded byte-by-byte (each byte is two hexadecimal digits). + * If length is an odd number, extra leading zero is assumed. + * + * @param hex the input string with hexadecimal digits + * @param len the length of the input string + * @param[out] bin the output buffer, must be at least len/2 bytes long (or + * len/2 + 1 if @a len is not even number) + * @return the number of bytes written to the output buffer, + * zero if found any character which is not hexadecimal digits + */ +MHD_INTERNAL size_t +mhd_hex_to_bin (const char *restrict hex, + size_t len, + void *restrict bin); + +/** + * Decode string with percent-encoded characters as defined by + * RFC 3986 #section-2.1. + * + * This function decode string by converting percent-encoded characters to + * their decoded versions and copying all other characters without extra + * processing. + * + * @param pct_encoded the input string to be decoded + * @param pct_encoded_len the length of the @a pct_encoded + * @param[out] decoded the output buffer, NOT zero-terminated, can point + * to the same buffer as @a pct_encoded + * @param buf_size the size of the output buffer + * @return the number of characters written to the output buffer or + * zero if any percent-encoded characters is broken ('%' followed + * by less than two hexadecimal digits) or output buffer is too + * small to hold the result + */ +MHD_INTERNAL size_t +mhd_str_pct_decode_strict_n (const char *pct_encoded, + size_t pct_encoded_len, + char *decoded, + size_t buf_size); + +/** + * Decode string with percent-encoded characters as defined by + * RFC 3986 #section-2.1. + * + * This function decode string by converting percent-encoded characters to + * their decoded versions and copying all other characters without extra + * processing. + * + * Any invalid percent-encoding sequences ('%' symbol not followed by two + * valid hexadecimal digits) are copied to the output string without decoding. + * + * @param pct_encoded the input string to be decoded + * @param pct_encoded_len the length of the @a pct_encoded + * @param[out] decoded the output buffer, NOT zero-terminated, can point + * to the same buffer as @a pct_encoded + * @param buf_size the size of the output buffer + * @param[out] broken_encoding will be set to true if any '%' symbol is not + * followed by two valid hexadecimal digits, + * optional, can be NULL + * @return the number of characters written to the output buffer or + * zero if output buffer is too small to hold the result + */ +MHD_INTERNAL size_t +mhd_str_pct_decode_lenient_n (const char *pct_encoded, + size_t pct_encoded_len, + char *decoded, + size_t buf_size, + bool *broken_encoding); + + +/** + * Decode string in-place with percent-encoded characters as defined by + * RFC 3986 #section-2.1. + * + * This function decode string by converting percent-encoded characters to + * their decoded versions and copying back all other characters without extra + * processing. + * + * @param[in,out] str the string to be updated in-place, must be zero-terminated + * on input, the output is zero-terminated; the string is + * truncated to zero length if broken encoding is found + * @return the number of character in decoded string + */ +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_strict (char *str); + + +/** + * Decode string in-place with percent-encoded characters as defined by + * RFC 3986 #section-2.1. + * + * This function decode string by converting percent-encoded characters to + * their decoded versions and copying back all other characters without extra + * processing. + * + * Any invalid percent-encoding sequences ('%' symbol not followed by two + * valid hexadecimal digits) are copied to the output string without decoding. + * + * @param[in,out] str the string to be updated in-place, must be zero-terminated + * on input, the output is zero-terminated + * @param[out] broken_encoding will be set to true if any '%' symbol is not + * followed by two valid hexadecimal digits, + * optional, can be NULL + * @return the number of character in decoded string + */ +MHD_INTERNAL size_t +mhd_str_pct_decode_in_place_lenient (char *str, + bool *broken_encoding); + +#ifdef DAUTH_SUPPORT +/** + * Check two strings for equality, "unquoting" the first string from quoted + * form as specified by RFC7230#section-3.2.6 and RFC7694#quoted.strings. + * + * Null-termination for input strings is not required, binary zeros compared + * like other characters. + * + * @param quoted the quoted string to compare, must NOT include leading and + * closing DQUOTE chars, does not need to be zero-terminated + * @param quoted_len the length in chars of the @a quoted string + * @param unquoted the unquoted string to compare, does not need to be + * zero-terminated + * @param unquoted_len the length in chars of the @a unquoted string + * @return zero if quoted form is broken (no character after the last escaping + * backslash), zero if strings are not equal after unquoting of the + * first string, + * non-zero if two strings are equal after unquoting of the + * first string. + */ +MHD_INTERNAL bool +mhd_str_equal_quoted_bin_n (const char *quoted, + size_t quoted_len, + const char *unquoted, + size_t unquoted_len); + +/** + * Check whether the string after "unquoting" equals static string. + * + * Null-termination for input string is not required, binary zeros compared + * like other characters. + * + * @param q the quoted string to compare, must NOT include leading and + * closing DQUOTE chars, does not need to be zero-terminated + * @param l the length in chars of the @a q string + * @param u the unquoted static string to compare + * @return zero if quoted form is broken (no character after the last escaping + * backslash), zero if strings are not equal after unquoting of the + * first string, + * non-zero if two strings are equal after unquoting of the + * first string. + */ +#define mhd_str_equal_quoted_s_bin_n(q,l,u) \ + mhd_str_equal_quoted_bin_n (q,l,u,mhd_SSTR_LEN (u)) + +/** + * Check two strings for equality, "unquoting" the first string from quoted + * form as specified by RFC7230#section-3.2.6 and RFC7694#quoted.strings and + * ignoring case of US-ASCII letters. + * + * Null-termination for input strings is not required, binary zeros compared + * like other characters. + * + * @param quoted the quoted string to compare, must NOT include leading and + * closing DQUOTE chars, does not need to be zero-terminated + * @param quoted_len the length in chars of the @a quoted string + * @param unquoted the unquoted string to compare, does not need to be + * zero-terminated + * @param unquoted_len the length in chars of the @a unquoted string + * @return zero if quoted form is broken (no character after the last escaping + * backslash), zero if strings are not equal after unquoting of the + * first string, + * non-zero if two strings are caseless equal after unquoting of the + * first string. + */ +MHD_INTERNAL bool +mhd_str_equal_caseless_quoted_bin_n (const char *quoted, + size_t quoted_len, + const char *unquoted, + size_t unquoted_len); + +/** + * Check whether the string after "unquoting" equals static string, ignoring + * case of US-ASCII letters. + * + * Null-termination for input string is not required, binary zeros compared + * like other characters. + * + * @param q the quoted string to compare, must NOT include leading and + * closing DQUOTE chars, does not need to be zero-terminated + * @param l the length in chars of the @a q string + * @param u the unquoted static string to compare + * @return zero if quoted form is broken (no character after the last escaping + * backslash), zero if strings are not equal after unquoting of the + * first string, + * non-zero if two strings are caseless equal after unquoting of the + * first string. + */ +#define mhd_str_equal_caseless_quoted_s_bin_n(q,l,u) \ + mhd_str_equal_caseless_quoted_bin_n (q,l,u,mhd_SSTR_LEN (u)) + +/** + * Convert string from quoted to unquoted form as specified by + * RFC7230#section-3.2.6 and RFC7694#quoted.strings. + * + * @param quoted the quoted string, must NOT include leading and closing + * DQUOTE chars, does not need to be zero-terminated + * @param quoted_len the length in chars of the @a quoted string + * @param[out] result the pointer to the buffer to put the result, must + * be at least @a size character long. May be modified even + * if @a quoted is invalid sequence. The result is NOT + * zero-terminated. + * @return The number of characters written to the output buffer, + * zero if last backslash is not followed by any character (or + * @a quoted_len is zero). + */ +MHD_INTERNAL size_t +mhd_str_unquote (const char *quoted, + size_t quoted_len, + char *result); + +#endif /* DAUTH_SUPPORT */ + +#if defined(DAUTH_SUPPORT) || defined(BAUTH_SUPPORT) + +/** + * Convert string from unquoted to quoted form as specified by + * RFC7230#section-3.2.6 and RFC7694#quoted.strings. + * + * @param unquoted the unquoted string, does not need to be zero-terminated + * @param unquoted_len the length in chars of the @a unquoted string + * @param[out] result the pointer to the buffer to put the result. May be + * modified even if function failed due to insufficient + * space. The result is NOT zero-terminated and does not + * have opening and closing DQUOTE chars. + * @param buf_size the size of the allocated memory for @a result + * @return The number of copied characters, can be up to two times more than + * @a unquoted_len, zero if @a unquoted_len is zero or if quoted + * string is larger than @a buf_size. + */ +MHD_INTERNAL size_t +mhd_str_quote (const char *unquoted, + size_t unquoted_len, + char *result, + size_t buf_size); + +#endif /* DAUTH_SUPPORT || BAUTH_SUPPORT */ + +#ifdef BAUTH_SUPPORT + +/** + * Returns the maximum possible size of the Base64 decoded data. + * The real recoded size could be up to two bytes smaller. + * @param enc_size the size of encoded data, in characters + * @return the maximum possible size of the decoded data, in bytes, if + * @a enc_size is valid (properly padded), + * undefined value smaller then @a enc_size if @a enc_size is not valid + */ +#define mhd_base64_max_dec_size(enc_size) (((enc_size) / 4) * 3) + +/** + * Convert Base64 encoded string to binary data. + * @param base64 the input string with Base64 encoded data, could be NOT zero + * terminated + * @param base64_len the number of characters to decode in @a base64 string, + * valid number must be a multiple of four + * @param[out] bin the pointer to the output buffer, the buffer may be altered + * even if decoding failed + * @param bin_size the size of the @a bin buffer in bytes, if the size is + * at least @a base64_len / 4 * 3 then result will always + * fit, regardless of the amount of the padding characters + * @return 0 if @a base64_len is zero, or input string has wrong data (not + * valid Base64 sequence), or @a bin_size is too small; + * non-zero number of bytes written to the @a bin, the number must be + * (base64_len / 4 * 3 - 2), (base64_len / 4 * 3 - 1) or + * (base64_len / 4 * 3), depending on the number of padding characters. + */ +MHD_INTERNAL size_t +mhd_base64_to_bin_n (const char *base64, + size_t base64_len, + void *bin, + size_t bin_size); + +#endif /* BAUTH_SUPPORT */ + +#endif /* MHD_STR_H */ diff --git a/src/mhd2/mhd_str_macros.h b/src/mhd2/mhd_str_macros.h @@ -0,0 +1,44 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_str_macros.h + * @brief The definition of the MHD_String helper macros + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STR_MACROS_H +#define MHD_STR_MACROS_H 1 + +#include "mhd_sys_options.h" + +/** + * The length of static string, not including terminating zero. + * Can be used with char[] arrays. + */ +#define mhd_SSTR_LEN(sstr) (sizeof(sstr) / sizeof(char) - 1) + +/** + * The initialiser for the struct MHD_String + */ +#define mhd_MSTR_INIT(sstr) { mhd_SSTR_LEN (sstr), (sstr)} + + +#endif /* ! MHD_STR_MACROS_H */ diff --git a/src/mhd2/mhd_str_types.h b/src/mhd2/mhd_str_types.h @@ -0,0 +1,76 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_str_types.h + * @brief The definition of the tristate type and helper macros + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STR_TYPES_H +#define MHD_STR_TYPES_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_STRINGS_DEFINED +#include "sys_base_types.h" + + +/** + * String with length data. + * This type should always have valid @a cstr pointer. + */ +struct MHD_String +{ + /** + * Number of characters in @e str, not counting 0-termination. + */ + size_t len; + + /** + * 0-terminated C-string. + * Must not be NULL. + */ + const char *cstr; +}; + +/** + * String with length data. + * This type of data may have NULL as the @a cstr pointer. + */ +struct MHD_StringNullable +{ + /** + * Number of characters in @e str, not counting 0-termination. + * If @a cstr is NULL, it must be zero. + */ + size_t len; + + /** + * 0-terminated C-string. + * In some cases it could be NULL. + */ + const char *cstr; +}; + +#define MHD_STRINGS_DEFINED 1 +#endif /* ! MHD_STRINGS_DEFINED */ + +#endif /* ! MHD_STR_TYPES_H */ diff --git a/src/mhd2/mhd_threads.c b/src/mhd2/mhd_threads.c @@ -0,0 +1,420 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016-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 microhttpd/mhd_threads.c + * @brief Implementation for thread functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_threads.h" +#include "sys_null_macro.h" +#ifdef MHD_USE_W32_THREADS +# include <process.h> +#endif +#if defined(MHD_USE_THREAD_NAME_) +# if ! defined(MHD_USE_THREAD_ATTR_SETNAME) +# include "sys_malloc.h" +# endif +# ifdef HAVE_PTHREAD_NP_H +# include <pthread_np.h> +# endif /* HAVE_PTHREAD_NP_H */ +#endif /* MHD_USE_THREAD_NAME_ */ +#include "sys_errno.h" +#include "mhd_assert.h" + +#ifndef MHD_USE_THREAD_NAME_ + +# define mhd_set_thread_name(t, n) (void) +# define mhd_set_cur_thread_name(n) (void) + +#else /* MHD_USE_THREAD_NAME_ */ + +# if defined(MHD_USE_POSIX_THREADS) +# if defined(HAVE_PTHREAD_ATTR_SETNAME_NP_NETBSD) || \ + defined(HAVE_PTHREAD_ATTR_SETNAME_NP_IBMI) +# define MHD_USE_THREAD_ATTR_SETNAME 1 +# endif /* HAVE_PTHREAD_ATTR_SETNAME_NP_NETBSD || \ + HAVE_PTHREAD_ATTR_SETNAME_NP_IBMI */ + +# if defined(HAVE_PTHREAD_SETNAME_NP_GNU) || \ + defined(HAVE_PTHREAD_SET_NAME_NP_FREEBSD) \ + || defined(HAVE_PTHREAD_SETNAME_NP_NETBSD) + +/** + * Set thread name + * + * @param thread_id ID of thread + * @param thread_name name to set + * @return true on success, false otherwise + */ +static bool +mhd_set_thread_name (const mhd_thread_ID_native thread_id, + const char *thread_name) +{ + if (NULL == thread_name) + return false; + +# if defined(HAVE_PTHREAD_SETNAME_NP_GNU) + return 0 == pthread_setname_np (thread_id, thread_name); +# elif defined(HAVE_PTHREAD_SET_NAME_NP_FREEBSD) + /* FreeBSD and OpenBSD use different function name and void return type */ + pthread_set_name_np (thread_id, thread_name); + return true; +# elif defined(HAVE_PTHREAD_SETNAME_NP_NETBSD) + /* NetBSD uses 3 arguments: second argument is string in printf-like format, + * third argument is a single argument for printf(); + * OSF1 use 3 arguments too, but last one always must be zero (NULL). + * MHD doesn't use '%' in thread names, so both form are used in same way. + */ + return 0 == pthread_setname_np (thread_id, thread_name, 0); +# endif /* HAVE_PTHREAD_SETNAME_NP_NETBSD */ +} + + +# ifndef __QNXNTO__ +/** + * Set current thread name + * @param n name to set + * @return non-zero on success, zero otherwise + */ +# define mhd_set_cur_thread_name(n) \ + mhd_set_thread_name (pthread_self (),(n)) +# else /* __QNXNTO__ */ +/* Special case for QNX Neutrino - using zero for thread ID sets name faster. */ +# define mhd_set_cur_thread_name(n) mhd_set_thread_name (0,(n)) +# endif /* __QNXNTO__ */ +# elif defined(HAVE_PTHREAD_SETNAME_NP_DARWIN) + +/** + * Set current thread name + * @param n name to set + * @return non-zero on success, zero otherwise + */ +# define mhd_set_cur_thread_name(n) (! (pthread_setname_np ((n)))) +# endif /* HAVE_PTHREAD_SETNAME_NP_DARWIN */ + +# elif defined(MHD_USE_W32_THREADS) +# ifndef _MSC_FULL_VER +#error Thread name available only for VC-compiler +# else /* _MSC_FULL_VER */ +/** + * Set thread name + * + * @param thread_id ID of thread, -1 for current thread + * @param thread_name name to set + * @return true on success, false otherwise + */ +static bool +mhd_set_thread_name (const mhd_thread_ID_native thread_id, + const char *thread_name) +{ + static const DWORD VC_SETNAME_EXC = 0x406D1388; +#pragma pack(push,8) + struct thread_info_struct + { + DWORD type; /* Must be 0x1000. */ + LPCSTR name; /* Pointer to name (in user address space). */ + DWORD ID; /* Thread ID (-1 = caller thread). */ + DWORD flags; /* Reserved for future use, must be zero. */ + } thread_info; +#pragma pack(pop) + + if (NULL == thread_name) + return false; + + thread_info.type = 0x1000; + thread_info.name = thread_name; + thread_info.ID = thread_id; + thread_info.flags = 0; + + __try + { /* This exception is intercepted by debugger */ + RaiseException (VC_SETNAME_EXC, + 0, + sizeof (thread_info) / sizeof(ULONG_PTR), + (ULONG_PTR *) &thread_info); + } + __except (EXCEPTION_EXECUTE_HANDLER) + {} + + return true; +} + + +/** + * Set current thread name + * @param n name to set + * @return true on success, false otherwise + */ +# define mhd_set_cur_thread_name(n) \ + mhd_set_thread_name ((mhd_thread_ID_native) (-1),(n)) +# endif /* _MSC_FULL_VER */ +# endif /* MHD_USE_W32_THREADS */ + +#endif /* MHD_USE_THREAD_NAME_ */ + + +/** + * Create a thread and set the attributes according to our options. + * + * If thread is created, thread handle must be freed by mhd_join_thread(). + * + * @param handle_id handle to initialise + * @param stack_size size of stack for new thread, 0 for default + * @param start_routine main function of thread + * @param arg argument for start_routine + * @return non-zero on success; zero otherwise (with errno set) + */ +bool +mhd_create_thread (mhd_thread_handle_ID *handle_id, + size_t stack_size, + mhd_THREAD_START_ROUTINE start_routine, + void *arg) +{ +#if defined(MHD_USE_POSIX_THREADS) + int res; +# if defined(mhd_thread_handle_ID_get_native_handle_ptr) + pthread_t *const new_tid_ptr = + mhd_thread_handle_ID_get_native_handle_ptr (handle_id); +# else /* ! mhd_thread_handle_ID_get_native_handle_ptr */ + pthread_t new_tid; + pthread_t *const new_tid_ptr = &new_tid; +# endif /* ! mhd_thread_handle_ID_get_native_handle_ptr */ + + mhd_assert (! mhd_thread_handle_ID_is_valid_handle (*handle_id)); + + if (0 != stack_size) + { + pthread_attr_t attr; + res = pthread_attr_init (&attr); + if (0 == res) + { + res = pthread_attr_setstacksize (&attr, + stack_size); + if (0 == res) + res = pthread_create (new_tid_ptr, + &attr, + start_routine, + arg); + pthread_attr_destroy (&attr); + } + } + else + res = pthread_create (new_tid_ptr, + NULL, + start_routine, + arg); + + if (0 != res) + { + errno = res; + mhd_thread_handle_ID_set_invalid (handle_id); + } +# if ! defined(mhd_thread_handle_ID_get_native_handle_ptr) + else + mhd_thread_handle_ID_set_native_handle (handle_id, new_tid); +# endif /* ! mhd_thread_handle_ID_set_current_thread_ID */ + + return 0 == res; +#elif defined(MHD_USE_W32_THREADS) + uintptr_t thr_handle; + unsigned int stack_size_w32; +# if SIZEOF_SIZE_T != SIZEOF_UNSIGNED_INT + + mhd_assert (! mhd_thread_handle_ID_is_valid_handle (*handle_id)); + + stack_size_w32 = (unsigned int) stack_size; + if (stack_size != stack_size_w32) + { + errno = EINVAL; + return false; + } +#endif /* SIZEOF_SIZE_T != SIZEOF_UNSIGNED_INT */ + thr_handle = _beginthreadex (NULL, + stack_size_w32, + start_routine, + arg, + 0, + NULL); + if ((mhd_thread_handle_native) 0 == (mhd_thread_handle_native) thr_handle) + return false; + + mhd_thread_handle_ID_set_native_handle (handle_id, \ + (mhd_thread_handle_native) \ + thr_handle); + + return true; +#endif /* MHD_USE_W32_THREADS */ +} + + +#ifdef MHD_USE_THREAD_NAME_ + +# ifndef MHD_USE_THREAD_ATTR_SETNAME +struct mhd_named_helper_param +{ + /** + * Real thread start routine + */ + mhd_THREAD_START_ROUTINE start_routine; + + /** + * Argument for thread start routine + */ + void *arg; + + /** + * Name for thread + */ + const char *name; +}; + + +static mhd_THRD_RTRN_TYPE mhd_THRD_CALL_SPEC +named_thread_starter (void *data) +{ + struct mhd_named_helper_param *const param = + (struct mhd_named_helper_param *) data; + void *arg; + mhd_THREAD_START_ROUTINE thr_func; + + if (NULL == data) + return (mhd_THRD_RTRN_TYPE) 0; + + mhd_set_cur_thread_name (param->name); + + arg = param->arg; + thr_func = param->start_routine; + free (data); + + return thr_func (arg); +} + + +# endif /* ! MHD_USE_THREAD_ATTR_SETNAME */ + + +/** + * Create a named thread and set the attributes according to our options. + * + * @param handle_id handle to initialise + * @param thread_name name for new thread + * @param stack_size size of stack for new thread, 0 for default + * @param start_routine main function of thread + * @param arg argument for start_routine + * @return non-zero on success; zero otherwise (with errno set) + */ +bool +mhd_create_named_thread (mhd_thread_handle_ID *handle_id, + const char *thread_name, + size_t stack_size, + mhd_THREAD_START_ROUTINE start_routine, + void *arg) +{ +# if defined(MHD_USE_THREAD_ATTR_SETNAME) + int res; + pthread_attr_t attr; +# if defined(mhd_thread_handle_ID_get_native_handle_ptr) + pthread_t *const new_tid_ptr = + mhd_thread_handle_ID_get_native_handle_ptr (handle_id); +# else /* ! mhd_thread_handle_ID_get_native_handle_ptr */ + pthread_t new_tid; + pthread_t *const new_tid_ptr = &new_tid; +# endif /* ! mhd_thread_handle_ID_get_native_handle_ptr */ + + res = pthread_attr_init (&attr); + if (0 == res) + { +# if defined(HAVE_PTHREAD_ATTR_SETNAME_NP_NETBSD) + /* NetBSD uses 3 arguments: second argument is string in printf-like format, + * third argument is single argument for printf; + * OSF1 uses 3 arguments too, but last one always must be zero (NULL). + * MHD doesn't use '%' in thread names, so both forms are used in same way. + */ + res = pthread_attr_setname_np (&attr, + thread_name, + 0); +# elif defined(HAVE_PTHREAD_ATTR_SETNAME_NP_IBMI) + res = pthread_attr_setname_np (&attr, + thread_name); +# else +#error No pthread_attr_setname_np() function. +# endif + if ((res == 0) && (0 != stack_size) ) + res = pthread_attr_setstacksize (&attr, + stack_size); + if (0 == res) + res = pthread_create (new_tid_ptr, + &attr, + start_routine, + arg); + pthread_attr_destroy (&attr); + } + if (0 != res) + { + errno = res; + mhd_thread_handle_ID_set_invalid (handle_id); + } +# if ! defined(mhd_thread_handle_ID_get_native_handle_ptr) + else + mhd_thread_handle_ID_set_native_handle (handle_id, new_tid); +# endif /* ! mhd_thread_handle_ID_set_current_thread_ID */ + + return 0 == res; +# else /* ! MHD_USE_THREAD_ATTR_SETNAME */ + struct mhd_named_helper_param *param; + + if (NULL == thread_name) + { + errno = EINVAL; + return false; + } + + param = malloc (sizeof (struct mhd_named_helper_param)); + if (NULL == param) + return false; + + param->start_routine = start_routine; + param->arg = arg; + param->name = thread_name; + + /* Set the thread name in the thread itself to avoid problems with + * threads which terminated before the name is set in other thread. + */ + if (! mhd_create_thread (handle_id, + stack_size, + &named_thread_starter, + (void *) param)) + { + int err_num; + + err_num = errno; + free (param); + errno = err_num; + return false; + } + + return true; +# endif /* ! MHD_USE_THREAD_ATTR_SETNAME */ +} + + +#endif /* MHD_USE_THREAD_NAME_ */ diff --git a/src/mhd2/mhd_threads.h b/src/mhd2/mhd_threads.h @@ -0,0 +1,545 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_threads.h + * @brief Header for platform-independent threads abstraction + * @author Karlson2k (Evgeny Grin) + * + * Provides basic abstraction for threads. + * Any functions can be implemented as macro on some platforms + * unless explicitly marked otherwise. + * Any function argument can be skipped in macro, so avoid + * variable modification in function parameters. + * + * @warning Unlike pthread functions, most of functions return + * nonzero on success. + */ + +#ifndef MHD_THREADS_H +#define MHD_THREADS_H 1 + +#include "mhd_sys_options.h" + +#if defined(MHD_USE_POSIX_THREADS) +# include <pthread.h> +# ifndef MHD_USE_THREADS +# define MHD_USE_THREADS 1 +# endif +#elif defined(MHD_USE_W32_THREADS) +# include <windows.h> +# ifndef MHD_USE_THREADS +# define MHD_USE_THREADS 1 +# endif +#else +# error No threading API is available. +#endif + +#include "sys_bool_type.h" +#include "sys_base_types.h" +#include "sys_thread_entry_type.h" + +#if defined(MHD_USE_POSIX_THREADS) && defined(MHD_USE_W32_THREADS) +# error Both MHD_USE_POSIX_THREADS and MHD_USE_W32_THREADS are defined +#endif /* MHD_USE_POSIX_THREADS && MHD_USE_W32_THREADS */ + +#ifndef MHD_NO_THREAD_NAMES +# if defined(MHD_USE_POSIX_THREADS) +# if defined(HAVE_PTHREAD_SETNAME_NP_GNU) || \ + defined(HAVE_PTHREAD_SET_NAME_NP_FREEBSD) || \ + defined(HAVE_PTHREAD_SETNAME_NP_DARWIN) || \ + defined(HAVE_PTHREAD_SETNAME_NP_NETBSD) || \ + defined(HAVE_PTHREAD_ATTR_SETNAME_NP_NETBSD) || \ + defined(HAVE_PTHREAD_ATTR_SETNAME_NP_IBMI) +# define MHD_USE_THREAD_NAME_ +# endif /* HAVE_PTHREAD_SETNAME_NP */ +# elif defined(MHD_USE_W32_THREADS) +# ifdef _MSC_FULL_VER +/* Thread names only available with VC compiler */ +# define MHD_USE_THREAD_NAME_ +# endif /* _MSC_FULL_VER */ +# endif +#endif + +/* ** Thread handle - used to control the thread ** */ + +#if defined(MHD_USE_POSIX_THREADS) +/** + * The native type to control the thread from other threads + */ +typedef pthread_t mhd_thread_handle_native; +#elif defined(MHD_USE_W32_THREADS) +/** + * The native type to control the thread from other threads + */ +typedef HANDLE mhd_thread_handle_native; +#endif + +#if defined(MHD_USE_POSIX_THREADS) +# if defined(__gnu_linux__) || \ + (defined(__linux__) && defined(__GLIBC__)) +/* The next part of code is disabled because it relies on undocumented + behaviour (while the thread ID cannot be zero with GNU C Library, it is + not specified anywhere officially). + It could be enabled for neglectable performance and size improvements. */ +# if 0 /* Disabled code */ +/** + * The native invalid value for native thread handle + */ +# define mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID \ + ((mhd_thread_handle_native) 0) +# endif /* Disabled code */ +# endif /* __gnu_linux__ || (__linux__ && __GLIBC__) */ +#elif defined(MHD_USE_W32_THREADS) +/* On W32 the invalid value for thread handle is described directly in + the official documentation. */ +/** + * The native invalid value for native thread handle + */ +# define mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID \ + ((mhd_thread_handle_native) NULL) +#endif /* MHD_USE_W32_THREADS */ + +#if defined(MHD_USE_POSIX_THREADS) +/** + * Wait until specified thread is ended and free the thread handle on success. + * @param native_handle the handle to watch + * @return nonzero on success, zero otherwise + */ +# define mhd_join_thread(native_handle) \ + (! pthread_join ((native_handle), NULL)) +#elif defined(MHD_USE_W32_THREADS) +/** + * Wait until specified thread is ended and the free thread handle on success. + * @param native_handle the handle to watch + * @return nonzero on success, zero otherwise + */ +# define mhd_join_thread(native_handle) \ + ( (WAIT_OBJECT_0 == WaitForSingleObject ( (native_handle), INFINITE)) ? \ + (CloseHandle ( (native_handle)), ! 0) : 0) +#endif + +#if ! defined(mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID) +/** + * Structure with thread handle and validity flag + */ +struct mhd_thread_handle_struct +{ + bool valid; /**< true if native handle is set */ + mhd_thread_handle_native native; /**< the native thread handle */ +}; +/** + * Type with thread handle that can be set to invalid value + */ +typedef struct mhd_thread_handle_struct mhd_thread_handle; + +/** + * Set variable pointed by @a handle_ptr to invalid (unset) value + */ +# define mhd_thread_handle_set_invalid(handle_ptr) \ + ((handle_ptr)->valid = false) +/** + * Set the native handle in the variable pointed by the @a handle_ptr + * to the @a native_val value + */ +# define mhd_thread_handle_set_native(handle_ptr,native_val) \ + ((handle_ptr)->valid = true, (handle_ptr)->native = native_val) +/** + * Check whether the native handle value is set in the @a handle_var variable + */ +# define mhd_thread_handle_is_valid(handle_var) \ + ((handle_var).valid) +/** + * Get the native handle value from the @a handle_var variable + */ +# define mhd_thread_handle_get_native(handle_var) \ + ((handle_var).native) +#else /* MHD_THREAD_HANDLE_NATIVE_INVALID_ */ +/** + * Type with thread handle that can be set to invalid value + */ +typedef mhd_thread_handle_native mhd_thread_handle; + +/** + * Set the variable pointed by the @a handle_ptr to the invalid (unset) value + */ +# define mhd_thread_handle_set_invalid(handle_ptr) \ + ((*(handle_ptr)) = mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID) +/** + * Set the native handle in the variable pointed by the @a handle_ptr + * to the @a native_val value + */ +# define mhd_thread_handle_set_native(handle_ptr,native_val) \ + ((*(handle_ptr)) = (native_val)) +/** + * Check whether the native handle value is set in the @a handle_var variable + */ +# define mhd_thread_handle_is_valid(handle_var) \ + (mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID != (handle_var)) +/** + * Get the native handle value from the @a handle_var variable + */ +# define mhd_thread_handle_get_native(handle_var) \ + (handle_var) +/** + * Get the pointer to the native handle stored the variable pointed by + * the @a handle_ptr pointer + * @note This macro could be not available if direct manipulation of + * the native handle is not possible + */ +# define mhd_thread_handle_get_native_ptr(handle_ptr) \ + (handle_ptr) +#endif /* MHD_THREAD_HANDLE_NATIVE_INVALID_ */ + + +/* ** Thread ID - used to check threads match ** */ + +#if defined(MHD_USE_POSIX_THREADS) +/** + * The native type used to check whether the current thread matches + * the expected thread + */ +typedef pthread_t mhd_thread_ID_native; + +/** + * Function to get the current thread native ID. + */ +# define mhd_thread_ID_native_current pthread_self + +/** + * Check whether two native thread IDs are equal. + * @return non-zero if equal, zero if not equal + */ +# define mhd_thread_ID_native_equal(id1,id2) \ + (pthread_equal ((id1),(id2))) +#elif defined(MHD_USE_W32_THREADS) +/** + * The native type used to check whether the current thread matches + * the expected thread + */ +typedef DWORD mhd_thread_ID_native; + +/** + * Function to get the current thread native ID. + */ +# define mhd_thread_ID_native_current GetCurrentThreadId + +/** + * Check whether two native thread IDs are equal. + * @return non-zero if equal, zero if not equal + */ +# define mhd_thread_ID_native_equal(id1,id2) \ + ((id1) == (id2)) +#endif + +/** + * Check whether the specified thread ID matches current thread. + * @param id the thread ID to match + * @return nonzero on match, zero otherwise + */ +#define mhd_thread_ID_native_is_current_thread(id) \ + mhd_thread_ID_native_equal ((id), mhd_thread_ID_native_current ()) + + +#if defined(MHD_USE_POSIX_THREADS) +# if defined(mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID) +/** + * The native invalid value for native thread ID + */ +# define MHD_THREAD_ID_NATIVE_VALUE_INVALID_ \ + mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID +# endif /* mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID */ +#elif defined(MHD_USE_W32_THREADS) +/** + * The native invalid value for native thread ID + */ + # define MHD_THREAD_ID_NATIVE_VALUE_INVALID_ \ + ((mhd_thread_ID_native) 0) +#endif /* MHD_USE_W32_THREADS */ + +#if ! defined(MHD_THREAD_ID_NATIVE_VALUE_INVALID_) +/** + * Structure with thread id and validity flag + */ +struct mhd_thread_ID_struct +{ + bool valid; /**< true if native ID is set */ + mhd_thread_ID_native native; /**< the native thread ID */ +}; +/** + * Type with thread ID that can be set to the invalid value + */ +typedef struct mhd_thread_ID_struct mhd_thread_ID; + +/** + * Set variable pointed by the @a ID_ptr to the invalid (unset) value + */ +# define mhd_thread_ID_set_invalid(ID_ptr) \ + ((ID_ptr)->valid = false) +/** + * Set the native ID in the variable pointed by the @a ID_ptr + * to the @a native_val value + */ +# define mhd_thread_ID_set_native(ID_ptr,native_val) \ + ((ID_ptr)->valid = true, (ID_ptr)->native = (native_val)) +/** + * Check whether the native ID value is set in the @a ID_var variable + */ +# define mhd_thread_ID_is_valid(ID_var) \ + ((ID_var).valid) +/** + * Get the native ID value from the @a ID_var variable + */ +# define mhd_thread_ID_get_native(ID_var) \ + ((ID_var).native) +/** + * Check whether the @a ID_var variable is equal current thread + */ +# define mhd_thread_ID_is_current_thread(ID_var) \ + (mhd_thread_ID_is_valid (ID_var) && \ + mhd_thread_ID_native_is_current_thread ((ID_var).native)) +#else /* MHD_THREAD_ID_NATIVE_INVALID_ */ +/** + * Type with thread ID that can be set to the invalid value + */ +typedef mhd_thread_ID_native mhd_thread_ID; + +/** + * Set variable pointed by the @a ID_ptr to the invalid (unset) value + */ +# define mhd_thread_ID_set_invalid(ID_ptr) \ + ((*(ID_ptr)) = MHD_THREAD_ID_NATIVE_VALUE_INVALID_) +/** + * Set the native ID in the variable pointed by the @a ID_ptr + * to the @a native_val value + */ +# define mhd_thread_ID_set_native(ID_ptr,native_val) \ + ((*(ID_ptr)) = (native_val)) +/** + * Check whether the native ID value is set in the @a ID_var variable + */ +# define mhd_thread_ID_is_valid(ID_var) \ + (MHD_THREAD_ID_NATIVE_VALUE_INVALID_ != (ID_var)) +/** + * Get the native ID value from the @a ID_var variable + */ +# define mhd_thread_ID_get_native(ID_var) \ + (ID_var) +/** + * Check whether the @a ID_var variable is equal current thread + */ +# define mhd_thread_ID_is_current_thread(ID_var) \ + mhd_thread_ID_native_is_current_thread (ID_var) +#endif /* MHD_THREAD_ID_NATIVE_INVALID_ */ + +/** + * Set current thread ID in the variable pointed by the @a ID_ptr + */ +# define mhd_thread_ID_set_current_thread(ID_ptr) \ + mhd_thread_ID_set_native ((ID_ptr),mhd_thread_ID_native_current ()) + + +#if defined(MHD_USE_POSIX_THREADS) +# if defined(mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID) && \ + ! defined(MHD_THREAD_ID_NATIVE_VALUE_INVALID_) +# error \ + MHD_THREAD_ID_NATIVE_VALUE_INVALID_ is defined, but MHD_THREAD_ID_NATIVE_VALUE_INVALID_ is not defined +# elif ! defined(mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID) && \ + defined(MHD_THREAD_ID_NATIVE_VALUE_INVALID_) +# error \ + MHD_THREAD_ID_NATIVE_VALUE_INVALID_ is not defined, but MHD_THREAD_ID_NATIVE_VALUE_INVALID_ is defined +# endif +#endif /* MHD_USE_POSIX_THREADS */ + +/* When staring a new thread, the kernel (and thread implementation) may + * pause the calling (initial) thread and start the new thread. + * If thread identifier is assigned to variable in the initial thread then + * the value of the identifier variable will be undefined in the new thread + * until the initial thread continue processing. + * However, it is also possible that the new thread created, but not executed + * for some time while the initial thread continue execution. In this case any + * variable assigned in the new thread will be undefined for some time until + * they really processed by the new thread. + * To avoid data races, a special structure mhd_thread_handle_ID is used. + * The "handle" is assigned by calling (initial) thread and should be always + * defined when checked in the initial thread. + * The "ID" is assigned by the new thread and should be always defined when + * checked inside the new thread. + */ +/* Depending on implementation, pthread_create() MAY set thread ID into + * provided pointer and after it start thread OR start thread and after + * it set thread ID. In the latter case, to avoid data races, additional + * pthread_self() call is required in thread routine. If some platform + * is known for setting thread ID BEFORE starting thread macro + * MHD_PTHREAD_CREATE__SET_ID_BEFORE_START_THREAD could be defined + * to save some resources. */ + +/* #define MHD_PTHREAD_CREATE__SET_ID_BEFORE_START_THREAD 1 */ + +/* * handle - must be valid when other thread knows that particular thread + is started. + * ID - must be valid when code is executed inside thread */ +#if defined(MHD_USE_POSIX_THREADS) && \ + defined(MHD_PTHREAD_CREATE__SET_ID_BEFORE_START_THREAD) && \ + defined(mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID) && \ + defined(MHD_THREAD_ID_NATIVE_VALUE_INVALID_) && \ + defined(mhd_thread_handle_get_native_ptr) +union mhd_thread_handle_ID_ +{ + mhd_thread_handle handle; /**< To be used in other threads */ + mhd_thread_ID ID; /**< To be used in the thread itself */ +}; +typedef union mhd_thread_handle_ID_ mhd_thread_handle_ID; +# define MHD_THREAD_HANDLE_ID_IS_UNION 1 +#else /* !MHD_USE_POSIX_THREADS + || !MHD_PTHREAD_CREATE__SET_ID_BEFORE_START_THREAD + || !mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID + || !MHD_THREAD_ID_NATIVE_VALUE_INVALID_ + || !mhd_thread_handle_get_native_ptr */ +struct mhd_thread_handle_ID_ +{ + mhd_thread_handle handle; /**< To be used in other threads */ + mhd_thread_ID ID; /**< To be used in the thread itself */ +}; +typedef struct mhd_thread_handle_ID_ mhd_thread_handle_ID; +#endif /* !MHD_USE_POSIX_THREADS + || !MHD_PTHREAD_CREATE__SET_ID_BEFORE_START_THREAD + || !mhd_THREAD_HANDLE_NATIVE_VALUE_INVALID + || !MHD_THREAD_ID_NATIVE_VALUE_INVALID_ + || !mhd_thread_handle_get_native_ptr */ + +/** + * Set the mhd_thread_handle_ID to the invalid value + */ +#define mhd_thread_handle_ID_set_invalid(hndl_id_ptr) \ + (mhd_thread_handle_set_invalid (&((hndl_id_ptr)->handle)), \ + mhd_thread_ID_set_invalid (&((hndl_id_ptr)->ID))) + +/** + * Check whether the thread handle is valid. + * To be used in threads other then the thread specified by the @a hndl_id. + */ +#define mhd_thread_handle_ID_is_valid_handle(hndl_id) \ + mhd_thread_handle_is_valid ((hndl_id).handle) + +/** + * Set the native handle in the variable pointed by the @a hndl_id_ptr + * to the @a native_val value + */ +#define mhd_thread_handle_ID_set_native_handle(hndl_id_ptr,native_val) \ + mhd_thread_handle_set_native (&((hndl_id_ptr)->handle),native_val) + +#if defined(mhd_thread_handle_get_native_ptr) +/** + * Get the pointer to the native handle stored the variable pointed by + * the @a hndl_id_ptr + * @note This macro could not available if direct manipulation of + * the native handle is not possible + */ +# define mhd_thread_handle_ID_get_native_handle_ptr(hndl_id_ptr) \ + mhd_thread_handle_get_native_ptr (&((hndl_id_ptr)->handle)) +#endif /* mhd_thread_handle_get_native_ptr */ + +/** + * Get the native thread handle from the mhd_thread_handle_ID variable. + */ +#define mhd_thread_handle_ID_get_native_handle(hndl_id) \ + mhd_thread_handle_get_native ((hndl_id).handle) + +/** + * Check whether the thread ID is valid. + * To be used in the thread itself. + */ +#define mhd_thread_handle_ID_is_valid_ID(hndl_id) \ + mhd_thread_ID_is_valid ((hndl_id).ID) + +#if defined(MHD_THREAD_HANDLE_ID_IS_UNION) +# if defined(MHD_USE_W32_THREADS) +# error mhd_thread_handle_ID cannot be a union with W32 threads +# endif /* MHD_USE_W32_THREADS */ +/** + * Set current thread ID in the variable pointed by the @a hndl_id_ptr + */ +# define mhd_thread_handle_ID_set_current_thread_ID(hndl_id_ptr) (void) 0 +#else /* ! MHD_THREAD_HANDLE_ID_IS_UNION */ +/** + * Set current thread ID in the variable pointed by the @a hndl_id_ptr + */ +# define mhd_thread_handle_ID_set_current_thread_ID(hndl_id_ptr) \ + mhd_thread_ID_set_current_thread (&((hndl_id_ptr)->ID)) +#endif /* ! MHD_THREAD_HANDLE_ID_IS_UNION */ + +/** + * Check whether provided thread ID matches current thread. + * @param ID thread ID to match + * @return nonzero on match, zero otherwise + */ +#define mhd_thread_handle_ID_is_current_thread(hndl_id) \ + mhd_thread_ID_is_current_thread ((hndl_id).ID) + +/** + * Wait until specified thread is ended and free thread handle on success. + * @param hndl_id_ handle with ID to watch + * @return nonzero on success, zero otherwise + */ +#define mhd_thread_handle_ID_join_thread(hndl_id) \ + mhd_join_thread (mhd_thread_handle_ID_get_native_handle (hndl_id)) + + +/** + * Create a thread and set the attributes according to our options. + * + * If thread is created, thread handle must be freed by mhd_join_thread(). + * + * @param handle_id handle to initialise + * @param stack_size size of stack for new thread, 0 for default + * @param start_routine main function of thread + * @param arg argument for start_routine + * @return non-zero on success; zero otherwise (with errno set) + */ +bool +mhd_create_thread (mhd_thread_handle_ID *handle_id, + size_t stack_size, + mhd_THREAD_START_ROUTINE start_routine, + void *arg); + +#ifndef MHD_USE_THREAD_NAME_ +#define mhd_create_named_thread(t,n,s,r,a) mhd_create_thread ((t),(s),(r),(a)) +#else /* MHD_USE_THREAD_NAME_ */ +/** + * Create a named thread and set the attributes according to our options. + * + * @param handle_id handle to initialise + * @param thread_name name for new thread + * @param stack_size size of stack for new thread, 0 for default + * @param start_routine main function of thread + * @param arg argument for start_routine + * @return non-zero on success; zero otherwise + */ +bool +mhd_create_named_thread (mhd_thread_handle_ID *handle_id, + const char *thread_name, + size_t stack_size, + mhd_THREAD_START_ROUTINE start_routine, + void *arg); + +#endif /* MHD_USE_THREAD_NAME_ */ + +#endif /* ! MHD_THREADS_H */ diff --git a/src/mhd2/mhd_tristate.h b/src/mhd2/mhd_tristate.h @@ -0,0 +1,83 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_tristate.h + * @brief The definition of the tristate type and helper macros + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_TRISTATE_H +#define MHD_TRISTATE_H 1 + +#include "mhd_sys_options.h" + +/** + * Enum with three values / states + */ +enum MHD_FIXED_ENUM_ mhd_Tristate +{ + /** + * Definitely no / negative / false + */ + mhd_T_NO = 0 + , + /** + * Definitely yes / positive / true + */ + mhd_T_YES = 1 + , + /** + * Undetermined / not known / maybe yes-maybe no + */ + mhd_T_MAYBE = -1 +}; + +/** + * Check whether tristate value is mhd_T_YES + */ +#define mhd_T_IS_YES(v) (mhd_T_NO < (v)) + +/** + * Check whether tristate value is mhd_T_NO + */ +#define mhd_T_IS_NO(v) (mhd_T_NO == (v)) + +/** + * Check whether tristate value is mhd_T_MAYBE + */ +#define mhd_T_IS_MAYBE(v) (mhd_T_NO > (v)) + +/** + * Check whether tristate value is NOT mhd_T_YES + */ +#define mhd_T_IS_NOT_YES(v) (mhd_T_NO >= (v)) + +/** + * Check whether tristate value is NOT mhd_T_NO + */ +#define mhd_T_IS_NOT_NO(v) (mhd_T_NO != (v)) + +/** + * Check whether tristate value is NOT mhd_T_MAYBE + */ +#define mhd_T_IS_NOT_MAYBE(v) (mhd_T_NO <= (v)) + +#endif /* ! MHD_TRISTATE_H */ diff --git a/src/mhd2/request_funcs.c b/src/mhd2/request_funcs.c @@ -0,0 +1,71 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-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/request_funcs.c + * @brief The definition of the request internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "mhd_request.h" +#include "mhd_connection.h" +#include "request_funcs.h" +#include "stream_funcs.h" +#include "mhd_dlinked_list.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_add_field_nullable (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_StringNullable *restrict value) +{ + struct mhd_RequestField *f; + + f = (struct mhd_RequestField *) + mhd_stream_alloc_memory (c, sizeof(struct mhd_RequestField)); + if (NULL == f) + return false; + + f->field.nv.name = *name; + f->field.nv.value = *value; + f->field.kind = kind; + mhd_DLINKEDL_INIT_LINKS (f, fields); + + mhd_DLINKEDL_INS_LAST (&(c->rq),f,fields); + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_add_field (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_String *restrict value) +{ + struct MHD_StringNullable value2; + + value2.len = value->len; + value2.cstr = value->cstr; + + return mhd_stream_add_field_nullable (c, kind, name, &value2); +} diff --git a/src/mhd2/request_funcs.h b/src/mhd2/request_funcs.h @@ -0,0 +1,78 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-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/request_funcs.h + * @brief The declaration of the request internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_REQUEST_FUNCS_H +#define MHD_REQUEST_FUNCS_H 1 + +#include "mhd_sys_options.h" +#include "sys_bool_type.h" +#include "mhd_str_types.h" +#include "mhd_public_api.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Add field to the request. + * The memory allocated in the request memory pool + * + * @param c the connection to use + * @param kind the kind of the field to add + * @param name the name of the field to add, the string is not copied, + * only copied the pointer value + * @param value the value of the field to add, the string is not copied, + * only copied the pointer value + * @return true if succeed, + * false if memory cannot be allocated + */ +MHD_INTERNAL bool +mhd_stream_add_field (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_String *restrict value) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Add field to the request. + * The memory allocated in the request memory pool + * The value can have NULL string ("no value"). + * + * @param c the connection to use + * @param kind the kind of the field to add + * @param name the name of the field to add, the string is not copied, + * only copied the pointer value + * @param value the value of the field to add, the string is not copied, + * only copied the pointer value + * @return true if succeed, + * false if memory cannot be allocated + */ +MHD_INTERNAL bool +mhd_stream_add_field_nullable (struct MHD_Connection *restrict c, + enum MHD_ValueKind kind, + const struct MHD_String *restrict name, + const struct MHD_StringNullable *restrict value) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_REQUEST_FUNCS_H */ diff --git a/src/mhd2/request_get_value.c b/src/mhd2/request_get_value.c @@ -0,0 +1,109 @@ +/* + 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/request_get_value.c + * @brief The implementation of MHD_request_get_value*() functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "request_get_value.h" +#include "sys_base_types.h" +#include <string.h> + +#include "mhd_request.h" + +#include "mhd_connection.h" + +#include "mhd_dlinked_list.h" +#include "mhd_assert.h" +#include "mhd_str.h" + +#include "mhd_public_api.h" + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_CSTR_ (4) const struct MHD_StringNullable * +mhd_request_get_value_n (struct MHD_Request *restrict request, + enum MHD_ValueKind kind, + size_t key_len, + const char *restrict key) +{ + struct mhd_RequestField *f; + + mhd_assert (strlen (key) == key_len); + + for (f = mhd_DLINKEDL_GET_FIRST (request, fields); NULL != f; + f = mhd_DLINKEDL_GET_NEXT (f, fields)) + { + if ((key_len == f->field.nv.name.len) && + (kind == f->field.kind) && + (0 == memcmp (key, f->field.nv.name.cstr, key_len))) + return &(f->field.nv.value); + } + return NULL; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) const struct MHD_StringNullable * +MHD_request_get_value (struct MHD_Request *MHD_RESTRICT request, + enum MHD_ValueKind kind, + const char *MHD_RESTRICT key) +{ + size_t len; + len = strlen (key); + return mhd_request_get_value_n (request, kind, len, key); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_CSTR_ (3) +MHD_FN_PAR_CSTR_ (5) bool +mhd_stream_has_header_token (const struct MHD_Connection *restrict c, + size_t header_len, + const char *restrict header, + size_t token_len, + const char *restrict token) +{ + struct mhd_RequestField *f; + + mhd_assert (MHD_CONNECTION_START_REPLY >= c->state); + + for (f = mhd_DLINKEDL_GET_FIRST (&(c->rq), fields); + NULL != f; + f = mhd_DLINKEDL_GET_NEXT (f, fields)) + { + if ((MHD_VK_HEADER == f->field.kind) && + (header_len == f->field.nv.name.len) && + (mhd_str_equal_caseless_bin_n (header, + f->field.nv.name.cstr, + header_len)) && + (mhd_str_has_token_caseless (f->field.nv.value.cstr, + token, + token_len))) + return true; + } + + return false; +} diff --git a/src/mhd2/request_get_value.h b/src/mhd2/request_get_value.h @@ -0,0 +1,116 @@ +/* + 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/request_get_value.h + * @brief The declaration of internal mhd_request_get_value* functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_REQUEST_GET_VALUE_H +#define MHD_REQUEST_GET_VALUE_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" +#include "sys_bool_type.h" +#include "mhd_str_macros.h" + +#include "mhd_public_api.h" + +/** + * Get specified field value from request + * If multiple values match the kind, return any one of them. + * + * The returned pointer is valid until the response is queued. + * If the data is needed beyond this point, it should be copied. + * + * @param request request to get values from + * @param kind what kind of value are we looking for + * @param key_len the length of the @a key string + * @param key the header to look for, empty to lookup 'trailing' value + * without a key + * @return NULL if no such item was found + * @ingroup request + */ +MHD_INTERNAL const struct MHD_StringNullable * +mhd_request_get_value_n (struct MHD_Request *restrict request, + enum MHD_ValueKind kind, + size_t key_len, + const char *restrict key) +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_CSTR_ (4); + +/** + * Get specified field value from request + * If multiple values match the kind, return any one of them. + * + * The returned pointer is valid until the response is queued. + * If the data is needed beyond this point, it should be copied. + * + * @param request request to get values from + * @param kind what kind of value are we looking for + * @param key the header to look for, empty to lookup 'trailing' value + * without a key; must be a static string or array + * @return NULL if no such item was found + * @ingroup request + */ +#define mhd_request_get_value_st(r,k,str) \ + mhd_request_get_value_n ((r),(k),mhd_SSTR_LEN (str),(str)) + +#endif /* ! MHD_REQUEST_GET_VALUE_H */ + + +/** + * Check whether the request header contains particular token. + * + * Token could be surrounded by spaces and tabs and delimited by comma. + * Case-insensitive match used for header names and tokens. + * @param c the connection to check values + * @param header_len the length of header, not including optional + * terminating null-character + * @param header the header name + * @param token_len the length of token, not including optional + * terminating null-character. + * @param token the token to find + * @return true if the token is found in the specified header, + * false otherwise + */ +MHD_INTERNAL bool +mhd_stream_has_header_token (const struct MHD_Connection *restrict c, + size_t header_len, + const char *restrict header, + size_t token_len, + const char *restrict token) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (5); + +/** + * Check whether the request header contains particular token. + * + * Token could be surrounded by spaces and tabs and delimited by comma. + * Case-insensitive match used for header names and tokens. + * @param c the connection to check values + * @param hdr the statically allocated header name string + * @param token the statically allocated string of token to find + * @return true if the token is found in the specified header, + * false otherwise + */ +#define mhd_stream_has_header_token_st(c,hdr,tkn) \ + mhd_stream_has_header_token ((c), mhd_SSTR_LEN (hdr), (hdr), \ + mhd_SSTR_LEN (tkn), (tkn)) diff --git a/src/mhd2/respond_with_error.c 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 @@ -0,0 +1,95 @@ +/* + 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.h + * @brief The declaration of error response functions and helper macros + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPOND_WITH_ERROR_H +#define MHD_RESPOND_WITH_ERROR_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" +#include "sys_null_macro.h" +#include "mhd_str_macros.h" + +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_line_len, + char *add_hdr_line) +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6); + +#ifdef HAVE_HTTP_AUTO_MESSAGES_BODIES +/** + * Transmit static string as error response + */ +# define mhd_RESPOND_WITH_ERROR_STATIC(c, code, msg) \ + respond_with_error_len ((c), (code), \ + mhd_SSTR_LEN (msg), (msg), \ + 0, NULL) + +/** + * Transmit static string as error response and add specified header + */ +# define mhd_RESPOND_WITH_ERROR_HEADER(c,code,m,hdrl_l,hdrl) \ + respond_with_error_len ((c), (code), \ + mhd_SSTR_LEN (m), (m), \ + (hdrl_l), (hdrl)) + +#else +/** + * Transmit static string as error response + */ +# define mhd_RESPOND_WITH_ERROR_STATIC(c, code, msg) \ + respond_with_error_len ((c), (code), \ + 0, NULL, \ + 0, NULL) + +/** + * Transmit static string as error response and add specified header + */ +# define mhd_RESPOND_WITH_ERROR_HEADER(c,code,m,hdrl_l,hdrl) \ + respond_with_error_len ((c), (code), \ + 0, NULL, \ + (hdrl_l), (hdrl)) +#endif + +#endif /* ! MHD_RESPOND_WITH_ERROR_H */ diff --git a/src/mhd2/response_add_header.c b/src/mhd2/response_add_header.c @@ -0,0 +1,165 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2021-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/response_add_header.c + * @brief The definitions of MHD_response_add_*header() functions + * @author Karlson2k (Evgeny Grin) + * @author Christian Grothoff + */ + +#include "mhd_sys_options.h" + +#include "response_add_header.h" +#include "mhd_response.h" +#include "mhd_locks.h" + +#include <string.h> +#include "sys_malloc.h" + +#include "mhd_public_api.h" + + +static +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_NONNULL_ (5) MHD_FN_PAR_CSTR_ (5) MHD_FN_PAR_IN_SIZE_ (5,4) bool +response_add_header_no_check ( + struct MHD_Response *response, + size_t name_len, + const char name[MHD_FN_PAR_DYN_ARR_SIZE_ (name_len)], + size_t value_len, + const char value[MHD_FN_PAR_DYN_ARR_SIZE_ (value_len)]) +{ + char *buf; + struct mhd_ResponseHeader *new_hdr; + + new_hdr = (struct mhd_ResponseHeader *) + malloc (sizeof(struct mhd_ResponseHeader) + name_len + + value_len + 2); + if (NULL == new_hdr) + return false; + + buf = ((char *) new_hdr) + sizeof(struct mhd_ResponseHeader); + memcpy (buf, name, name_len); + buf[name_len] = 0; + new_hdr->name.cstr = buf; + new_hdr->name.len = name_len; + buf += name_len + 1; + memcpy (buf, value, value_len); + buf[value_len] = 0; + new_hdr->value.cstr = buf; + new_hdr->value.len = value_len; + + mhd_DLINKEDL_INIT_LINKS (new_hdr, headers); + mhd_DLINKEDL_INS_LAST (response, new_hdr, headers); + return true; +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) void +mhd_response_remove_all_headers (struct MHD_Response *restrict r) +{ + struct mhd_ResponseHeader *hdr; + + for (hdr = mhd_DLINKEDL_GET_LAST (r, headers); NULL != hdr; + hdr = mhd_DLINKEDL_GET_LAST (r, headers)) + { + mhd_DLINKEDL_DEL (r, hdr, headers); + free (hdr); + } +} + + +static enum MHD_StatusCode +response_add_header_int (struct MHD_Response *response, + const char *name, + const char *value) +{ + const size_t name_len = strlen (name); + const size_t value_len = strlen (value); + + if (response->frozen) /* Re-check with the lock held */ + return MHD_SC_TOO_LATE; + + if ((NULL != memchr (name, ' ', name_len)) || + (NULL != memchr (name, '\t', name_len)) || + (NULL != memchr (name, ':', name_len)) || + (NULL != memchr (name, '\n', name_len)) || + (NULL != memchr (name, '\r', name_len))) + return MHD_SC_RESP_HEADER_NAME_INVALID; + if ((NULL != memchr (value, '\n', value_len)) || + (NULL != memchr (value, '\r', value_len))) + return MHD_SC_RESP_HEADER_VALUE_INVALID; + + if (! response_add_header_no_check (response, name_len, name, + value_len, value)) + return MHD_SC_RESPONSE_HEADER_MALLOC_FAILED; + + return MHD_SC_OK; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) enum MHD_StatusCode +MHD_response_add_header (struct MHD_Response *response, + const char *name, + const char *value) +{ + bool need_unlock; + enum MHD_StatusCode res; + + if (response->frozen) + return MHD_SC_TOO_LATE; + + if (response->reuse.reusable) + { + need_unlock = true; + if (! mhd_mutex_lock (&(response->reuse.settings_lock))) + return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED; + mhd_assert (1 == mhd_atomic_counter_get (&(response->reuse.counter))); + } + else + need_unlock = false; + + // TODO: add special processing for "Date", "Connection", "Content-Length", "Transfer-Encoding" + + res = response_add_header_int (response, name, value); + + if (need_unlock) + mhd_mutex_unlock_chk (&(response->reuse.settings_lock)); + + return res; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) enum MHD_StatusCode +MHD_response_add_predef_header (struct MHD_Response *response, + enum MHD_PredefinedHeader stk, + const char *content) +{ + (void) response; (void) stk; (void) content; + return MHD_SC_FEATURE_DISABLED; +} diff --git a/src/mhd2/response_add_header.h b/src/mhd2/response_add_header.h @@ -0,0 +1,44 @@ +/* + 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/response_add_header.h + * @brief The declarations of the response header manipulation internal + * functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_ADD_HEADER_H +#define MHD_RESPONSE_ADD_HEADER_H 1 + +#include "mhd_sys_options.h" + +struct MHD_Response; /* forward declaration */ + +/** + * Remove all response headers + * @param r the response to use + */ +MHD_INTERNAL void +mhd_response_remove_all_headers (struct MHD_Response *restrict r) +MHD_FN_PAR_NONNULL_ (1); + + +#endif /* ! MHD_RESPONSE_ADD_HEADER_H */ diff --git a/src/mhd2/response_destroy.c b/src/mhd2/response_destroy.c @@ -0,0 +1,94 @@ +/* + 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/response_destroy.c + * @brief The declarations of internal functions for response deletion + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "response_destroy.h" +#include "mhd_response.h" + +#include "mhd_assert.h" +#include "mhd_atomic_counter.h" + +#include "sys_malloc.h" + +#include "mhd_public_api.h" + +#include "response_add_header.h" +#include "response_funcs.h" +#include "response_from.h" + +/** + * Perform full response de-initialisation, with cleaning-up / freeing + * all content data and headers. + * The response settings (if any) must be already freed. + * @param r the response to free + */ +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); + free (r); +} + + +MHD_INTERNAL void +mhd_response_dec_use_count (struct MHD_Response *restrict r) +{ + mhd_assert (r->frozen); + + if (r->reuse.reusable) + { + if (0 != mhd_atomic_counter_dec_get (&(r->reuse.counter))) + return; /* The response is still used somewhere */ + } + + response_full_detinit (r); +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) void +MHD_response_destroy (struct MHD_Response *response) +{ + if (! response->frozen) + { + /* This response has been never used for actions */ + mhd_assert (NULL != response->settings); + free (response->settings); +#ifndef NDEBUG + /* Decrement counter to avoid triggering assert in deinit function */ + mhd_assert (0 == mhd_atomic_counter_dec_get (&(response->reuse.counter))); +#endif + response_full_detinit (response); + return; + } + + mhd_response_dec_use_count (response); +} diff --git a/src/mhd2/response_destroy.h b/src/mhd2/response_destroy.h @@ -0,0 +1,43 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/response_destroy.h + * @brief The declarations of internal functions for response deletion + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_DESTROY_H +#define MHD_RESPONSE_DESTROY_H 1 + +#include "mhd_sys_options.h" + +struct MHD_Response; /* forward declaration */ + +/** + * Free/destroy non-reusable response, decrement use count for reusable + * response and free/destroy if it is not used any more. + * @param response the response to manipulate + */ +MHD_INTERNAL void +mhd_response_dec_use_count (struct MHD_Response *restrict response); + + +#endif /* ! MHD_RESPONSE_DESTROY_H */ diff --git a/src/mhd2/response_from.c b/src/mhd2/response_from.c @@ -0,0 +1,405 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2021-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/response_from.c + * @brief The definitions of MHD_response_from_X() functions and related + * internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "response_from.h" + +#include <string.h> + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "compat_calloc.h" +#include "sys_malloc.h" +#include "sys_file_fd.h" + +#include "mhd_public_api.h" + +#include "mhd_locks.h" +#include "mhd_response.h" +#include "response_options.h" + +#include "mhd_assert.h" + +#include "mhd_limits.h" + +static struct MHD_Response * +response_create_basic (enum MHD_HTTP_StatusCode sc, + uint_fast64_t cntn_size, + MHD_FreeCallback free_cb, + void *free_cb_cls) +{ + struct MHD_Response *restrict r; + struct ResponseOptions *restrict s; + + if ((100 > sc) || (999 < sc)) + return NULL; + + r = mhd_calloc (1, sizeof(struct MHD_Response)); + if (NULL != r) + { + s = mhd_calloc (1, sizeof(struct ResponseOptions)); + if (NULL != s) + { +#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; +#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ + + r->sc = sc; + r->cntn_size = cntn_size; + r->free.cb = free_cb; + r->free.cls = free_cb_cls; + r->settings = s; + + return r; /* Success exit point */ + } + free (r); + } + return NULL; /* Failure exit point */ +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) void +mhd_response_deinit_content_data (struct MHD_Response *restrict r) +{ + mhd_assert (mhd_RESPONSE_CONTENT_DATA_INVALID != r->cntn_dtype); + if (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype) + free (r->cntn.iovec.iov); + else if (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype) + close (r->cntn.file.fd); + /* For #mhd_RESPONSE_CONTENT_DATA_BUFFER clean-up performed by callback + for both modes: internal copy and external cleanup */ + if (NULL != r->free.cb) + r->free.cb (r->free.cls); +} + + +MHD_EXTERN_ struct MHD_Response * +MHD_response_from_callback (enum MHD_HTTP_StatusCode sc, + uint_fast64_t size, + MHD_DynamicContentCreator dyn_cont, + void *dyn_cont_cls, + MHD_FreeCallback dyn_cont_fc) +{ + struct MHD_Response *restrict res; + res = response_create_basic (sc, size, dyn_cont_fc, dyn_cont_cls); + if (NULL != res) + { + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_CALLBACK; + res->cntn.dyn.cb = dyn_cont; + res->cntn.dyn.cls = dyn_cont_cls; + } + return res; +} + + +static const unsigned char empty_buf[1] = { 0 }; + +MHD_EXTERN_ +MHD_FN_PAR_IN_SIZE_ (3,2) struct MHD_Response * +MHD_response_from_buffer ( + enum MHD_HTTP_StatusCode sc, + size_t buffer_size, + const char buffer[MHD_FN_PAR_DYN_ARR_SIZE_ (buffer_size)], + MHD_FreeCallback free_cb, + void *free_cb_cls) +{ + struct MHD_Response *restrict res; + + if (MHD_SIZE_UNKNOWN == buffer_size) + return NULL; + + res = response_create_basic (sc, buffer_size, free_cb, free_cb_cls); + if (NULL != res) + { + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_BUFFER; + res->cntn.buf = (0 != buffer_size) ? + (const unsigned char *) buffer : empty_buf; + } + return res; +} + + +static void +response_cntn_free_buf (void *ptr) +{ + free (ptr); +} + + +MHD_EXTERN_ +MHD_FN_PAR_IN_SIZE_ (3,2) struct MHD_Response * +MHD_response_from_buffer_copy ( + enum MHD_HTTP_StatusCode sc, + size_t buffer_size, + const char buffer[MHD_FN_PAR_DYN_ARR_SIZE_ (buffer_size)]) +{ + struct MHD_Response *restrict res; + const unsigned char *buf_copy; + + if (MHD_SIZE_UNKNOWN == buffer_size) + return NULL; + + if (0 != buffer_size) + { + unsigned char *new_buf; + new_buf = (unsigned char *) malloc (buffer_size); + if (NULL == new_buf) + return NULL; + memcpy (new_buf, buffer, buffer_size); + res = response_create_basic (sc, buffer_size, + response_cntn_free_buf, new_buf); + buf_copy = new_buf; + } + else + { + buf_copy = empty_buf; + res = response_create_basic (sc, 0, NULL, NULL); + } + + if (NULL != res) + { + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_BUFFER; + res->cntn.buf = buf_copy; + } + return res; +} + + +MHD_EXTERN_ struct MHD_Response * +MHD_response_from_iovec ( + enum MHD_HTTP_StatusCode sc, + unsigned int iov_count, + const struct MHD_IoVec iov[MHD_FN_PAR_DYN_ARR_SIZE_ (iov_count)], + MHD_FreeCallback free_cb, + void *free_cb_cls) +{ + unsigned int i; + size_t i_cp = 0; /**< Index in the copy of iov */ + uint_fast64_t total_size = 0; + + /* Calculate final size, number of valid elements, and check 'iov' */ + for (i = 0; i < iov_count; ++i) + { + if (0 == iov[i].iov_len) + continue; /* skip zero-sized elements */ + if (NULL == iov[i].iov_base) + return NULL; /* NULL pointer with non-zero size */ + + total_size += iov[i].iov_len; + if ((total_size < iov[i].iov_len) || (0 > (ssize_t) total_size) + || (((size_t) total_size) != total_size)) + return NULL; /* Larger than send function may report as success */ +#if defined(MHD_POSIX_SOCKETS) || ! defined(_WIN64) + i_cp++; +#else /* ! MHD_POSIX_SOCKETS && _WIN64 */ + if (1) + { + size_t i_add; + + i_add = (size_t) (iov[i].iov_len / mhd_IOV_ELMN_MAX_SIZE); + if (0 != iov[i].iov_len % mhd_IOV_ELMN_MAX_SIZE) + i_add++; + i_cp += i_add; + if (i_cp < i_add) + return NULL; /* Counter overflow */ + } +#endif /* ! MHD_POSIX_SOCKETS && _WIN64 */ + } + if (0 == total_size) + { + struct MHD_Response *restrict res; + + res = response_create_basic (sc, 0, free_cb, free_cb_cls); + if (NULL != res) + { + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_BUFFER; + res->cntn.buf = empty_buf; + } + return res; + } + if (MHD_SIZE_UNKNOWN == total_size) + return NULL; + + mhd_assert (0 < i_cp); + if (1) + { /* for local variables local scope only */ + struct MHD_Response *restrict res; + mhd_iovec *iov_copy; + size_t num_copy_elements = i_cp; + + iov_copy = mhd_calloc (num_copy_elements, sizeof(mhd_iovec)); + if (NULL == iov_copy) + return NULL; + + i_cp = 0; + for (i = 0; i < iov_count; ++i) + { + size_t element_size = iov[i].iov_len; + const unsigned char *buf = (const unsigned char *) iov[i].iov_base; + + if (0 == element_size) + continue; /* skip zero-sized elements */ +#if defined(MHD_WINSOCK_SOCKETS) && defined(_WIN64) + while (mhd_IOV_ELMN_MAX_SIZE < element_size) + { + iov_copy[i_cp].iov_base = (char *) mhd_DROP_CONST (buf); + iov_copy[i_cp].iov_len = mhd_IOV_ELMN_MAX_SIZE; + buf += mhd_IOV_ELMN_MAX_SIZE; + element_size -= mhd_IOV_ELMN_MAX_SIZE; + i_cp++; + } +#endif /* MHD_WINSOCK_SOCKETS && _WIN64 */ + iov_copy[i_cp].iov_base = mhd_DROP_CONST (buf); + iov_copy[i_cp].iov_len = (mhd_iov_elmn_size) element_size; + i_cp++; + } + mhd_assert (num_copy_elements == i_cp); + mhd_assert (0 < i_cp); + + res = response_create_basic (sc, total_size, free_cb, free_cb_cls); + if (NULL != res) + { + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_IOVEC; + res->cntn.iovec.iov = iov_copy; + res->cntn.iovec.cnt = i_cp; + return res; /* Success exit point */ + } + + /* Below is a cleanup path */ + free (iov_copy); + } + return NULL; +} + + +MHD_EXTERN_ +MHD_FN_PAR_FD_READ_ (2) struct MHD_Response * +MHD_response_from_fd (enum MHD_HTTP_StatusCode sc, + int fd, + uint_fast64_t offset, + uint_fast64_t size) +{ + struct MHD_Response *restrict res; + if (offset == MHD_SIZE_UNKNOWN) + return NULL; + if (size != MHD_SIZE_UNKNOWN) + { + if (size > ((size + offset) & 0xFFFFFFFFFFFFFFFFU)) + return NULL; + } + res = response_create_basic (sc, size, NULL, NULL); + if (NULL != res) + { + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_FILE; + res->cntn.file.fd = fd; + res->cntn.file.offset = offset; +#ifdef MHD_USE_SENDFILE + res->cntn.file.use_sf = (size < MHD_SIZE_UNKNOWN); +#endif + res->cntn.file.is_pipe = false; /* Not necessary */ + } + return res; +} + + +MHD_EXTERN_ +MHD_FN_PAR_FD_READ_ (2) struct MHD_Response * +MHD_response_from_pipe (enum MHD_HTTP_StatusCode sc, + int fd) +{ + struct MHD_Response *restrict res; + res = response_create_basic (sc, MHD_SIZE_UNKNOWN, NULL, NULL); + if (NULL != res) + { + res->cntn_dtype = mhd_RESPONSE_CONTENT_DATA_FILE; + res->cntn.file.fd = fd; + res->cntn.file.offset = 0; /* Not necessary */ +#ifdef MHD_USE_SENDFILE + res->cntn.file.use_sf = false; /* Not necessary */ +#endif + res->cntn.file.is_pipe = true; + } + 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 @@ -0,0 +1,53 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/response_from.h + * @brief The declarations of internal functions for response creation and + * deletion + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_FROM_H +#define MHD_RESPONSE_FROM_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" + +struct MHD_Response; /* forward declaration */ + +/** + * Deinit / free / cleanup content data of the response + * @param r the response to use + */ +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 @@ -0,0 +1,137 @@ +/* + 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/response_funcs.с + * @brief The definition of the internal response helper functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_malloc.h" + +#include "sys_null_macro.h" +#include "mhd_response.h" +#include "response_funcs.h" +#include "mhd_locks.h" +#include "response_options.h" + + +#include "mhd_atomic_counter.h" + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) bool +response_make_reusable (struct MHD_Response *restrict r) +{ + mhd_assert (! r->reuse.reusable); + mhd_assert (! r->frozen); + mhd_assert (NULL != r->settings); + + if (mhd_mutex_init (&(r->reuse.settings_lock))) + { + if (mhd_atomic_counter_init (&(r->reuse.counter), 1)) + { + r->reuse.reusable = true; + return true; + } + (void) mhd_mutex_destroy (&(r->reuse.settings_lock)); + } + return false; +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) void +mhd_response_deinit_reusable (struct MHD_Response *restrict r) +{ + mhd_assert (r->reuse.reusable); + mhd_assert (0 == mhd_atomic_counter_get (&(r->reuse.counter))); + + mhd_atomic_counter_deinit (&(r->reuse.counter)); + mhd_mutex_destroy_chk (&(r->reuse.settings_lock)); +} + + +static void +response_set_properties (struct MHD_Response *restrict r) +{ + struct ResponseOptions *restrict const s = r->settings; + mhd_assert (NULL != s); + + r->cfg.head_only = s->head_only_response; + if (s->http_1_0_compatible_strict) + { + r->cfg.close_forced = true; + r->cfg.chunked = false; + r->cfg.mode_1_0 = s->http_1_0_server; + } + else if (s->http_1_0_server) + { + r->cfg.close_forced = s->conn_close || (MHD_SIZE_UNKNOWN == r->cntn_size); + r->cfg.chunked = false; + r->cfg.mode_1_0 = true; + } + else + { + r->cfg.close_forced = s->conn_close; + r->cfg.chunked = s->chunked_enc || (MHD_SIZE_UNKNOWN == r->cntn_size); + r->cfg.mode_1_0 = false; + } + + r->cfg.cnt_len_by_app = s->insanity_header_content_length; // TODO: set only if "content-lengh" header is used + + // TODO: calculate size of the headers and the "Connection:" header + + r->frozen = true; + + r->settings = NULL; + free (s); +} + + +/** + * Check whether response is "frozen" (modifications blocked) and "freeze" + * it if it was not frozen before + * @param response the response to manipulate + */ +MHD_INTERNAL void +mhd_response_check_frozen_freeze (struct MHD_Response *restrict response) +{ + bool need_unlock; + if (response->frozen) + return; + + if (response->reuse.reusable) + { + need_unlock = true; + mhd_mutex_lock_chk (&(response->reuse.settings_lock)); + mhd_assert (1 == mhd_atomic_counter_get (&(response->reuse.counter))); + } + else + need_unlock = false; + + if (! response->frozen)/* Re-check under the lock */ + response_set_properties (response); + + if (need_unlock) + mhd_mutex_unlock_chk (&(response->reuse.settings_lock)); +} diff --git a/src/mhd2/response_funcs.h b/src/mhd2/response_funcs.h @@ -0,0 +1,64 @@ +/* + 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/response_funcs.h + * @brief The declarations of the internal response helper functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_FUNCS_H +#define MHD_RESPONSE_FUNCS_H 1 + +#include "mhd_sys_options.h" +#include "sys_bool_type.h" + + +struct MHD_Response; /* forward declaration */ + +/** + * Make response re-usable, initialise all required data + * @param r the response to make re-usable + * @return 'true' if succeed, 'false' if failed + */ +MHD_INTERNAL bool +response_make_reusable (struct MHD_Response *restrict r) +MHD_FN_PAR_NONNULL_ (1); + + +/** + * De-initialise re-usability data + * @param r the response to de-initialise re-usability data + */ +MHD_INTERNAL void +mhd_response_deinit_reusable (struct MHD_Response *restrict r) +MHD_FN_PAR_NONNULL_ (1); + + +/** + * Check whether response is "frozen" (modifications blocked) and "freeze" + * it if not frozen before + * @param response the response to manipulate + */ +MHD_INTERNAL void +mhd_response_check_frozen_freeze (struct MHD_Response *restrict r); + + +#endif /* ! MHD_RESPONSE_FUNCS_H */ diff --git a/src/mhd2/response_options.h b/src/mhd2/response_options.h @@ -0,0 +1,69 @@ +/* This is generated code, it is still under LGPLv2.1+. + Do not edit directly! */ +/* *INDENT-OFF* */ +/** + * @file response_options.h + * @author response-options-generator.c + */ + +#ifndef MHD_RESPONSE_OPTIONS_H +#define MHD_RESPONSE_OPTIONS_H 1 + +#include "mhd_sys_options.h" +#include "mhd_public_api.h" + +struct ResponseOptions +{ + /** + * Value for #MHD_R_O_REUSABLE. + */ + enum MHD_Bool reusable; + + + /** + * Value for #MHD_R_O_HEAD_ONLY_RESPONSE. + */ + enum MHD_Bool head_only_response; + + + /** + * Value for #MHD_R_O_CHUNKED_ENC. + */ + enum MHD_Bool chunked_enc; + + + /** + * Value for #MHD_R_O_CONN_CLOSE. + */ + enum MHD_Bool conn_close; + + + /** + * Value for #MHD_R_O_HTTP_1_0_COMPATIBLE_STRICT. + */ + enum MHD_Bool http_1_0_compatible_strict; + + + /** + * Value for #MHD_R_O_HTTP_1_0_SERVER. + */ + enum MHD_Bool http_1_0_server; + + + /** + * Value for #MHD_R_O_INSANITY_HEADER_CONTENT_LENGTH. + */ + enum MHD_Bool insanity_header_content_length; + + + /** + * Value for #MHD_R_O_TERMINATION_CALLBACK. + * the function to call, + * NULL to not use the callback + */ + struct MHD_ResponeOptionValueTermCB termination_callback; + + +}; + +#endif /* ! MHD_RESPONSE_OPTIONS_H 1 */ diff --git a/src/mhd2/response_set_options.c b/src/mhd2/response_set_options.c @@ -0,0 +1,94 @@ +/* This is generated code, it is still under LGPLv2.1+. + Do not edit directly! */ +/* *INDENT-OFF* */ +/** + * @file response_set_options.c + * @author response-options-generator.c + */ + +#include "mhd_sys_options.h" +#include "sys_bool_type.h" +#include "sys_base_types.h" +#include "sys_malloc.h" +#include <string.h> +#include "mhd_response.h" +#include "response_options.h" +#include "mhd_public_api.h" +#include "mhd_locks.h" +#include "mhd_assert.h" +#include "response_funcs.h" + +MHD_FN_PAR_NONNULL_ALL_ MHD_EXTERN_ +enum MHD_StatusCode +MHD_response_set_options ( + struct MHD_Response *response, + const struct MHD_ResponseOptionAndValue *options, + size_t options_max_num) +{ + struct ResponseOptions *restrict settings = response->settings; + enum MHD_StatusCode res = MHD_SC_OK; + size_t i; + bool need_unlock = false; + + if (response->frozen) + return MHD_SC_TOO_LATE; + if (response->reuse.reusable) + { + need_unlock = true; + if (! mhd_mutex_lock(&response->reuse.settings_lock)) + return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED; + mhd_assert (1 == mhd_atomic_counter_get(&response->reuse.counter)); + if (! response->frozen) /* Firm re-check under the lock */ + { + mhd_mutex_unlock_chk(&response->reuse.settings_lock); + return MHD_SC_TOO_LATE; + } + } + + for (i = 0; i < options_max_num; i++) + { + const struct MHD_ResponseOptionAndValue *const option + = options + i; + switch (option->opt) + { + case MHD_R_O_END: + i = options_max_num - 1; + break; + case MHD_R_O_REUSABLE: + settings->reusable = option->val.reusable; + continue; + case MHD_R_O_HEAD_ONLY_RESPONSE: + settings->head_only_response = option->val.head_only_response; + continue; + case MHD_R_O_CHUNKED_ENC: + settings->chunked_enc = option->val.chunked_enc; + continue; + case MHD_R_O_CONN_CLOSE: + settings->conn_close = option->val.conn_close; + continue; + case MHD_R_O_HTTP_1_0_COMPATIBLE_STRICT: + settings->http_1_0_compatible_strict = option->val.http_1_0_compatible_strict; + continue; + case MHD_R_O_HTTP_1_0_SERVER: + settings->http_1_0_server = option->val.http_1_0_server; + continue; + case MHD_R_O_INSANITY_HEADER_CONTENT_LENGTH: + settings->insanity_header_content_length = option->val.insanity_header_content_length; + continue; + case MHD_R_O_TERMINATION_CALLBACK: + settings->termination_callback.v_term_cb = option->val.termination_callback.v_term_cb; + settings->termination_callback.v_term_cb_cls = option->val.termination_callback.v_term_cb_cls; + continue; + case MHD_R_O_SENTINEL: + default: /* for -Wswitch-default -Wswitch-enum */ + res = MHD_SC_OPTION_UNKNOWN; + i = options_max_num - 1; + break; + } + } + + if (need_unlock) + mhd_mutex_unlock_chk(&response->reuse.settings_lock); + + return res; +} diff --git a/src/mhd2/response_set_options.h b/src/mhd2/response_set_options.h @@ -0,0 +1,35 @@ +/* + 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/response_set_options.h + * @brief The declarations of the internal response functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_SET_OPTIONS_H +#define MHD_RESPONSE_SET_OPTIONS_H 1 + +#include "mhd_sys_options.h" + +struct MHD_Response; /* forward declaration */ + + +#endif /* ! MHD_RESPONSE_SET_OPTIONS_H */ diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c @@ -0,0 +1,899 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-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/stream_funcs.c + * @brief The definition of the stream internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "stream_funcs.h" + +#include <string.h> +#ifdef MHD_USE_EPOLL +# include <sys/epoll.h> +#endif +#include "sys_malloc.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" +#include "mhd_response.h" +#include "mhd_assert.h" +#include "mhd_mempool.h" +#include "mhd_str.h" +#include "mhd_str_macros.h" + +#include "mhd_sockets_funcs.h" + +#include "request_get_value.h" +#include "response_destroy.h" +#include "mhd_mono_clock.h" +#include "daemon_logger.h" +#include "daemon_funcs.h" +#include "conn_mark_ready.h" +#include "stream_process_reply.h" + +#include "mhd_public_api.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void * +mhd_stream_alloc_memory (struct MHD_Connection *restrict c, + size_t size) +{ + struct mhd_MemoryPool *const restrict pool = c->pool; /* a short alias */ + size_t need_to_be_freed = 0; /**< The required amount of additional free memory */ + void *res; + + res = mhd_pool_try_alloc (pool, + size, + &need_to_be_freed); + if (NULL != res) + return res; + + if (mhd_pool_is_resizable_inplace (pool, + c->write_buffer, + c->write_buffer_size)) + { + if (c->write_buffer_size - c->write_buffer_append_offset >= + need_to_be_freed) + { + char *buf; + const size_t new_buf_size = c->write_buffer_size - need_to_be_freed; + buf = mhd_pool_reallocate (pool, + c->write_buffer, + c->write_buffer_size, + new_buf_size); + mhd_assert (c->write_buffer == buf); + mhd_assert (c->write_buffer_append_offset <= new_buf_size); + mhd_assert (c->write_buffer_send_offset <= new_buf_size); + c->write_buffer_size = new_buf_size; + c->write_buffer = buf; + } + else + return NULL; + } + else if (mhd_pool_is_resizable_inplace (pool, + c->read_buffer, + c->read_buffer_size)) + { + if (c->read_buffer_size - c->read_buffer_offset >= need_to_be_freed) + { + char *buf; + const size_t new_buf_size = c->read_buffer_size - need_to_be_freed; + buf = mhd_pool_reallocate (pool, + c->read_buffer, + c->read_buffer_size, + new_buf_size); + mhd_assert (c->read_buffer == buf); + mhd_assert (c->read_buffer_offset <= new_buf_size); + c->read_buffer_size = new_buf_size; + c->read_buffer = buf; + } + else + return NULL; + } + else + return NULL; + res = mhd_pool_allocate (pool, size, true); + mhd_assert (NULL != res); /* It has been checked that pool has enough space */ + return res; +} + + +/** + * Shrink stream read buffer to the zero size of free space in the buffer + * @param c the connection whose read buffer is being manipulated + */ +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_shrink_read_buffer (struct MHD_Connection *restrict c) +{ + void *new_buf; + + if ((NULL == c->read_buffer) || (0 == c->read_buffer_size)) + { + mhd_assert (0 == c->read_buffer_size); + mhd_assert (0 == c->read_buffer_offset); + return; + } + + mhd_assert (c->read_buffer_offset <= c->read_buffer_size); + if (0 == c->read_buffer_offset) + { + mhd_pool_deallocate (c->pool, c->read_buffer, c->read_buffer_size); + c->read_buffer = NULL; + c->read_buffer_size = 0; + } + else + { + mhd_assert (mhd_pool_is_resizable_inplace (c->pool, c->read_buffer, \ + c->read_buffer_size)); + new_buf = mhd_pool_reallocate (c->pool, c->read_buffer, c->read_buffer_size, + c->read_buffer_offset); + mhd_assert (c->read_buffer == new_buf); + c->read_buffer = new_buf; + c->read_buffer_size = c->read_buffer_offset; + } +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ size_t +mhd_stream_maximize_write_buffer (struct MHD_Connection *restrict c) +{ + struct mhd_MemoryPool *const restrict pool = c->pool; + void *new_buf; + size_t new_size; + size_t free_size; + + mhd_assert ((NULL != c->write_buffer) || (0 == c->write_buffer_size)); + mhd_assert (c->write_buffer_append_offset >= c->write_buffer_send_offset); + mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset); + + free_size = mhd_pool_get_free (pool); + if (0 != free_size) + { + new_size = c->write_buffer_size + free_size; + /* This function must not move the buffer position. + * mhd_pool_reallocate () may return the new position only if buffer was + * allocated 'from_end' or is not the last allocation, + * which should not happen. */ + mhd_assert ((NULL == c->write_buffer) || \ + mhd_pool_is_resizable_inplace (pool, c->write_buffer, \ + c->write_buffer_size)); + new_buf = mhd_pool_reallocate (pool, + c->write_buffer, + c->write_buffer_size, + new_size); + mhd_assert ((c->write_buffer == new_buf) || (NULL == c->write_buffer)); + c->write_buffer = new_buf; + c->write_buffer_size = new_size; + if (c->write_buffer_send_offset == c->write_buffer_append_offset) + { + /* All data have been sent, reset offsets to zero. */ + c->write_buffer_send_offset = 0; + c->write_buffer_append_offset = 0; + } + } + + return c->write_buffer_size - c->write_buffer_append_offset; +} + + +#ifndef MHD_MAX_REASONABLE_HEADERS_SIZE_ +/** + * A reasonable headers size (excluding request line) that should be sufficient + * for most requests. + * If incoming data buffer free space is not enough to process the complete + * header (the request line and all headers) and the headers size is larger than + * this size then the status code 431 "Request Header Fields Too Large" is + * returned to the client. + * The larger headers are processed by MHD if enough space is available. + */ +# define MHD_MAX_REASONABLE_HEADERS_SIZE_ (6 * 1024) +#endif /* ! MHD_MAX_REASONABLE_HEADERS_SIZE_ */ + +#ifndef MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ +/** + * A reasonable request target (the request URI) size that should be sufficient + * for most requests. + * If incoming data buffer free space is not enough to process the complete + * header (the request line and all headers) and the request target size is + * larger than this size then the status code 414 "URI Too Long" is + * returned to the client. + * The larger request targets are processed by MHD if enough space is available. + * The value chosen according to RFC 9112 Section 3, paragraph 5 + */ +# define MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ 8000 +#endif /* ! MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_HEADERS_SIZE_ +/** + * A reasonable headers size (excluding request line) that should be sufficient + * for basic simple requests. + * When no space left in the receiving buffer try to avoid replying with + * the status code 431 "Request Header Fields Too Large" if headers size + * is smaller then this value. + */ +# define MHD_MIN_REASONABLE_HEADERS_SIZE_ 26 +#endif /* ! MHD_MIN_REASONABLE_HEADERS_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ +/** + * A reasonable request target (the request URI) size that should be sufficient + * for basic simple requests. + * When no space left in the receiving buffer try to avoid replying with + * the status code 414 "URI Too Long" if the request target size is smaller then + * this value. + */ +# define MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ 40 +#endif /* ! MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ +/** + * A reasonable request method string size that should be sufficient + * for basic simple requests. + * When no space left in the receiving buffer try to avoid replying with + * the status code 501 "Not Implemented" if the request method size is + * smaller then this value. + */ +# define MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ 16 +#endif /* ! MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ */ + +#ifndef MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ +/** + * A reasonable minimal chunk line length. + * When no space left in the receiving buffer reply with 413 "Content Too Large" + * if the chunk line length is larger than this value. + */ +# define MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ 4 +#endif /* ! MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ */ + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_IN_SIZE_ (4,3) unsigned int +mhd_stream_get_no_space_err_status_code (struct MHD_Connection *restrict c, + enum MHD_ProcRecvDataStage stage, + size_t add_element_size, + const char *restrict add_element) +{ + size_t method_size; + size_t uri_size; + size_t opt_headers_size; + size_t host_field_line_size; + + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVED < c->state); + 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); + opt_headers_size = + (size_t) ((c->read_buffer + c->read_buffer_offset) + - c->rq.field_lines.start); + } + else + opt_headers_size = c->rq.field_lines.size; + + /* The read buffer is fully used by the request line, the field lines + (headers) and internal information. + The return status code works as a suggestion for the client to reduce + one of the request elements. */ + + if ((MHD_PROC_RECV_BODY_CHUNKED == stage) && + (MHD_MIN_REASONABLE_REQ_CHUNK_LINE_LENGTH_ < add_element_size)) + { + /* Request could be re-tried easily with smaller chunk sizes */ + return MHD_HTTP_STATUS_CONTENT_TOO_LARGE; + } + + host_field_line_size = 0; + /* The "Host:" field line is mandatory. + The total size of the field lines (headers) cannot be smaller than + the size of the "Host:" field line. */ + if ((MHD_PROC_RECV_HEADERS == stage) + && (0 != add_element_size)) + { + static const size_t header_host_key_len = + mhd_SSTR_LEN (MHD_HTTP_HEADER_HOST); + const bool is_host_header = + (header_host_key_len + 1 <= add_element_size) + && ( (0 == add_element[header_host_key_len]) + || (':' == add_element[header_host_key_len]) ) + && mhd_str_equal_caseless_bin_n (MHD_HTTP_HEADER_HOST, + add_element, + header_host_key_len); + if (is_host_header) + { + const bool is_parsed = ! ( + (MHD_CONNECTION_HEADERS_RECEIVED > c->state) && + (add_element_size == c->read_buffer_offset) && + (c->read_buffer == add_element) ); + size_t actual_element_size; + + mhd_assert (! is_parsed || (0 == add_element[header_host_key_len])); + /* The actual size should be larger due to CRLF or LF chars, + however the exact termination sequence is not known here and + as perfect precision is not required, to simplify the code + assume the minimal length. */ + if (is_parsed) + actual_element_size = add_element_size + 1; /* "1" for LF */ + else + actual_element_size = add_element_size; + + host_field_line_size = actual_element_size; + mhd_assert (opt_headers_size >= actual_element_size); + opt_headers_size -= actual_element_size; + } + } + if (0 == host_field_line_size) + { + static const size_t host_field_name_len = + mhd_SSTR_LEN (MHD_HTTP_HEADER_HOST); + const struct MHD_StringNullable *host_value; + host_value = mhd_request_get_value_n (&(c->rq), + MHD_VK_HEADER, + host_field_name_len, + MHD_HTTP_HEADER_HOST); + if (NULL != host_value) + { + /* Calculate the minimal size of the field line: no space between + colon and the field value, line terminated by LR */ + host_field_line_size = + host_field_name_len + host_value->len + 2; /* "2" for ':' and LF */ + + /* The "Host:" field could be added by application */ + if (opt_headers_size >= host_field_line_size) + { + opt_headers_size -= host_field_line_size; + /* Take into account typical space after colon and CR at the end of the line */ + if (opt_headers_size >= 2) + opt_headers_size -= 2; + } + else + host_field_line_size = 0; /* No "Host:" field line set by the client */ + } + } + + uri_size = c->rq.req_target_len; + if (mhd_HTTP_METHOD_OTHER != c->rq.http_mthd) + method_size = 0; /* Do not recommend shorter request method */ + else + { + mhd_assert (NULL != c->rq.method); + method_size = strlen (c->rq.method); + } + + if ((size_t) MHD_MAX_REASONABLE_HEADERS_SIZE_ < opt_headers_size) + { + /* Typically the easiest way to reduce request header size is + a removal of some optional headers. */ + if (opt_headers_size > (uri_size / 8)) + { + if ((opt_headers_size / 2) > method_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + else + { /* Request target is MUCH larger than headers */ + if ((uri_size / 16) > method_size) + return MHD_HTTP_STATUS_URI_TOO_LONG; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + } + if ((size_t) MHD_MAX_REASONABLE_REQ_TARGET_SIZE_ < uri_size) + { + /* If request target size if larger than maximum reasonable size + recommend client to reduce the request target size (length). */ + if ((uri_size / 16) > method_size) + return MHD_HTTP_STATUS_URI_TOO_LONG; /* Request target is MUCH larger than headers */ + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + + /* The read buffer is too small to handle reasonably large requests */ + + if ((size_t) MHD_MIN_REASONABLE_HEADERS_SIZE_ < opt_headers_size) + { + /* Recommend application to retry with minimal headers */ + if ((opt_headers_size * 4) > uri_size) + { + if (opt_headers_size > method_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + else + { /* Request target is significantly larger than headers */ + if (uri_size > method_size * 4) + return MHD_HTTP_STATUS_URI_TOO_LONG; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + } + if ((size_t) MHD_MIN_REASONABLE_REQ_TARGET_SIZE_ < uri_size) + { + /* Recommend application to retry with a shorter request target */ + if (uri_size > method_size * 4) + return MHD_HTTP_STATUS_URI_TOO_LONG; + else + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; /* The length of the HTTP request method is unreasonably large */ + } + + if ((size_t) MHD_MIN_REASONABLE_REQ_METHOD_SIZE_ < method_size) + { + /* The request target (URI) and headers are (reasonably) very small. + Some non-standard long request method is used. */ + /* The last resort response as it means "the method is not supported + by the server for any URI". */ + return MHD_HTTP_STATUS_NOT_IMPLEMENTED; + } + + /* The almost impossible situation: all elements are small, but cannot + fit the buffer. The application set the buffer size to + critically low value? */ + + if ((1 < opt_headers_size) || (1 < uri_size)) + { + if (opt_headers_size >= uri_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + else + return MHD_HTTP_STATUS_URI_TOO_LONG; + } + + /* Nothing to reduce in the request. + Reply with some status. */ + if (0 != host_field_line_size) + return MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE; + + return MHD_HTTP_STATUS_URI_TOO_LONG; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_switch_from_recv_to_send (struct MHD_Connection *c) +{ + /* Read buffer is not needed for this request, shrink it.*/ + mhd_stream_shrink_read_buffer (c); +} + + +/** + * Finish request serving. + * The stream will be re-used or closed. + * + * @param c the connection to use. + */ +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_finish_req_serving (struct MHD_Connection *restrict c, + bool reuse) +{ + struct MHD_Daemon *const restrict d = c->daemon; + + 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 (c, + c->stop_with_error ? + mhd_CONN_CLOSE_ERR_REPLY_SENT : + mhd_CONN_CLOSE_HTTP_COMPLETED, + NULL); + } + else + { + /* Reset connection to process the next request */ + 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) && + (c->rq.app_aware) ) + d->notify_completed (d->notify_completed_cls, + c, + &c->rq.app_context, + MHD_REQUEST_TERMINATED_COMPLETED_OK); + c->rq.app_aware = false; +#endif + + mhd_stream_call_dcc_cleanup_if_needed (c); + if (NULL != c->rp.resp_iov.iov) + { + free (c->rp.resp_iov.iov); + c->rp.resp_iov.iov = NULL; + } + + if (NULL != c->rp.response) + mhd_response_dec_use_count (c->rp.response); + c->rp.response = NULL; + + c->conn_reuse = mhd_CONN_KEEPALIVE_POSSIBLE; + c->state = MHD_CONNECTION_INIT; + c->event_loop_info = + (0 == c->read_buffer_offset) ? + MHD_EVENT_LOOP_INFO_READ : MHD_EVENT_LOOP_INFO_PROCESS; + + memset (&c->rq, 0, sizeof(c->rq)); + + /* iov (if any) will be deallocated by mhd_pool_reset */ + memset (&c->rp, 0, sizeof(c->rp)); + + // TODO: set all rq and tp pointers to NULL manually. Do the same in other places. + + c->write_buffer = NULL; + c->write_buffer_size = 0; + c->write_buffer_send_offset = 0; + c->write_buffer_append_offset = 0; + c->continue_message_write_offset = 0; + + /* Reset the read buffer to the starting size, + preserving the bytes we have already read. */ + new_read_buf_size = d->conns.cfg.mem_pool_size / 2; + if (c->read_buffer_offset > new_read_buf_size) + new_read_buf_size = c->read_buffer_offset; + + c->read_buffer + = mhd_pool_reset (c->pool, + c->read_buffer, + c->read_buffer_offset, + new_read_buf_size); + c->read_buffer_size = new_read_buf_size; + } + c->rq.app_context = NULL; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_check_timedout (struct MHD_Connection *restrict c) +{ + const uint_fast64_t timeout = c->connection_timeout_ms; + uint_fast64_t now; + uint_fast64_t since_actv; + + mhd_assert (! c->suspended); + + if (0 == timeout) + return false; + + now = MHD_monotonic_msec_counter (); // TODO: Get and use timer value one time only per round + since_actv = now - c->last_activity; + /* Keep the next lines in sync with #connection_get_wait() to avoid + * undesired side-effects like busy-waiting. */ + if (timeout < since_actv) + { + const uint_fast64_t jump_back = c->last_activity - now; + if (jump_back < since_actv) + { + /* Very unlikely that it is more than quarter-million years pause. + * More likely that system clock jumps back. */ + if (4000 >= jump_back) + { + c->last_activity = now; /* Avoid repetitive messages. + Warn: the order of connections sorted + by timeout is not updated. */ + mhd_LOG_PRINT (c->daemon, MHD_SC_SYS_CLOCK_JUMP_BACK_CORRECTED, \ + mhd_LOG_FMT ("Detected system clock %u " \ + "milliseconds jump back."), + (unsigned int) jump_back); + return false; + } + mhd_LOG_PRINT (c->daemon, MHD_SC_SYS_CLOCK_JUMP_BACK_LARGE, \ + mhd_LOG_FMT ("Detected too large system clock %" \ + PRIuFAST64 " milliseconds jump back"), + jump_back); + } + return true; + } + return false; +} + + +/** + * Update last activity mark to the current time.. + * @param c the connection to update + */ +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_update_activity_mark (struct MHD_Connection *restrict c) +{ + struct MHD_Daemon *const restrict d = c->daemon; +#if defined(MHD_USE_THREADS) + mhd_assert (! mhd_D_HAS_WORKERS (d)); +#endif /* MHD_USE_THREADS */ + + mhd_assert (! c->suspended); + + if (0 == c->connection_timeout_ms) + return; /* Skip update of activity for connections + without timeout timer. */ + + c->last_activity = MHD_monotonic_msec_counter (); // TODO: Get and use time value one time per round + if (mhd_D_HAS_THR_PER_CONN (d)) + return; /* each connection has personal timeout */ + + if (c->connection_timeout_ms != d->conns.cfg.timeout) + return; /* custom timeout, no need to move it in "normal" DLL */ + + /* move connection to head of timeout list (by remove + add operation) */ + mhd_DLINKEDL_DEL_D (&(d->conns.def_timeout), c, by_timeout); + mhd_DLINKEDL_INS_FIRST_D (&(d->conns.def_timeout), c, by_timeout); +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3) void +mhd_conn_pre_close (struct MHD_Connection *restrict c, + enum mhd_ConnCloseReason reason, + const char *log_msg) +{ + bool close_hard; + bool use_local_lingering; + enum MHD_RequestTerminationCode term_code; + enum MHD_StatusCode sc; + + sc = MHD_SC_INTERNAL_ERROR; + switch (reason) + { + case mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN: + close_hard = true; + term_code = MHD_REQUEST_TERMINATED_HTTP_PROTOCOL_ERROR; + sc = MHD_SC_REQ_MALFORMED; + break; + case mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REQUEST: + close_hard = true; + term_code = MHD_REQUEST_TERMINATED_NO_RESOURCES; + break; + case mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY: + close_hard = true; + term_code = MHD_REQUEST_TERMINATED_CLIENT_ABORT; + sc = MHD_SC_REPLY_POOL_ALLOCATION_FAILURE; + break; + case mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY: + close_hard = true; + 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; + term_code = MHD_REQUEST_TERMINATED_BY_APP_ERROR; + sc = MHD_SC_APPLICATION_DATA_GENERATION_FAILURE_CLOSED; + break; + case mhd_CONN_CLOSE_APP_ABORTED: + close_hard = true; + term_code = MHD_REQUEST_TERMINATED_BY_APP_ABORT; + sc = MHD_SC_APPLICATION_CALLBACK_ABORT_ACTION; + break; + case mhd_CONN_CLOSE_INT_ERROR: + close_hard = true; + term_code = MHD_REQUEST_TERMINATED_NO_RESOURCES; + break; + case mhd_CONN_CLOSE_SOCKET_ERR: + close_hard = true; + switch (c->sk_discnt_err) + { + 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_CONNECTION_INIT == c->state) ? + MHD_REQUEST_TERMINATED_COMPLETED_OK /* Not used */ : + MHD_REQUEST_TERMINATED_CLIENT_ABORT; + break; + case mhd_SOCKET_ERR_CONNRESET: + term_code = MHD_REQUEST_TERMINATED_CLIENT_ABORT; + break; + case mhd_SOCKET_ERR_CONN_BROKEN: + case mhd_SOCKET_ERR_NOTCONN: + case mhd_SOCKET_ERR_TLS: + case mhd_SOCKET_ERR_PIPE: + case mhd_SOCKET_ERR_NOT_CHECKED: + case mhd_SOCKET_ERR_BADF: + case mhd_SOCKET_ERR_INVAL: + case mhd_SOCKET_ERR_OPNOTSUPP: + case mhd_SOCKET_ERR_NOTSOCK: + case mhd_SOCKET_ERR_OTHER: + case mhd_SOCKET_ERR_INTERNAL: + case mhd_SOCKET_ERR_NO_ERROR: + term_code = MHD_REQUEST_TERMINATED_CONNECTION_ERROR; + break; + case mhd_SOCKET_ERR_AGAIN: + case mhd_SOCKET_ERR_INTR: + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + } + break; + case mhd_CONN_CLOSE_DAEMON_SHUTDOWN: + close_hard = true; + term_code = MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN; + break; + + case mhd_CONN_CLOSE_TIMEDOUT: + if (MHD_CONNECTION_INIT == c->state) + { + close_hard = false; + term_code = MHD_REQUEST_TERMINATED_COMPLETED_OK; /* Not used */ + break; + } + close_hard = true; + term_code = MHD_REQUEST_TERMINATED_TIMEOUT_REACHED; + break; + + case mhd_CONN_CLOSE_ERR_REPLY_SENT: + close_hard = false; + term_code = c->rq.too_large ? + MHD_REQUEST_TERMINATED_NO_RESOURCES : + MHD_REQUEST_TERMINATED_HTTP_PROTOCOL_ERROR; + break; + case mhd_CONN_CLOSE_HTTP_COMPLETED: + close_hard = false; + term_code = MHD_REQUEST_TERMINATED_COMPLETED_OK; + break; + + default: + mhd_assert (0 && "Unreachable code"); + MHD_UNREACHABLE_; + term_code = MHD_REQUEST_TERMINATED_COMPLETED_OK; + close_hard = false; + } + + 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) + { + /* Use abortive closing, send RST to remote to indicate a problem */ + (void) mhd_socket_set_hard_close (c->socket_fd); + c->state = MHD_CONNECTION_CLOSED; + c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + } + else + { + if (mhd_socket_shut_wr (c->socket_fd) && (! c->sk_rmt_shut_wr)) + { + (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 */ + c->state = MHD_CONNECTION_CLOSED; + c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + } + } + +#ifdef HAVE_LOG_FUNCTIONALITY + if (NULL != log_msg) + { + mhd_LOG_MSG (c->daemon, sc, log_msg); + } +#else /* ! HAVE_LOG_FUNCTIONALITY */ + (void) log_msg; +#endif /* ! HAVE_LOG_FUNCTIONALITY */ + + mhd_stream_call_dcc_cleanup_if_needed (c); + if (NULL != c->rp.resp_iov.iov) + { + free (c->rp.resp_iov.iov); + c->rp.resp_iov.iov = NULL; + } + +#if 0 // TODO: notification callback + mhd_assert ((MHD_CONNECTION_INIT != c->state) || (! c->rq.app_aware)); + if ( (NULL != d->notify_completed) && + (c->rq.app_aware) ) + d->notify_completed (d->notify_completed_cls, + c, + &c->rq.app_context, + MHD_REQUEST_TERMINATED_COMPLETED_OK); +#else + (void) term_code; +#endif + c->rq.app_aware = false; + + 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 + + mhd_conn_mark_unready (c, c->daemon); + + 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; + + mhd_assert (NULL != c->pool); + c->read_buffer_offset = 0; + c->read_buffer_size = 0; + c->read_buffer = NULL; + c->write_buffer_send_offset = 0; + 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 == c->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 (c->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 @@ -0,0 +1,306 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-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/stream_funcs.h + * @brief The declaration of the stream internal functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STREAM_FUNCS_H +#define MHD_STREAM_FUNCS_H 1 + +#include "mhd_sys_options.h" +#include "sys_base_types.h" +#include "sys_bool_type.h" + + +struct MHD_Connection; /* forward declaration */ + + +/** + * The stage of input data processing. + * Used for out-of-memory (in the pool) handling. + */ +enum MHD_FIXED_ENUM_ MHD_ProcRecvDataStage +{ + MHD_PROC_RECV_INIT, /**< No data HTTP request data have been processed yet */ + MHD_PROC_RECV_METHOD, /**< Processing/receiving the request HTTP method */ + MHD_PROC_RECV_URI, /**< Processing/receiving the request URI */ + MHD_PROC_RECV_HTTPVER, /**< Processing/receiving the request HTTP version string */ + MHD_PROC_RECV_HEADERS, /**< Processing/receiving the request HTTP headers */ + MHD_PROC_RECV_COOKIE, /**< Processing the received request cookie header */ + MHD_PROC_RECV_BODY_NORMAL, /**< Processing/receiving the request non-chunked body */ + MHD_PROC_RECV_BODY_CHUNKED,/**< Processing/receiving the request chunked body */ + MHD_PROC_RECV_FOOTERS /**< Processing/receiving the request footers */ +}; + +/** + * Allocate memory from connection's memory pool. + * If memory pool doesn't have enough free memory but read or write buffer + * have some unused memory, the size of the buffer will be reduced as needed. + * @param connection the connection to use + * @param size the size of allocated memory area + * @return pointer to allocated memory region in the pool or + * NULL if no memory is available + */ +MHD_INTERNAL void * +mhd_stream_alloc_memory (struct MHD_Connection *restrict connection, + size_t size) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Shrink stream read buffer to the zero size of free space in the buffer + * @param c the connection whose read buffer is being manipulated + */ +MHD_INTERNAL void +mhd_stream_shrink_read_buffer (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Allocate the maximum available amount of memory from MemoryPool + * for write buffer. + * @param c the connection whose write buffer is being manipulated + * @return the size of the free space in the write buffer + */ +MHD_INTERNAL size_t +mhd_stream_maximize_write_buffer (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Select the HTTP error status code for "out of receive buffer space" error. + * @param c the connection to process + * @param stage the current stage of request receiving + * @param add_element_size the size of the @a add_element; + * zero if @a add_element is NULL + * @param add_element the optional pointer to the element failed to be processed + * or added, the meaning of the element depends on + * the @a stage. Could be not zero-terminated and can + * contain binary zeros. Can be NULL. + * @return the HTTP error code to use in the error reply + */ +MHD_INTERNAL unsigned int +mhd_stream_get_no_space_err_status_code (struct MHD_Connection *restrict c, + enum MHD_ProcRecvDataStage stage, + size_t add_element_size, + const char *restrict add_element) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_IN_SIZE_ (4,3); + +/** + * Switch connection from recv mode to send mode. + * + * Current request header or body will not be read anymore, + * response must be assigned to connection. + * @param c the connection to prepare for sending. + */ +MHD_INTERNAL void +mhd_stream_switch_from_recv_to_send (struct MHD_Connection *c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Finish request serving. + * The stream will be re-used or closed. + * + * @param c the connection to use. + */ +MHD_INTERNAL void +mhd_stream_finish_req_serving (struct MHD_Connection *restrict c, + bool reuse) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Update last activity mark to the current time.. + * @param c the connection to update + */ +MHD_INTERNAL void +mhd_stream_update_activity_mark (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Update last activity mark to the current time.. + * @param c the connection to update + * @return 'true' if connection has not been timed out, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_stream_check_timedout (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * The reason to close the connection + */ +enum mhd_ConnCloseReason +{ + /* Hard problem while receiving */ + /** + * Client sent data that cannot be interpreted as HTTP data + */ + mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN + , + /** + * No space in the connection pool memory for receiving or processing + * the request + */ + mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REQUEST + , + /** + * The client shut down send before complete request sent + */ + mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY + , + + /* Hard problem while sending */ + + /** + * No space in the connection pool memory for the reply + */ + 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 + */ + mhd_CONN_CLOSE_APP_ERROR + , + /** + * Application requested about of the stream + */ + mhd_CONN_CLOSE_APP_ABORTED + , + + /* Hard problem while receiving or sending */ + /** + * MHD internal error. + * Should never appear. + */ + mhd_CONN_CLOSE_INT_ERROR + , + /** + * The TCP or TLS connection is broken or aborted due to error on socket + * or TLS + */ + mhd_CONN_CLOSE_SOCKET_ERR + , + /** + * The daemon is being shut down, all connection must be closed + */ + mhd_CONN_CLOSE_DAEMON_SHUTDOWN + , + + /* Could be hard or soft error depending on connection state */ + /** + * Timeout detected when receiving request + */ + mhd_CONN_CLOSE_TIMEDOUT + , + + /* Soft problem */ + /** + * The connection must be closed after error response as the client + * violates HTTP specification + */ + mhd_CONN_CLOSE_ERR_REPLY_SENT + , + + /* Graceful closing */ + /** + * Close connection after graceful completion of HTTP communication + */ + mhd_CONN_CLOSE_HTTP_COMPLETED + +}; + + +/** + * 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, + enum mhd_ConnCloseReason reason, + const char *log_msg) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); + +/** + * Abort the stream and log message + */ +#ifdef HAVE_LOG_FUNCTIONALITY +# define mhd_STREAM_ABORT(c,r,m) (mhd_conn_pre_close ((c),(r),(m))) +#else /* ! HAVE_LOG_FUNCTIONALITY */ +# define mhd_STREAM_ABORT(c,r,m) (mhd_conn_pre_close ((c),(r),NULL)) +#endif /* ! HAVE_LOG_FUNCTIONALITY */ + +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "aborted by application" + * @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) + +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "socket error" + * @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) + +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "request finished" + * @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) + +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "timed out". + * @param c the connection for pre-closing + */ +#define mhd_conn_pre_close_timedout(c) \ + mhd_conn_pre_close ((c), mhd_CONN_CLOSE_TIMEDOUT, NULL) + +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "daemon shutdown". + * @param c the connection for pre-closing + */ +#define mhd_conn_pre_close_d_shutdown(c) \ + mhd_conn_pre_close ((c), mhd_CONN_CLOSE_DAEMON_SHUTDOWN, 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 @@ -0,0 +1,1314 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + 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/stream_process_reply.h + * @brief The implementation of internal functions for forming and sending + * replies for requests + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other + * contributors. + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include <string.h> +#ifdef HAVE_TIME_H +# include <time.h> +#endif + +#include "mhd_daemon.h" +#include "mhd_response.h" +#include "mhd_reply.h" +#include "mhd_connection.h" + +#include "daemon_logger.h" +#include "mhd_assert.h" + +#include "mhd_str.h" +#include "http_status_str.h" +#include "stream_process_reply.h" +#include "stream_funcs.h" +#include "request_get_value.h" + +#include "mhd_public_api.h" + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_call_dcc_cleanup_if_needed (struct MHD_Connection *restrict c) +{ + if (mhd_DCC_ACTION_CONTINUE != c->rp.app_act.act) + return; + if (NULL == c->rp.app_act.data.cntnue.iov_data) + return; + + mhd_assert (mhd_RESPONSE_CONTENT_DATA_CALLBACK == \ + c->rp.response->cntn_dtype); + + if (NULL != c->rp.app_act.data.cntnue.iov_data->iov_fcb) + { + c->rp.app_act.data.cntnue.iov_data->iov_fcb ( + c->rp.app_act.data.cntnue.iov_data->iov_fcb_cls); + } + + c->rp.app_act.data.cntnue.iov_data = NULL; +} + + +/** + * This enum type describes requirements for reply body and reply bode-specific + * headers (namely Content-Length, Transfer-Encoding). + */ +enum replyBodyUse +{ + /** + * No reply body allowed. + * Reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked' are + * not allowed as well. + */ + RP_BODY_NONE = 0, + + /** + * Do not send reply body. + * Reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked' are + * allowed, but optional. + */ + RP_BODY_HEADERS_ONLY = 1, + + /** + * Send reply body and + * reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked'. + * Reply body headers are required. + */ + RP_BODY_SEND = 2 +}; + + +/** + * Is it allowed to reuse the connection? + * The TCP stream can be reused for the next requests if the connection + * is HTTP 1.1 and the "Connection" header either does not exist or + * is not set to "close", or if the connection is HTTP 1.0 and the + * "Connection" header is explicitly set to "keep-alive". + * If no HTTP version is specified (or if it is not 1.0 or 1.1), the connection + * is definitively closed. If the "Connection" header is not exactly "close" + * or "keep-alive", connection is reused if is it HTTP/1.1. + * If response has HTTP/1.0 flag or has "Connection: close" header + * then connection must be closed. + * If full request has not been read then connection must be closed + * as well as more client data may be sent. + * + * @param c the connection to check for re-use + * @return mhd_CONN_KEEPALIVE_POSSIBLE if (based on the request and + * the response) a connection could be reused, + * MHD_CONN_MUST_CLOSE if connection must be closed after sending + * complete reply, + * mhd_CONN_MUST_UPGRADE if connection must be upgraded. + */ +static MHD_FN_PAR_NONNULL_ALL_ enum mhd_ConnReuse +get_conn_reuse (struct MHD_Connection *c) +{ + const struct MHD_Response *const restrict rp = c->rp.response; + + mhd_assert (NULL != rp); + if (mhd_CONN_MUST_CLOSE == c->conn_reuse) + return mhd_CONN_MUST_CLOSE; + + mhd_assert ( (! c->stop_with_error) || (c->discard_request)); + if ((c->sk_rmt_shut_wr) || (c->discard_request)) + return mhd_CONN_MUST_CLOSE; + + if (rp->cfg.close_forced) + return mhd_CONN_MUST_CLOSE; + + mhd_assert ((MHD_SIZE_UNKNOWN != rp->cntn_size) || \ + (! rp->cfg.mode_1_0)); + + if (! MHD_HTTP_VERSION_IS_SUPPORTED (c->rq.http_ver)) + return mhd_CONN_MUST_CLOSE; + + if (rp->cfg.mode_1_0 && + ! mhd_stream_has_header_token_st (c, + MHD_HTTP_HEADER_CONNECTION, + "keep-alive")) + return mhd_CONN_MUST_CLOSE; + +#if 0 // def UPGRADE_SUPPORT // TODO: Implement upgrade support + /* TODO: Move below the next check when MHD stops closing connections + * when response is queued in first callback */ + if (NULL != r->upgrade_handler) + { + /* No "close" token is enforced by 'add_response_header_connection()' */ + mhd_assert (0 == (r->flags_auto & MHD_RAF_HAS_CONNECTION_CLOSE)); + /* Valid HTTP version is enforced by 'MHD_queue_response()' */ + mhd_assert (MHD_IS_HTTP_VER_SUPPORTED (c->rq.http_ver)); + mhd_assert (! c->stop_with_error); + return mhd_CONN_MUST_UPGRADE; + } +#endif /* UPGRADE_SUPPORT */ + + return mhd_CONN_KEEPALIVE_POSSIBLE; +} + + +/** + * Check whether reply body must be used. + * + * If reply body is needed, it could be zero-sized. + * + * @param c the connection to check + * @param rcode the response code + * @return enum value indicating whether response body can be used and + * whether response body length headers are allowed or required. + * @sa is_reply_body_header_needed() + */ +static enum replyBodyUse +is_reply_body_needed (struct MHD_Connection *restrict c, + uint_fast16_t rcode) +{ + mhd_assert (100 <= rcode); + mhd_assert (999 >= rcode); + + if (199 >= rcode) + return RP_BODY_NONE; + + if (MHD_HTTP_STATUS_NO_CONTENT == rcode) + return RP_BODY_NONE; + +#if 0 + /* This check is not needed as upgrade handler is used only with code 101 */ +#ifdef UPGRADE_SUPPORT + if (NULL != rp.response->upgrade_handler) + return RP_BODY_NONE; +#endif /* UPGRADE_SUPPORT */ +#endif + +#if 0 + /* CONNECT is not supported by MHD */ + /* Successful responses for connect requests are filtered by + * MHD_queue_response() */ + if ( (mhd_HTTP_METHOD_CONNECT == c->rq.http_mthd) && + (2 == rcode / 100) ) + return false; /* Actually pass-through CONNECT is not supported by MHD */ +#endif + + /* Reply body headers could be used. + * Check whether reply body itself must be used. */ + + if (mhd_HTTP_METHOD_HEAD == c->rq.http_mthd) + return RP_BODY_HEADERS_ONLY; + + if (MHD_HTTP_STATUS_NOT_MODIFIED == rcode) + return RP_BODY_HEADERS_ONLY; + + /* Reply body must be sent. + * The body may have zero length, but body size must be indicated by + * headers ('Content-Length:' or 'Transfer-Encoding: chunked'). */ + return RP_BODY_SEND; +} + + +/** + * Setup connection reply properties. + * + * Reply properties include presence of reply body, transfer-encoding + * type and other. + * + * @param connection to connection to process + */ +static MHD_FN_PAR_NONNULL_ALL_ void +setup_reply_properties (struct MHD_Connection *restrict c) +{ + struct MHD_Response *const restrict r = c->rp.response; /**< a short alias */ + enum replyBodyUse use_rp_body; + bool use_chunked; + bool end_by_closing; + + mhd_assert (NULL != r); + + /* ** Adjust reply properties ** */ + + c->conn_reuse = get_conn_reuse (c); + use_rp_body = is_reply_body_needed (c, r->sc); + c->rp.props.send_reply_body = (use_rp_body > RP_BODY_HEADERS_ONLY); + c->rp.props.use_reply_body_headers = (use_rp_body >= RP_BODY_HEADERS_ONLY); + +#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support + mhd_assert ( (NULL == r->upgrade_handler) || + (RP_BODY_NONE == use_rp_body) ); +#endif /* UPGRADE_SUPPORT */ + + use_chunked = false; + end_by_closing = false; + if (c->rp.props.use_reply_body_headers) + { + if (r->cfg.chunked) + { + mhd_assert (! r->cfg.mode_1_0); + use_chunked = (MHD_HTTP_VERSION_1_1 == c->rq.http_ver); + } + if ((MHD_SIZE_UNKNOWN == r->cntn_size) && + (! use_chunked) && + (c->rp.props.send_reply_body)) + { + /* End of the stream is indicated by closure */ + end_by_closing = true; + } + } + + if (end_by_closing) + { + mhd_assert (mhd_CONN_MUST_UPGRADE != c->conn_reuse); + /* End of the stream is indicated by closure */ + c->conn_reuse = mhd_CONN_MUST_CLOSE; + } + + c->rp.props.chunked = use_chunked; + c->rp.props.end_by_closing = end_by_closing; + + if ((! c->rp.props.send_reply_body) || (0 == r->cntn_size)) + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE; + else if (c->rp.props.chunked) + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF; + else + { + switch (r->cntn_dtype) + { + case mhd_RESPONSE_CONTENT_DATA_BUFFER: + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_RESP_BUF; + break; + case mhd_RESPONSE_CONTENT_DATA_IOVEC: + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_IOV; + break; + case mhd_RESPONSE_CONTENT_DATA_FILE: +#if 0 // TODO: TLS support + if (use_tls) + { + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF; + break; + } +#endif +#ifdef MHD_USE_SENDFILE + if (r->cntn.file.use_sf) + { + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_FILE; + break; + } +#endif + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF; + break; + case mhd_RESPONSE_CONTENT_DATA_CALLBACK: + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF; + break; + case mhd_RESPONSE_CONTENT_DATA_INVALID: + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE; + break; + } + } + +#ifdef _DEBUG + c->rp.props.set = true; +#endif /* _DEBUG */ +} + + +/** + * Check whether queued response is suitable for @a connection. + * @param connection to connection to check + */ +static void +check_connection_reply (struct MHD_Connection *restrict c) +{ + struct MHD_Response *const restrict r = c->rp.response; /**< a short alias */ + + mhd_assert (c->rp.props.set); + + if ( (! c->rp.props.use_reply_body_headers) && + (0 != r->cntn_size) ) + { + mhd_LOG_PRINT (c->daemon, MHD_SC_REPLY_NOT_EMPTY_RESPONSE, + mhd_LOG_FMT ("This reply with response code %u " \ + "cannot use reply content. Non-empty " \ + "response content is ignored and not used."), + (unsigned) (c->rp.response->sc)); + } + if ( (! c->rp.props.use_reply_body_headers) && + (r->cfg.cnt_len_by_app) ) + { + mhd_LOG_PRINT (c->daemon, MHD_SC_REPLY_CONTENT_LENGTH_NOT_ALLOWED, + mhd_LOG_FMT ("This reply with response code %u " \ + "cannot use reply content. Application " \ + "defined \"Content-Length\" header " \ + "violates HTTP specification."), + (unsigned) (c->rp.response->sc)); + } +} + + +/** + * Produce time stamp. + * + * Result is NOT null-terminated. + * Result is always 29 bytes long. + * + * @param[out] date where to write the time stamp, with + * at least 29 bytes of savailable space. + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (1) bool +get_date_str (char *date) +{ + static const char *const days[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char *const mons[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + static const size_t buf_len = 29; + struct tm now; + time_t t; + const char *src; +#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ + ! defined(HAVE_GMTIME_R) + struct tm *pNow; +#endif + + if ((time_t) -1 == time (&t)) + return false; +#if defined(HAVE_GMTIME_R) + if (NULL == gmtime_r (&t, + &now)) + return false; +#elif defined(HAVE_C11_GMTIME_S) + if (NULL == gmtime_s (&t, + &now)) + return false; +#elif defined(HAVE_W32_GMTIME_S) + if (0 != gmtime_s (&now, + &t)) + return false; +#else + pNow = gmtime (&t); + if (NULL == pNow) + return false; + now = *pNow; +#endif + + /* Day of the week */ + src = days[now.tm_wday % 7]; + date[0] = src[0]; + date[1] = src[1]; + date[2] = src[2]; + date[3] = ','; + date[4] = ' '; + /* Day of the month */ + if (2 != mhd_uint8_to_str_pad ((uint8_t) now.tm_mday, 2, + date + 5, buf_len - 5)) + return false; + date[7] = ' '; + /* Month */ + src = mons[now.tm_mon % 12]; + date[8] = src[0]; + date[9] = src[1]; + date[10] = src[2]; + date[11] = ' '; + /* Year */ + if (4 != mhd_uint16_to_str ((uint_least16_t) (1900 + now.tm_year), date + 12, + buf_len - 12)) + return false; + date[16] = ' '; + /* Time */ + mhd_uint8_to_str_pad ((uint8_t) now.tm_hour, 2, date + 17, buf_len - 17); + date[19] = ':'; + mhd_uint8_to_str_pad ((uint8_t) now.tm_min, 2, date + 20, buf_len - 20); + date[22] = ':'; + mhd_uint8_to_str_pad ((uint8_t) now.tm_sec, 2, date + 23, buf_len - 23); + date[25] = ' '; + date[26] = 'G'; + date[27] = 'M'; + date[28] = 'T'; + + return true; +} + + +/** + * Produce HTTP DATE header. + * Result is always 37 bytes long (plus one terminating null). + * + * @param[out] header where to write the header, with + * at least 38 bytes available space. + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (1) bool +get_date_header (char *header) +{ + header[0] = 'D'; + header[1] = 'a'; + header[2] = 't'; + header[3] = 'e'; + header[4] = ':'; + header[5] = ' '; + if (! get_date_str (header + 6)) + { + header[0] = 0; + return false; + } + header[35] = '\r'; + header[36] = '\n'; + header[37] = 0; + return true; +} + + +/** + * Append data to the buffer if enough space is available, + * update position. + * @param[out] buf the buffer to append data to + * @param[in,out] ppos the pointer to position in the @a buffer + * @param buf_size the size of the @a buffer + * @param append the data to append + * @param append_size the size of the @a append + * @return true if data has been added and position has been updated, + * false if not enough space is available + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +buffer_append (char *buf, + size_t *ppos, + size_t buf_size, + const char *append, + size_t append_size) +{ + mhd_assert (NULL != buf); /* Mute static analyzer */ + if (buf_size < *ppos + append_size) + return false; + memcpy (buf + *ppos, append, append_size); + *ppos += append_size; + return true; +} + + +/** + * Add user-defined headers from response object to + * the text buffer. + * + * @param buf the buffer to add headers to + * @param ppos the pointer to the position in the @a buf + * @param buf_size the size of the @a buf + * @param response the response + * @param filter_content_len skip "Content-Length" header if any + * @param add_close add "close" token to the + * "Connection:" header (if any), ignored if no "Connection:" + * header was added by user or if "close" token is already + * present in "Connection:" header + * @param add_keep_alive add "Keep-Alive" token to the + * "Connection:" header (if any) + * @return true if succeed, + * false if buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +add_user_headers (char *restrict buf, + size_t *restrict ppos, + size_t buf_size, + struct MHD_Response *restrict r, + bool filter_content_len, + bool add_close, + bool add_keep_alive) +{ + struct mhd_ResponseHeader *hdr; /**< Iterates through User-specified headers */ + size_t el_size; /**< the size of current element to be added to the @a buf */ + + mhd_assert (! add_close || ! add_keep_alive); + mhd_assert (! add_keep_alive || ! add_close); + + if (r->cfg.has_hdr_conn) + { + add_close = false; /* No such header */ + add_keep_alive = false; /* No such header */ + } + + for (hdr = mhd_DLINKEDL_GET_FIRST (r, headers); + NULL != hdr; + hdr = mhd_DLINKEDL_GET_NEXT (hdr, headers)) + { + size_t initial_pos = *ppos; + + if (filter_content_len) + { /* Need to filter-out "Content-Length" */ + if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_CONTENT_LENGTH, \ + hdr->name.cstr, + hdr->name.len)) + { + /* Reset filter flag */ + filter_content_len = false; + continue; /* Skip "Content-Length" header */ + } + } + + /* Add user header */ + el_size = hdr->name.len + 2 + hdr->value.len + 2; + if (buf_size < *ppos + el_size) + return false; + memcpy (buf + *ppos, hdr->name.cstr, hdr->name.len); + (*ppos) += hdr->name.len; + buf[(*ppos)++] = ':'; + buf[(*ppos)++] = ' '; + if (add_close || add_keep_alive) + { + /* "Connection:" header must be always the first one */ + mhd_assert (mhd_str_equal_caseless_n (hdr->name.cstr, \ + MHD_HTTP_HEADER_CONNECTION, \ + hdr->name.len)); + + if (add_close) + { + el_size += mhd_SSTR_LEN ("close, "); + if (buf_size < initial_pos + el_size) + return false; + memcpy (buf + *ppos, "close, ", + mhd_SSTR_LEN ("close, ")); + *ppos += mhd_SSTR_LEN ("close, "); + } + else + { + el_size += mhd_SSTR_LEN ("Keep-Alive, "); + if (buf_size < initial_pos + el_size) + return false; + memcpy (buf + *ppos, "Keep-Alive, ", + mhd_SSTR_LEN ("Keep-Alive, ")); + *ppos += mhd_SSTR_LEN ("Keep-Alive, "); + } + add_close = false; + add_keep_alive = false; + } + if (0 != hdr->value.len) + memcpy (buf + *ppos, hdr->value.cstr, hdr->value.len); + *ppos += hdr->value.len; + buf[(*ppos)++] = '\r'; + buf[(*ppos)++] = '\n'; + mhd_assert (initial_pos + el_size == (*ppos)); + } + return true; +} + + +/** + * Append static string to the buffer if enough space is available, + * update position. + * @param[out] buf the buffer to append data to + * @param[in,out] ppos the pointer to position in the @a buffer + * @param buf_size the size of the @a buffer + * @param str the static string to append + * @return true if data has been added and position has been updated, + * false if not enough space is available + */ +#define buffer_append_s(buf,ppos,buf_size,str) \ + buffer_append (buf,ppos,buf_size,str, mhd_SSTR_LEN (str)) + +/** + * Allocate the connection's write buffer and fill it with all of the + * headers from the response. + * Inner version of the function. + * + * @param c the connection to process + * @return 'true' if state has been update, + * 'false' if connection is going to be aborted + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +build_header_response_inn (struct MHD_Connection *restrict c) +{ + struct MHD_Response *const restrict r = c->rp.response; + char *restrict buf; /**< the output buffer */ + size_t pos; /**< append offset in the @a buf */ + size_t buf_size; /**< the size of the @a buf */ + size_t el_size; /**< the size of current element to be added to the @a buf */ + uint_fast16_t rcode; /**< the response code */ + bool use_conn_close; /**< Use "Connection: close" header */ + bool use_conn_k_alive; /**< Use "Connection: Keep-Alive" header */ + + mhd_assert (NULL != r); + + /* ** Adjust response properties ** */ + setup_reply_properties (c); + + mhd_assert (c->rp.props.set); + mhd_assert ((mhd_CONN_MUST_CLOSE == c->conn_reuse) || \ + (mhd_CONN_KEEPALIVE_POSSIBLE == c->conn_reuse) || \ + (mhd_CONN_MUST_UPGRADE == c->conn_reuse)); +#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support + mhd_assert ((NULL == r->upgrade_handler) || \ + (mhd_CONN_MUST_UPGRADE == c->keepalive)); +#else /* ! UPGRADE_SUPPORT */ + mhd_assert (mhd_CONN_MUST_UPGRADE != c->conn_reuse); +#endif /* ! UPGRADE_SUPPORT */ + mhd_assert ((! c->rp.props.chunked) || c->rp.props.use_reply_body_headers); + mhd_assert ((! c->rp.props.send_reply_body) || \ + c->rp.props.use_reply_body_headers); + mhd_assert ((! c->rp.props.end_by_closing) || \ + (mhd_CONN_MUST_CLOSE == c->conn_reuse)); +#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support + mhd_assert (NULL == r->upgrade_handler || \ + ! c->rp.props.use_reply_body_headers); +#endif /* UPGRADE_SUPPORT */ + + check_connection_reply (c); + + rcode = (uint_fast16_t) c->rp.response->sc; + if (mhd_CONN_MUST_CLOSE == c->conn_reuse) + { + /* The closure of connection must be always indicated by header + * to avoid hung connections */ + use_conn_close = true; + use_conn_k_alive = false; + } + else if (mhd_CONN_KEEPALIVE_POSSIBLE == c->conn_reuse) + { + mhd_assert (! r->cfg.mode_1_0); + use_conn_close = false; + /* Add "Connection: keep-alive" if request is HTTP/1.0 or + * if reply is HTTP/1.0 + * For HTTP/1.1 add header only if explicitly requested by app + * (by response flag), as "Keep-Alive" is default for HTTP/1.1. */ + if (r->cfg.mode_1_0 || + (MHD_HTTP_VERSION_1_0 == c->rq.http_ver)) + use_conn_k_alive = true; + else + use_conn_k_alive = false; + } + else + { + use_conn_close = false; + use_conn_k_alive = false; + } + + /* ** Actually build the response header ** */ + + /* Get all space available */ + mhd_stream_maximize_write_buffer (c); + buf = c->write_buffer; + pos = c->write_buffer_append_offset; + buf_size = c->write_buffer_size; + if (0 == buf_size) + return false; + mhd_assert (NULL != buf); + + // TODO: use pre-calculated header size + /* * The status line * */ + + /* The HTTP version */ + if (! c->rp.responseIcy) + { /* HTTP reply */ + if (! r->cfg.mode_1_0) + { /* HTTP/1.1 reply */ + /* Use HTTP/1.1 responses for HTTP/1.0 clients. + * See https://datatracker.ietf.org/doc/html/rfc7230#section-2.6 */ + if (! buffer_append_s (buf, &pos, buf_size, MHD_HTTP_VERSION_1_1_STR)) + return false; + } + else + { /* HTTP/1.0 reply */ + if (! buffer_append_s (buf, &pos, buf_size, MHD_HTTP_VERSION_1_0_STR)) + return false; + } + } + else + { /* ICY reply */ + if (! buffer_append_s (buf, &pos, buf_size, "ICY")) + return false; + } + + /* The response code */ + if (buf_size < pos + 5) /* space + code + space */ + return false; + buf[pos++] = ' '; + pos += mhd_uint16_to_str ((uint16_t) rcode, buf + pos, + buf_size - pos); + buf[pos++] = ' '; + + /* The reason phrase */ + if (1) + { + const struct MHD_String *stat_str; + stat_str = mhd_HTTP_status_code_to_string_int (rcode); + mhd_assert (0 != stat_str->len); + if (! buffer_append (buf, &pos, buf_size, + stat_str->cstr, + stat_str->len)) + return false; + } + /* The linefeed */ + if (buf_size < pos + 2) + return false; + buf[pos++] = '\r'; + buf[pos++] = '\n'; + + /* * 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 */ + if ( (! r->cfg.has_hdr_date) && + (! c->daemon->req_cfg.suppress_date) ) + { + /* Additional byte for unused zero-termination */ + if (buf_size < pos + 38) + return false; + if (get_date_header (buf + pos)) + pos += 37; + } + /* The "Connection:" header */ + mhd_assert (! use_conn_close || ! use_conn_k_alive); + mhd_assert (! use_conn_k_alive || ! use_conn_close); + if (! r->cfg.has_hdr_conn) + { + if (use_conn_close) + { + if (! buffer_append_s (buf, &pos, buf_size, + MHD_HTTP_HEADER_CONNECTION ": close\r\n")) + return false; + } + else if (use_conn_k_alive) + { + if (! buffer_append_s (buf, &pos, buf_size, + MHD_HTTP_HEADER_CONNECTION ": Keep-Alive\r\n")) + return false; + } + } + + /* User-defined headers */ + + if (! add_user_headers (buf, &pos, buf_size, r, + ! c->rp.props.use_reply_body_headers, + use_conn_close, + use_conn_k_alive)) + return false; + + /* Other automatic headers */ + + if (c->rp.props.use_reply_body_headers) + { + /* Body-specific headers */ + + if (c->rp.props.chunked) + { /* Chunked encoding is used */ + mhd_assert (! c->rp.props.end_by_closing); + if (! buffer_append_s (buf, &pos, buf_size, + MHD_HTTP_HEADER_TRANSFER_ENCODING ": " \ + "chunked\r\n")) + return false; + } + else /* Chunked encoding is not used */ + { + if ((MHD_SIZE_UNKNOWN != r->cntn_size) && + (! c->rp.props.end_by_closing) && + (! 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 app-defined "Content-Length" header */ + if (! buffer_append_s (buf, &pos, buf_size, + MHD_HTTP_HEADER_CONTENT_LENGTH ": ")) + return false; + el_size = mhd_uint64_to_str (r->cntn_size, + buf + pos, + buf_size - pos); + if (0 == el_size) + return false; + pos += el_size; + + if (buf_size < pos + 2) + return false; + buf[pos++] = '\r'; + buf[pos++] = '\n'; + } + } + else + { + mhd_assert ((! c->rp.props.send_reply_body) || \ + (mhd_CONN_MUST_CLOSE == c->conn_reuse)); + (void) 0; + } + } + } + + /* * Header termination * */ + if (buf_size < pos + 2) + return false; + buf[pos++] = '\r'; + buf[pos++] = '\n'; + + c->write_buffer_append_offset = pos; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_build_header_response (struct MHD_Connection *restrict c) +{ + if (! build_header_response_inn (c)) + { + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY, + "No memory in the pool for the reply headers."); + return false; + } + c->state = MHD_CONNECTION_HEADERS_SENDING; + return true; +} + + +/** + * Pre-process DCC action provided by application. + * 'abort' and 'suspend' actions are fully processed, + * 'continue' and 'finish' actions needs to be processed by the caller. + * @param c the stream to use + * @param act the action provided by application + * @return 'true' if action if 'continue' or 'finish' and need to be + * processed, + * 'false' if action is 'suspend' or 'abort' and is already processed. + */ +static MHD_FN_PAR_NONNULL_ (1) bool +preprocess_dcc_action (struct MHD_Connection *restrict c, + const struct MHD_DynamicContentCreatorAction *act) +{ + /** + * The action created for the current request + */ + struct MHD_DynamicContentCreatorAction *const a = + &(c->rp.app_act); + + if (NULL != act) + { + if ((a != act) || + ! mhd_DCC_ACTION_IS_VALID (c->rp.app_act.act) || + ((MHD_SIZE_UNKNOWN != c->rp.response->cntn_size) && + (mhd_DCC_ACTION_FINISH == c->rp.app_act.act))) + { + mhd_LOG_MSG (c->daemon, MHD_SC_ACTION_INVALID, \ + "Provided Dynamic Content Creator action is not " \ + "a correct action generated for the current request."); + act = NULL; + } + } + if (NULL == act) + a->act = mhd_DCC_ACTION_ABORT; + + switch (a->act) + { + case mhd_DCC_ACTION_CONTINUE: + return true; + case mhd_DCC_ACTION_FINISH: + mhd_assert (MHD_SIZE_UNKNOWN == c->rp.response->cntn_size); + return true; + case mhd_DCC_ACTION_SUSPEND: + mhd_assert (0 && "Not implemented yet"); + // TODO: Implement suspend; + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_INT_ERROR, + "Suspending connection is not implemented yet"); + return false; + case mhd_DCC_ACTION_ABORT: + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_APP_ABORTED, + "Dynamic Content Creator requested abort " \ + "of the request"); + return false; + case mhd_DCC_ACTION_NO_ACTION: + default: + break; + } + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_INT_ERROR, + "Impossible code path"); + return false; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_prep_unchunked_body (struct MHD_Connection *restrict c) +{ + struct MHD_Response *const restrict r = c->rp.response; + + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.rsp_cntn_read_pos != r->cntn_size); + + mhd_stream_call_dcc_cleanup_if_needed (c); + + if (0 == r->cntn_size) + { /* 0-byte response is always ready */ + c->state = MHD_CONNECTION_FULL_REPLY_SENT; + return true; + } + + mhd_assert (mhd_REPLY_CNTN_LOC_NOWHERE != c->rp.cntn_loc); + if (mhd_REPLY_CNTN_LOC_RESP_BUF == c->rp.cntn_loc) + { + (void) 0; /* Nothing to do, buffers are ready */ + } + else if (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc) + { + if (mhd_RESPONSE_CONTENT_DATA_CALLBACK == r->cntn_dtype) + { + const struct MHD_DynamicContentCreatorAction *act; + const size_t size_to_fill = + c->write_buffer_size - c->write_buffer_append_offset; + size_t filled; + + mhd_assert (c->write_buffer_append_offset < c->write_buffer_size); + mhd_assert (NULL == c->rp.app_act_ctx.connection); + + c->rp.app_act_ctx.connection = c; + c->rp.app_act.act = mhd_DCC_ACTION_NO_ACTION; + + act = + r->cntn.dyn.cb (r->cntn.dyn.cls, + &(c->rp.app_act_ctx), + c->rp.rsp_cntn_read_pos, + (void *) + (c->write_buffer + c->write_buffer_append_offset), + size_to_fill); + c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */ + if (! preprocess_dcc_action (c, act)) + return false; + if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act) + { + mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size); + mhd_assert (c->rp.props.end_by_closing); + + c->state = MHD_CONNECTION_FULL_REPLY_SENT; + + return true; + } + mhd_assert (mhd_DCC_ACTION_CONTINUE == c->rp.app_act.act); + // TODO: implement iov sending + + filled = c->rp.app_act.data.cntnue.buf_data_size; + if (size_to_fill < filled) + { + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_APP_ERROR, + "Closing connection (application returned more data " + "than requested)."); + return false; + } + c->rp.rsp_cntn_read_pos += filled; + c->write_buffer_append_offset += filled; + } + else if (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype) + { + // TODO: implement fallback + mhd_assert (0 && "Not implemented yet"); + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE; + c->rp.rsp_cntn_read_pos = r->cntn_size; + } + else + { + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE; + c->rp.rsp_cntn_read_pos = r->cntn_size; + } + + mhd_assert (0 && "Not implemented yet"); + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE; + c->rp.rsp_cntn_read_pos = r->cntn_size; + } + else if (mhd_REPLY_CNTN_LOC_IOV == c->rp.cntn_loc) + { + size_t copy_size; + + mhd_assert (NULL == c->rp.resp_iov.iov); + mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype); + + copy_size = r->cntn.iovec.cnt * sizeof(mhd_iovec); + c->rp.resp_iov.iov = mhd_stream_alloc_memory (c, + copy_size); + if (NULL == c->rp.resp_iov.iov) + { + /* not enough memory */ + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY, + "No memory in the pool for the response data."); + return false; + } + memcpy (c->rp.resp_iov.iov, + &(r->cntn.iovec.iov), + copy_size); + c->rp.resp_iov.cnt = r->cntn.iovec.cnt; + c->rp.resp_iov.sent = 0; + } +#if defined(MHD_USE_SENDFILE) + else if (mhd_REPLY_CNTN_LOC_FILE == c->rp.cntn_loc) + { + (void) 0; /* Nothing to do, file should be read directly */ + } +#endif /* MHD_USE_SENDFILE */ + else + { + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE; + c->rp.rsp_cntn_read_pos = r->cntn_size; + } + + c->state = MHD_CONNECTION_UNCHUNKED_BODY_READY; + return false; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_prep_chunked_body (struct MHD_Connection *restrict c) +{ + size_t filled; + struct MHD_Response *const restrict r = c->rp.response; + static const size_t max_chunk = 0xFFFFFF; + char chunk_hdr[6]; /* 6: max strlen of "FFFFFF" */ + /* "FFFFFF" + "\r\n" */ + static const size_t max_chunk_hdr_len = sizeof(chunk_hdr) + 2; + /* "FFFFFF" + "\r\n" + "\r\n" (chunk termination) */ + static const size_t max_chunk_overhead = sizeof(chunk_hdr) + 2 + 2; + size_t chunk_hdr_len; + uint64_t left_to_send; + size_t size_to_fill; + + mhd_assert (0 == c->write_buffer_append_offset); + mhd_assert (0 == c->write_buffer_send_offset); + + mhd_stream_call_dcc_cleanup_if_needed (c); + + /* The buffer must be reasonably large enough */ + if (32 > c->write_buffer_size) + { + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY, + "No memory in the pool for the reply chunked content."); + } + mhd_assert (max_chunk_overhead < \ + (c->write_buffer_size)); + + if (MHD_SIZE_UNKNOWN == r->cntn_size) + left_to_send = MHD_SIZE_UNKNOWN; + else + left_to_send = r->cntn_size - c->rp.rsp_cntn_read_pos; + + mhd_assert (0 != left_to_send); + if (0 != left_to_send) + { + size_to_fill = + c->write_buffer_size - max_chunk_overhead; + /* Limit size for the callback to the max usable size */ + if (max_chunk < size_to_fill) + size_to_fill = max_chunk; + if (left_to_send < size_to_fill) + size_to_fill = (size_t) left_to_send; + } + else + size_to_fill = 0; + + if ((0 == left_to_send) && + (mhd_RESPONSE_CONTENT_DATA_CALLBACK != r->cntn_dtype)) + { + c->state = MHD_CONNECTION_CHUNKED_BODY_SENT; + return true; + } + else if (mhd_RESPONSE_CONTENT_DATA_BUFFER == r->cntn_dtype) + { + mhd_assert (size_to_fill <= \ + r->cntn_size - (size_t) c->rp.rsp_cntn_read_pos); + memcpy (c->write_buffer + max_chunk_hdr_len, + r->cntn.buf + (size_t) c->rp.rsp_cntn_read_pos, + size_to_fill); + filled = size_to_fill; + } + else if (mhd_RESPONSE_CONTENT_DATA_CALLBACK == r->cntn_dtype) + { + const struct MHD_DynamicContentCreatorAction *act; + + mhd_assert (NULL == c->rp.app_act_ctx.connection); + + c->rp.app_act_ctx.connection = c; + c->rp.app_act.act = mhd_DCC_ACTION_NO_ACTION; + + act = + r->cntn.dyn.cb (r->cntn.dyn.cls, + &(c->rp.app_act_ctx), + c->rp.rsp_cntn_read_pos, + (void *) + (c->write_buffer + max_chunk_hdr_len), + size_to_fill); + c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */ + if (! preprocess_dcc_action (c, act)) + return false; + if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act) + { + mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size); + c->state = MHD_CONNECTION_CHUNKED_BODY_SENT; + + return true; + } + mhd_assert (mhd_DCC_ACTION_CONTINUE == c->rp.app_act.act); + // TODO: implement iov sending + + filled = c->rp.app_act.data.cntnue.buf_data_size; + if (size_to_fill < filled) + { + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_APP_ERROR, + "Closing connection (application returned more data " + "than requested)."); + return false; + } + c->rp.rsp_cntn_read_pos += filled; + c->write_buffer_append_offset += filled; + } + else + { + mhd_assert (0 && "Not implemented yet"); + filled = 0; + } + + chunk_hdr_len = mhd_uint32_to_strx ((uint_fast32_t) filled, + chunk_hdr, + sizeof(chunk_hdr)); + mhd_assert (chunk_hdr_len != 0); + mhd_assert (chunk_hdr_len < sizeof(chunk_hdr)); + c->write_buffer_send_offset = max_chunk_hdr_len - (chunk_hdr_len + 2); + memcpy (c->write_buffer + c->write_buffer_send_offset, + chunk_hdr, + chunk_hdr_len); + c->write_buffer[max_chunk_hdr_len - 2] = '\r'; + c->write_buffer[max_chunk_hdr_len - 1] = '\n'; + c->write_buffer[max_chunk_hdr_len + filled] = '\r'; + c->write_buffer[max_chunk_hdr_len + filled + 1] = '\n'; + c->write_buffer_append_offset = max_chunk_hdr_len + filled + 2; + if (0 != filled) + c->rp.rsp_cntn_read_pos += filled; + else + c->rp.rsp_cntn_read_pos = r->cntn_size; + + c->state = MHD_CONNECTION_CHUNKED_BODY_READY; + + return false; +} + + +/** + * Allocate the connection's write buffer (if necessary) and fill it + * with response footers. + * Inner version. + * + * @param c the connection + * @return 'true' if footers formed successfully, + * 'false' if not enough buffer + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +prep_chunked_footer_inn (struct MHD_Connection *restrict c) +{ + char *buf; /**< the buffer to write footers to */ + size_t buf_size; /**< the size of the @a buf */ + size_t used_size; /**< the used size of the @a buf */ + // struct MHD_HTTP_Res_Header *pos; + + mhd_assert (c->rp.props.chunked); + mhd_assert (MHD_CONNECTION_CHUNKED_BODY_SENT == c->state); + mhd_assert (NULL != c->rp.response); + + buf_size = mhd_stream_maximize_write_buffer (c); + /* '5' is the minimal size of chunked footer ("0\r\n\r\n") */ + if (buf_size < 5) + return false; + mhd_assert (NULL != c->write_buffer); + buf = c->write_buffer + c->write_buffer_append_offset; + mhd_assert (NULL != buf); + used_size = 0; + buf[used_size++] = '0'; + buf[used_size++] = '\r'; + buf[used_size++] = '\n'; + +#if 0 // TODO: use dynamic/connection's footers + for (pos = c->rp.response->first_header; NULL != pos; pos = pos->next) + { + if (MHD_FOOTER_KIND == pos->kind) + { + size_t new_used_size; /* resulting size with this header */ + /* '4' is colon, space, linefeeds */ + new_used_size = used_size + pos->header_size + pos->value_size + 4; + if (new_used_size > buf_size) + return MHD_NO; + memcpy (buf + used_size, pos->header, pos->header_size); + used_size += pos->header_size; + buf[used_size++] = ':'; + buf[used_size++] = ' '; + memcpy (buf + used_size, pos->value, pos->value_size); + used_size += pos->value_size; + buf[used_size++] = '\r'; + buf[used_size++] = '\n'; + mhd_assert (used_size == new_used_size); + } + } +#endif + + if (used_size + 2 > buf_size) + return false; + buf[used_size++] = '\r'; + buf[used_size++] = '\n'; + + c->write_buffer_append_offset += used_size; + mhd_assert (c->write_buffer_append_offset <= c->write_buffer_size); + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_prep_chunked_footer (struct MHD_Connection *restrict c) +{ + if (! prep_chunked_footer_inn (c)) + { + mhd_STREAM_ABORT (c, + mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY, + "No memory in the pool for the reply chunked footer."); + return; + } + c->state = MHD_CONNECTION_FOOTERS_SENDING; +} diff --git a/src/mhd2/stream_process_reply.h b/src/mhd2/stream_process_reply.h @@ -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 src/mhd2/stream_process_reply.h + * @brief The declarations of internal functions for forming and sending + * replies for requests + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STREAM_PROCESS_REPLY_H +#define MHD_STREAM_PROCESS_REPLY_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + + +/** + * Check whether Dynamic Content Creator cleanup callback is set and + * call it, if needed. + * Un-set cleanup callback after calling. + * @param c the connection to process + */ +MHD_INTERNAL void +mhd_stream_call_dcc_cleanup_if_needed (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Allocate the connection's write buffer and fill it with all of the + * headers from the response. + * Required headers are added here. + * + * @param c the connection to process + * @return 'true' if state has been update, + * 'false' if connection is going to be aborted + */ +MHD_INTERNAL bool +mhd_stream_build_header_response (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Prepare the unchunked response content of this connection for sending. + * + * @param c the connection + * @return 'true' if connection new state could be processed now, + * 'false' if no new state processing is needed. + */ +MHD_INTERNAL bool +mhd_stream_prep_unchunked_body (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Prepare the chunked response content of this connection for sending. + * + * @return 'true' if connection new state could be processed now, + * 'false' if no new state processing is needed. + */ +MHD_INTERNAL bool +mhd_stream_prep_chunked_body (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Allocate the connection's write buffer (if necessary) and fill it + * with response footers. + * + * @param c the connection + */ +MHD_INTERNAL void +mhd_stream_prep_chunked_footer (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_STREAM_PROCESS_REPLY_H */ diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c @@ -0,0 +1,3840 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + 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/stream_process_request.c + * @brief The implementation of internal functions for requests parsing + * and processing + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other + * contributors. + */ + +#include "mhd_sys_options.h" +#include "stream_process_request.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "sys_malloc.h" + +#include "mhd_str_types.h" +#include "mhd_str_macros.h" +#include "mhd_str.h" + +#include <string.h> + +#include "mhd_daemon.h" +#include "mhd_connection.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. + */ +#define ERR_RSP_REQUEST_MALFORMED \ + "<html><head><title>Request malformed</title></head>" \ + "<body>HTTP request is syntactically incorrect.</body></html>" + +/** + * Response text used when the request HTTP version is too old. + */ +#define ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD \ + "<html>" \ + "<head><title>Requested HTTP version is not supported</title></head>" \ + "<body>Requested HTTP version is too old and not " \ + "supported.</body></html>" +/** + * Response text used when the request HTTP version is not supported. + */ +#define ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED \ + "<html>" \ + "<head><title>Requested HTTP version is not supported</title></head>" \ + "<body>Requested HTTP version is not supported.</body></html>" + +/** + * Response text used when the request HTTP header has bare CR character + * without LF character (and CR is not allowed to be treated as whitespace). + */ +#define ERR_RSP_BARE_CR_IN_HEADER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>Request HTTP header has bare CR character without " \ + "following LF character.</body>" \ + "</html>" + +/** + * Response text used when the request HTTP footer has bare CR character + * without LF character (and CR is not allowed to be treated as whitespace). + */ +#define ERR_RSP_BARE_CR_IN_FOOTER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>Request HTTP footer has bare CR character without " \ + "following LF character.</body>" \ + "</html>" + +/** + * Response text used when the request HTTP header has bare LF character + * without CR character. + */ +#define ERR_RSP_BARE_LF_IN_HEADER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>Request HTTP header has bare LF character without " \ + "preceding CR character.</body>" \ + "</html>" +/** + * Response text used when the request HTTP footer has bare LF character + * without CR character. + */ +#define ERR_RSP_BARE_LF_IN_FOOTER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>Request HTTP footer has bare LF character without " \ + "preceding CR character.</body>" \ + "</html>" + +/** + * Response text used when the request line has more then two whitespaces. + */ +#define ERR_RSP_RQ_LINE_TOO_MANY_WSP \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>The request line has more then two whitespaces.</body>" \ + "</html>" + +/** + * Response text used when the request line has invalid characters in URI. + */ +#define ERR_RSP_RQ_TARGET_INVALID_CHAR \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request has invalid characters in " \ + "the request-target.</body>" \ + "</html>" + +/** + * Response text used when line folding is used in request headers. + */ +#define ERR_RSP_OBS_FOLD \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>Obsolete line folding is used in HTTP request header.</body>" \ + "</html>" + +/** + * Response text used when line folding is used in request footers. + */ +#define ERR_RSP_OBS_FOLD_FOOTER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>Obsolete line folding is used in HTTP request footer.</body>" \ + "</html>" + +/** + * Response text used when request header has no colon character. + */ +#define ERR_RSP_HEADER_WITHOUT_COLON \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request header line has no colon character.</body>" \ + "</html>" + +/** + * Response text used when request footer has no colon character. + */ +#define ERR_RSP_FOOTER_WITHOUT_COLON \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request footer line has no colon character.</body>" \ + "</html>" +/** + * Response text used when the request has whitespace at the start + * of the first header line. + */ +#define ERR_RSP_WSP_BEFORE_HEADER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request has whitespace between the request line and " \ + "the first header.</body>" \ + "</html>" + +/** + * Response text used when the request has whitespace at the start + * of the first footer line. + */ +#define ERR_RSP_WSP_BEFORE_FOOTER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>First HTTP footer line has whitespace at the first " \ + "position.</body>" \ + "</html>" + +/** + * Response text used when the whitespace found before colon (inside header + * name or between header name and colon). + */ +#define ERR_RSP_WSP_IN_HEADER_NAME \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request has whitespace before the first colon " \ + "in header line.</body>" \ + "</html>" + +/** + * Response text used when the whitespace found before colon (inside header + * name or between header name and colon). + */ +#define ERR_RSP_WSP_IN_FOOTER_NAME \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request has whitespace before the first colon " \ + "in footer line.</body>" \ + "</html>" +/** + * Response text used when request header has invalid character. + */ +#define ERR_RSP_INVALID_CHR_IN_HEADER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request has invalid character in header.</body>" \ + "</html>" + +/** + * Response text used when request header has invalid character. + */ +#define ERR_RSP_INVALID_CHR_IN_FOOTER \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request has invalid character in footer.</body>" \ + "</html>" + +/** + * Response text used when request header has zero-length header (filed) name. + */ +#define ERR_RSP_EMPTY_HEADER_NAME \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request header has empty header name.</body>" \ + "</html>" + +/** + * Response text used when request header has zero-length header (filed) name. + */ +#define ERR_RSP_EMPTY_FOOTER_NAME \ + "<html>" \ + "<head><title>Request broken</title></head>" \ + "<body>HTTP request footer has empty footer name.</body>" \ + "</html>" + +/** + * Response text used when the request header is too big to be processed. + */ +#define ERR_RSP_REQUEST_HEADER_TOO_BIG \ + "<html>" \ + "<head><title>Request too big</title></head>" \ + "<body><p>The total size of the request headers, which includes the " \ + "request target and the request field lines, exceeds the memory " \ + "constraints of this web server.</p>" \ + "<p>The request could be re-tried with shorter field lines, a shorter " \ + "request target or a shorter request method token.</p></body>" \ + "</html>" + +/** + * Response text used when the request header is too big to be processed. + */ +#define ERR_RSP_REQUEST_FOOTER_TOO_BIG \ + "<html>" \ + "<head><title>Request too big</title></head>" \ + "<body><p>The total size of the request headers, which includes the " \ + "request target, the request field lines and the chunked trailer " \ + "section exceeds the memory constraints of this web server.</p>" \ + "<p>The request could be re-tried with a shorter chunked trailer " \ + "section, shorter field lines, a shorter request target or " \ + "a shorter request method token.</p></body>" \ + "</html>" + +/** + * Response text used when the request (http header) is too big to + * be processed. + */ +#define ERR_RSP_MSG_REQUEST_TOO_BIG \ + "<html>" \ + "<head><title>Request too big</title></head>" \ + "<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 + * contain a "Host:" header and still claims to be HTTP 1.1. + */ +#define ERR_RSP_REQUEST_LACKS_HOST \ + "<html>" \ + "<head><title>&quot;Host:&quot; header required</title></head>" \ + "<body>HTTP/1.1 request without <b>&quot;Host:&quot;</b>.</body>" \ + "</html>" + +/** + * Response text used when the request has more than one "Host:" header. + */ +#define ERR_RSP_REQUEST_HAS_SEVERAL_HOSTS \ + "<html>" \ + "<head>" \ + "<title>Several &quot;Host:&quot; headers used</title></head>" \ + "<body>" \ + "Request with more than one <b>&quot;Host:&quot;</b> header.</body>" \ + "</html>" + +/** + * Response text used when the request has unsupported "Transfer-Encoding:". + */ +#define ERR_RSP_UNSUPPORTED_TR_ENCODING \ + "<html>" \ + "<head><title>Unsupported Transfer-Encoding</title></head>" \ + "<body>The Transfer-Encoding used in request is not supported.</body>" \ + "</html>" + +/** + * Response text used when the request has unsupported both headers: + * "Transfer-Encoding:" and "Content-Length:" + */ +#define ERR_RSP_REQUEST_CNTNLENGTH_WITH_TR_ENCODING \ + "<html>" \ + "<head><title>Malformed request</title></head>" \ + "<body>Wrong combination of the request headers: both Transfer-Encoding " \ + "and Content-Length headers are used at the same time.</body>" \ + "</html>" + +/** + * Response text used when the request HTTP content is too large. + */ +#define ERR_RSP_REQUEST_CONTENTLENGTH_TOOLARGE \ + "<html><head><title>Request content too large</title></head>" \ + "<body>HTTP request has too large value for " \ + "<b>Content-Length</b> header.</body></html>" + +/** + * Response text used when the request HTTP chunked encoding is + * malformed. + */ +#define ERR_RSP_REQUEST_CONTENTLENGTH_MALFORMED \ + "<html><head><title>Request malformed</title></head>" \ + "<body>HTTP request has wrong value for " \ + "<b>Content-Length</b> header.</body></html>" + +/** + * Response text used when the request has more than one "Content-Length:" + * header. + */ +#define ERR_RSP_REQUEST_CONTENTLENGTH_SEVERAL \ + "<html><head><title>Request malformed</title></head>" \ + "<body>HTTP request has several " \ + "<b>Content-Length</b> headers.</body></html>" + +/** + * Response text used when the request HTTP chunked encoding is + * malformed. + */ +#define ERR_RSP_REQUEST_CHUNKED_MALFORMED \ + "<html><head><title>Request malformed</title></head>" \ + "<body>HTTP chunked encoding is syntactically incorrect.</body></html>" + +/** + * Response text used when the request HTTP chunk is too large. + */ +#define ERR_RSP_REQUEST_CHUNK_TOO_LARGE \ + "<html><head><title>Request content too large</title></head>" \ + "<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 + * strictness level. + * RFC 9112, section 2.2 + */ +#define MHD_ALLOW_BARE_LF_AS_CRLF_(discp_lvl) (0 >= discp_lvl) + +/** + * The valid length of any HTTP version string + */ +#define HTTP_VER_LEN (mhd_SSTR_LEN (MHD_HTTP_VERSION_1_1_STR)) + + +/** + * Detect standard HTTP request method + * + * @param connection the connection + * @param method the pointer to HTTP request method string + * @param len the length of @a method in bytes + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) void +parse_http_std_method (struct MHD_Connection *restrict connection, + size_t len, + const char *restrict method) +{ + const char *const m = method; /**< short alias */ + mhd_assert (NULL != m); + mhd_assert (0 != len); + + if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_GET) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_GET, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_GET; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_HEAD) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_HEAD, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_HEAD; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_POST) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_POST, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_POST; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_PUT) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_PUT, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_PUT; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_DELETE) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_DELETE, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_DELETE; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_CONNECT) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_CONNECT, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_CONNECT; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_OPTIONS) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_OPTIONS, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_OPTIONS; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_TRACE) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_TRACE, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_TRACE; + else if ((mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_ASTERISK) == len) && + (0 == memcmp (m, MHD_HTTP_METHOD_STR_ASTERISK, len))) + connection->rq.http_mthd = mhd_HTTP_METHOD_ASTERISK; + else + connection->rq.http_mthd = mhd_HTTP_METHOD_OTHER; +} + + +/** + * Detect HTTP version, send error response if version is not supported + * + * @param connection the connection + * @param http_string the pointer to HTTP version string + * @param len the length of @a http_string in bytes + * @return true if HTTP version is correct and supported, + * false if HTTP version is not correct or unsupported. + */ +static bool +parse_http_version (struct MHD_Connection *restrict connection, + const char *restrict http_string, + size_t len) +{ + const char *const h = http_string; /**< short alias */ + mhd_assert (NULL != http_string); + + /* String must start with 'HTTP/d.d', case-sensetive match. + * See https://www.rfc-editor.org/rfc/rfc9112#name-http-version */ + if ((HTTP_VER_LEN != len) || + ('H' != h[0]) || ('T' != h[1]) || ('T' != h[2]) || ('P' != h[3]) || + ('/' != h[4]) + || ('.' != h[6])) + { + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + mhd_RESPOND_WITH_ERROR_STATIC (connection, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_MALFORMED); + return false; + } + if (1 == h[5] - '0') + { + /* HTTP/1.x */ + if (1 == h[7] - '0') + { + connection->rq.http_ver = MHD_HTTP_VERSION_1_1; + return true; + } + else if (0 == h[7] - '0') + { + connection->rq.http_ver = MHD_HTTP_VERSION_1_0; + return true; + } + else + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + + } + else if (0 == h[5] - '0') + { + /* Too old major version */ + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + mhd_RESPOND_WITH_ERROR_STATIC (connection, + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD); + return false; + } + else if ((2 == h[5] - '0') && ('0' == h[7] - '0')) + connection->rq.http_ver = MHD_HTTP_VERSION_2; + else + connection->rq.http_ver = MHD_HTTP_VERSION_INVALID; + + mhd_RESPOND_WITH_ERROR_STATIC (connection, + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED); + return false; +} + + +#ifndef MHD_MAX_EMPTY_LINES_SKIP +/** + * The maximum number of ignored empty line before the request line + * at default "strictness" level. + */ +# define MHD_MAX_EMPTY_LINES_SKIP 1024 +#endif /* ! MHD_MAX_EMPTY_LINES_SKIP */ + + +/** + * Find and parse the request line. + * @param c the connection to process + * @return true if request line completely processed (or unrecoverable error + * found) and state is changed, + * false if not enough data yet in the receive buffer + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +get_request_line_inner (struct MHD_Connection *restrict c) +{ + size_t p; /**< The current processing position */ + const int discp_lvl = c->daemon->req_cfg.strictnees; + /* Allow to skip one or more empty lines before the request line. + RFC 9112, section 2.2 */ + const bool skip_empty_lines = (1 >= discp_lvl); + /* Allow to skip more then one empty line before the request line. + RFC 9112, section 2.2 */ + const bool skip_several_empty_lines = (skip_empty_lines && (0 >= discp_lvl)); + /* Allow to skip unlimited number of empty lines before the request line. + RFC 9112, section 2.2 */ + const bool skip_unlimited_empty_lines = + (skip_empty_lines && (-3 >= discp_lvl)); + /* Treat bare LF as the end of the line. + RFC 9112, section 2.2 */ + const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + /* Treat tab as whitespace delimiter. + RFC 9112, section 3 */ + const bool tab_as_wsp = (0 >= discp_lvl); + /* Treat VT (vertical tab) and FF (form feed) as whitespace delimiters. + RFC 9112, section 3 */ + const bool other_wsp_as_wsp = (-1 >= discp_lvl); + /* Treat continuous whitespace block as a single space. + RFC 9112, section 3 */ + const bool wsp_blocks = (-1 >= discp_lvl); + /* Parse whitespace in URI, special parsing of the request line. + RFC 9112, section 3.2 */ + const bool wsp_in_uri = (0 >= discp_lvl); + /* Keep whitespace in URI, give app URI with whitespace instead of + automatic redirect to fixed URI. + Violates RFC 9112, section 3.2 */ + const bool wsp_in_uri_keep = (-2 >= discp_lvl); + /* Keep bare CR character as is. + Violates RFC 9112, section 2.2 */ + const bool bare_cr_keep = (wsp_in_uri_keep && (-3 >= discp_lvl)); + /* Treat bare CR as space; replace it with space before processing. + RFC 9112, section 2.2 */ + const bool bare_cr_as_sp = ((! bare_cr_keep) && (-1 >= discp_lvl)); + + mhd_assert (MHD_CONNECTION_INIT == c->state || \ + MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (NULL == c->rq.method || \ + MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd || \ + MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd || \ + 0 != c->rq.hdrs.rq_line.proc_pos); + + if (0 == c->read_buffer_offset) + { + mhd_assert (MHD_CONNECTION_INIT == c->state); + return false; /* No data to process */ + } + p = c->rq.hdrs.rq_line.proc_pos; + mhd_assert (p <= c->read_buffer_offset); + + /* Skip empty lines, if any (and if allowed) */ + /* See RFC 9112, section 2.2 */ + if ((0 == p) + && (skip_empty_lines)) + { + /* Skip empty lines before the request line. + See RFC 9112, section 2.2 */ + bool is_empty_line; + mhd_assert (MHD_CONNECTION_INIT == c->state); + mhd_assert (NULL == c->rq.method); + mhd_assert (NULL == c->rq.url); + mhd_assert (0 == c->rq.url_len); + mhd_assert (NULL == c->rq.hdrs.rq_line.rq_tgt); + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + do + { + is_empty_line = false; + if ('\r' == c->read_buffer[0]) + { + if (1 == c->read_buffer_offset) + return false; /* Not enough data yet */ + if ('\n' == c->read_buffer[1]) + { + is_empty_line = true; + c->read_buffer += 2; + c->read_buffer_size -= 2; + c->read_buffer_offset -= 2; + c->rq.hdrs.rq_line.skipped_empty_lines++; + } + } + else if (('\n' == c->read_buffer[0]) && + (bare_lf_as_crlf)) + { + is_empty_line = true; + c->read_buffer += 1; + c->read_buffer_size -= 1; + c->read_buffer_offset -= 1; + c->rq.hdrs.rq_line.skipped_empty_lines++; + } + if (is_empty_line) + { + if ((! skip_unlimited_empty_lines) && + (((unsigned int) ((skip_several_empty_lines) ? + MHD_MAX_EMPTY_LINES_SKIP : 1)) < + c->rq.hdrs.rq_line.skipped_empty_lines)) + { + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "Too many meaningless extra empty lines " \ + "received before the request."); + return true; /* Process connection closure */ + } + if (0 == c->read_buffer_offset) + return false; /* No more data to process */ + } + } while (is_empty_line); + } + /* All empty lines are skipped */ + + c->state = MHD_CONNECTION_REQ_LINE_RECEIVING; + /* Read and parse the request line */ + mhd_assert (1 <= c->read_buffer_offset); + + while (p < c->read_buffer_offset) + { + char *const restrict read_buffer = c->read_buffer; + const char chr = read_buffer[p]; + bool end_of_line; + /* + The processing logic is different depending on the configured strictness: + + When whitespace BLOCKS are NOT ALLOWED, the end of the whitespace is + processed BEFORE processing of the current character. + When whitespace BLOCKS are ALLOWED, the end of the whitespace is + processed AFTER processing of the current character. + + When space char in the URI is ALLOWED, the delimiter between the URI and + the HTTP version string is processed only at the END of the line. + When space in the URI is NOT ALLOWED, the delimiter between the URI and + the HTTP version string is processed as soon as the FIRST whitespace is + found after URI start. + */ + + end_of_line = false; + + mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_end) || \ + (c->rq.hdrs.rq_line.last_ws_end > \ + c->rq.hdrs.rq_line.last_ws_start)); + mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_start) || \ + (0 != c->rq.hdrs.rq_line.last_ws_end)); + + /* Check for the end of the line */ + if ('\r' == chr) + { + if (p + 1 == c->read_buffer_offset) + { + c->rq.hdrs.rq_line.proc_pos = p; + return false; /* Not enough data yet */ + } + else if ('\n' == read_buffer[p + 1]) + end_of_line = true; + else + { + /* Bare CR alone */ + /* Must be rejected or replaced with space char. + See RFC 9112, section 2.2 */ + if (bare_cr_as_sp) + { + read_buffer[p] = ' '; + c->rq.num_cr_sp_replaced++; + continue; /* Re-start processing of the current character */ + } + else if (! bare_cr_keep) + { + /* A quick simple check whether this 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_BAD_REQUEST, + ERR_RSP_BARE_CR_IN_HEADER); + } + else + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "Bare CR characters are not allowed " \ + "in the request line."); + + return true; /* Error in the request */ + } + } + } + else if ('\n' == chr) + { + /* Bare LF may be recognised as a line delimiter. + See RFC 9112, section 2.2 */ + if (bare_lf_as_crlf) + end_of_line = true; + else + { + /* While RFC does not enforce error for bare LF character, + if this char is not treated as a line delimiter, it should be + rejected to avoid any security weakness due to request smuggling. */ + /* A quick simple check whether this 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_BAD_REQUEST, + ERR_RSP_BARE_LF_IN_HEADER); + } + else + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "Bare LF characters are not allowed " \ + "in the request line."); + return true; /* Error in the request */ + } + } + + if (end_of_line) + { + /* Handle the end of the request line */ + + if (NULL != c->rq.method) + { + if (wsp_in_uri) + { + /* The end of the URI and the start of the HTTP version string + should be determined now. */ + mhd_assert (NULL == c->rq.version); + mhd_assert (0 == c->rq.req_target_len); + if (0 != c->rq.hdrs.rq_line.last_ws_end) + { + /* Determine the end and the length of the URI */ + if (NULL != c->rq.hdrs.rq_line.rq_tgt) + { + read_buffer [c->rq.hdrs.rq_line.last_ws_start] = 0; /* Zero terminate the URI */ + c->rq.req_target_len = + c->rq.hdrs.rq_line.last_ws_start + - (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer); + } + else if ((c->rq.hdrs.rq_line.last_ws_start + 1 < + c->rq.hdrs.rq_line.last_ws_end) && + (HTTP_VER_LEN == (p - c->rq.hdrs.rq_line.last_ws_end))) + { + /* Found only HTTP method and HTTP version and more than one + whitespace between them. Assume zero-length URI. */ + mhd_assert (wsp_blocks); + c->rq.hdrs.rq_line.last_ws_start++; + read_buffer[c->rq.hdrs.rq_line.last_ws_start] = 0; /* Zero terminate the URI */ + c->rq.hdrs.rq_line.rq_tgt = + read_buffer + c->rq.hdrs.rq_line.last_ws_start; + c->rq.req_target_len = 0; + c->rq.hdrs.rq_line.num_ws_in_uri = 0; + c->rq.hdrs.rq_line.rq_tgt_qmark = NULL; + } + /* Determine the start of the HTTP version string */ + if (NULL != c->rq.hdrs.rq_line.rq_tgt) + { + c->rq.version = read_buffer + c->rq.hdrs.rq_line.last_ws_end; + } + } + } + else + { + /* The end of the URI and the start of the HTTP version string + should be already known. */ + if ((NULL == c->rq.version) + && (NULL != c->rq.hdrs.rq_line.rq_tgt) + && (HTTP_VER_LEN == p - (size_t) (c->rq.hdrs.rq_line.rq_tgt + - read_buffer)) + && (0 != read_buffer[(size_t) + (c->rq.hdrs.rq_line.rq_tgt + - read_buffer) - 1])) + { + /* Found only HTTP method and HTTP version and more than one + whitespace between them. Assume zero-length URI. */ + size_t uri_pos; + mhd_assert (wsp_blocks); + mhd_assert (0 == c->rq.req_target_len); + uri_pos = (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer) - 1; + mhd_assert (uri_pos < p); + c->rq.version = c->rq.hdrs.rq_line.rq_tgt; + read_buffer[uri_pos] = 0; /* Zero terminate the URI */ + c->rq.hdrs.rq_line.rq_tgt = read_buffer + uri_pos; + c->rq.req_target_len = 0; + c->rq.hdrs.rq_line.num_ws_in_uri = 0; + c->rq.hdrs.rq_line.rq_tgt_qmark = NULL; + } + } + + if (NULL != c->rq.version) + { + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + if (! parse_http_version (c, c->rq.version, + p + - (size_t) (c->rq.version + - read_buffer))) + { + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state); + return true; /* Unsupported / broken HTTP version */ + } + read_buffer[p] = 0; /* Zero terminate the HTTP version strings */ + if ('\r' == chr) + { + p++; /* Consume CR */ + mhd_assert (p < c->read_buffer_offset); /* The next character has been already checked */ + } + p++; /* Consume LF */ + c->read_buffer += p; + c->read_buffer_size -= p; + c->read_buffer_offset -= p; + mhd_assert (c->rq.hdrs.rq_line.num_ws_in_uri <= \ + c->rq.req_target_len); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + (0 != c->rq.req_target_len)); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + ((size_t) (c->rq.hdrs.rq_line.rq_tgt_qmark \ + - c->rq.hdrs.rq_line.rq_tgt) < \ + c->rq.req_target_len)); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + (c->rq.hdrs.rq_line.rq_tgt_qmark >= \ + c->rq.hdrs.rq_line.rq_tgt)); + return true; /* The request line is successfully parsed */ + } + } + /* Error in the request line */ + + /* A quick simple check whether this 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_BAD_REQUEST, + ERR_RSP_REQUEST_MALFORMED); + } + else + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "The request line is malformed."); + + return true; + } + + /* Process possible end of the previously found whitespace delimiter */ + if ((! wsp_blocks) && + (p == c->rq.hdrs.rq_line.last_ws_end) && + (0 != c->rq.hdrs.rq_line.last_ws_end)) + { + /* Previous character was a whitespace char and whitespace blocks + are not allowed. */ + /* The current position is the next character after + a whitespace delimiter */ + if (NULL == c->rq.hdrs.rq_line.rq_tgt) + { + /* The current position is the start of the URI */ + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + c->rq.hdrs.rq_line.rq_tgt = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + else + { + /* It was a whitespace after the start of the URI */ + if (! wsp_in_uri) + { + mhd_assert ((0 != c->rq.req_target_len) || \ + (c->rq.hdrs.rq_line.rq_tgt + 1 == read_buffer + p)); + mhd_assert (NULL == c->rq.version); /* Too many whitespaces? This error is handled at whitespace start */ + c->rq.version = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + } + } + + /* Process the current character. + Is it not the end of the line. */ + if ((' ' == chr) + || (('\t' == chr) && (tab_as_wsp)) + || ((other_wsp_as_wsp) && ((0xb == chr) || (0xc == chr)))) + { + /* A whitespace character */ + if ((0 == c->rq.hdrs.rq_line.last_ws_end) || + (p != c->rq.hdrs.rq_line.last_ws_end) || + (! wsp_blocks)) + { + /* Found first whitespace char of the new whitespace block */ + if (NULL == c->rq.method) + { + /* Found the end of the HTTP method string */ + mhd_assert (0 == c->rq.hdrs.rq_line.last_ws_start); + mhd_assert (0 == c->rq.hdrs.rq_line.last_ws_end); + mhd_assert (NULL == c->rq.hdrs.rq_line.rq_tgt); + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + if (0 == p) + { + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "The request line starts with a whitespace."); + return true; /* Error in the request */ + } + read_buffer[p] = 0; /* Zero-terminate the request method string */ + c->rq.method = read_buffer; + parse_http_std_method (c, p, c->rq.method); + } + else + { + /* A whitespace after the start of the URI */ + if (! wsp_in_uri) + { + /* Whitespace in URI is not allowed to be parsed */ + if (NULL == c->rq.version) + { + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + /* This is a delimiter between URI and HTTP version string */ + read_buffer[p] = 0; /* Zero-terminate request URI string */ + mhd_assert (((size_t) (c->rq.hdrs.rq_line.rq_tgt \ + - read_buffer)) <= p); + c->rq.req_target_len = + p - (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer); + } + else + { + /* This is a delimiter AFTER version string */ + + /* A quick simple check whether this 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_BAD_REQUEST, + ERR_RSP_RQ_LINE_TOO_MANY_WSP); + } + else + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "The request line has more than " + "two whitespaces."); + return true; /* Error in the request */ + } + } + else + { + /* Whitespace in URI is allowed to be parsed */ + if (0 != c->rq.hdrs.rq_line.last_ws_end) + { + /* The whitespace after the start of the URI has been found already */ + c->rq.hdrs.rq_line.num_ws_in_uri += + c->rq.hdrs.rq_line.last_ws_end + - c->rq.hdrs.rq_line.last_ws_start; + } + } + } + c->rq.hdrs.rq_line.last_ws_start = p; + c->rq.hdrs.rq_line.last_ws_end = p + 1; /* Will be updated on the next char parsing */ + } + else + { + /* Continuation of the whitespace block */ + mhd_assert (0 != c->rq.hdrs.rq_line.last_ws_end); + mhd_assert (0 != p); + c->rq.hdrs.rq_line.last_ws_end = p + 1; + } + } + else + { + /* Non-whitespace char, not the end of the line */ + mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_end) || \ + (c->rq.hdrs.rq_line.last_ws_end == p) || \ + wsp_in_uri); + + if ((p == c->rq.hdrs.rq_line.last_ws_end) && + (0 != c->rq.hdrs.rq_line.last_ws_end) && + (wsp_blocks)) + { + /* The end of the whitespace block */ + if (NULL == c->rq.hdrs.rq_line.rq_tgt) + { + /* This is the first character of the URI */ + mhd_assert (0 == c->rq.req_target_len); + mhd_assert (NULL == c->rq.version); + c->rq.hdrs.rq_line.rq_tgt = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + else + { + if (! wsp_in_uri) + { + /* This is the first character of the HTTP version */ + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + mhd_assert ((0 != c->rq.req_target_len) || \ + (c->rq.hdrs.rq_line.rq_tgt + 1 == read_buffer + p)); + mhd_assert (NULL == c->rq.version); /* Handled at whitespace start */ + c->rq.version = read_buffer + p; + /* Reset the whitespace marker */ + c->rq.hdrs.rq_line.last_ws_start = 0; + c->rq.hdrs.rq_line.last_ws_end = 0; + } + } + } + + /* Handle other special characters */ + if ('?' == chr) + { + if ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) && + (NULL != c->rq.hdrs.rq_line.rq_tgt)) + { + c->rq.hdrs.rq_line.rq_tgt_qmark = read_buffer + p; + } + } + else if ((0xb == chr) || (0xc == chr)) + { + /* VT or LF characters */ + mhd_assert (! other_wsp_as_wsp); + if ((NULL != c->rq.hdrs.rq_line.rq_tgt) && + (NULL == c->rq.version) && + (wsp_in_uri)) + { + c->rq.hdrs.rq_line.num_ws_in_uri++; + } + else + { + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "Invalid character is in the request line."); + return true; /* Error in the request */ + } + } + else if (0 == chr) + { + /* NUL character */ + mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_CLIENT_HTTP_ERR_ABORT_CONN, + "The NUL character is in the request line."); + return true; /* Error in the request */ + } + } + + p++; + } + + c->rq.hdrs.rq_line.proc_pos = p; + return false; /* Not enough data yet */ +} + + +/** + * Callback for iterating over GET parameters + * @param cls the iterator metadata + * @param name the name of the parameter + * @param value the value of the parameter + * @return bool to continue iterations, + * false to stop the iteration + */ +static MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) bool +request_add_get_arg (void *restrict cls, + const struct MHD_String *restrict name, + const struct MHD_StringNullable *restrict value) +{ + struct MHD_Connection *c = (struct MHD_Connection *) cls; + + return mhd_stream_add_field_nullable (c, MHD_VK_GET_ARGUMENT, name, value); +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_INOUT_ (2) bool +// TODO: detect and report errors +mhd_parse_get_args (size_t args_len, + char *restrict args, + mhd_GetArgumentInter cb, + void *restrict cls) +{ + size_t i; + + mhd_assert (args_len < (size_t) (args_len + 1)); /* Does not work when args_len == SIZE_MAX */ + + for (i = 0; i < args_len; ++i) /* Looking for names of the parameters */ + { + size_t name_start; + size_t name_len; + size_t value_start; + size_t value_len; + struct MHD_String name; + struct MHD_StringNullable value; + + /* Found start of the name */ + + value_start = 0; + for (name_start = i; i < args_len; ++i) /* Processing parameter */ + { + if ('+' == args[i]) + args[i] = ' '; + else if ('=' == args[i]) + { + /* Found start of the value */ + args[i] = 0; /* zero-terminate the name */ + for (value_start = ++i; i < args_len; ++i) /* Processing parameter value */ + { + if ('+' == args[i]) + args[i] = ' '; + else if ('&' == args[i]) /* delimiter for the next parameter */ + break; /* Next parameter */ + } + break; /* End of the current parameter */ + } + else if ('&' == args[i]) + break; /* End of the name of the parameter without a value */ + } + if (i < args_len) /* Zero-terminate if not terminated */ + args[i] = 0; + mhd_assert (0 == args[i]); + + /* Store found parameter */ + + if (0 != value_start) /* Value cannot start at zero position */ + { /* Name with value */ + mhd_assert (name_start + 2 <= value_start); + name_len = value_start - name_start - 1; + + value_len = + mhd_str_pct_decode_lenient_n (args + value_start, i - value_start, + args + value_start, i - value_start, + NULL); // TODO: add support for broken encoding detection + value.cstr = args + value_start; + value.len = value_len; + } + else + { /* Name without value */ + name_len = i - name_start; + + value.cstr = NULL; + value.len = 0; + } + name_len = mhd_str_pct_decode_lenient_n (args + name_start, name_len, + args + name_start, name_len, + NULL); // TODO: add support for broken encoding detection + name.cstr = args + name_start; + name.len = name_len; + if (! cb (cls, &name, &value)) + return false; + } + return true; +} + + +/** + * Process request-target string, form URI and URI parameters + * @param c the connection to process + * @return true if request-target successfully processed, + * false if error encountered + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +process_request_target (struct MHD_Connection *c) +{ + size_t params_len; + + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (NULL == c->rq.url); + mhd_assert (0 == c->rq.url_len); + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + (c->rq.hdrs.rq_line.rq_tgt <= c->rq.hdrs.rq_line.rq_tgt_qmark)); + mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \ + (c->rq.req_target_len > \ + (size_t) (c->rq.hdrs.rq_line.rq_tgt_qmark \ + - c->rq.hdrs.rq_line.rq_tgt))); + + /* Log callback before the request-target is modified/decoded */ + if (NULL != c->daemon->req_cfg.uri_cb.cb) + { + struct MHD_String full_uri; + struct MHD_EarlyUriCbData req_data; + full_uri.cstr = c->rq.hdrs.rq_line.rq_tgt; + full_uri.len = c->rq.req_target_len; + req_data.request = &(c->rq); + req_data.request_app_context = NULL; + c->rq.app_aware = true; + c->daemon->req_cfg.uri_cb.cb (c->daemon->req_cfg.uri_cb.cls, + &full_uri, + &req_data); + c->rq.app_context = req_data.request_app_context; + } + + if (NULL != c->rq.hdrs.rq_line.rq_tgt_qmark) + { + params_len = + c->rq.req_target_len + - (size_t) (c->rq.hdrs.rq_line.rq_tgt_qmark - c->rq.hdrs.rq_line.rq_tgt); + + mhd_assert (1 <= params_len); + + c->rq.hdrs.rq_line.rq_tgt_qmark[0] = 0; /* Replace '?' with zero termination */ + + // TODO: support detection of decoding errors + if (! mhd_parse_get_args (params_len - 1, + c->rq.hdrs.rq_line.rq_tgt_qmark + 1, + &request_add_get_arg, + c)) + { + mhd_LOG_MSG (c->daemon, MHD_SC_CONNECTION_POOL_NO_MEM_GET_PARAM, + "Not enough memory in the pool to store GET parameter"); + + mhd_RESPOND_WITH_ERROR_STATIC ( + c, + 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; + + } + } + else + params_len = 0; + + mhd_assert (strlen (c->rq.hdrs.rq_line.rq_tgt) == \ + c->rq.req_target_len - params_len); + + /* Finally unescape URI itself */ + // TODO: support detection of decoding errors + c->rq.url_len = + mhd_str_pct_decode_lenient_n (c->rq.hdrs.rq_line.rq_tgt, + c->rq.req_target_len - params_len, + c->rq.hdrs.rq_line.rq_tgt, + c->rq.req_target_len - params_len, + NULL); + c->rq.url = c->rq.hdrs.rq_line.rq_tgt; + + return true; +} + + +#ifndef MHD_MAX_FIXED_URI_LEN +/** + * The maximum size of the fixed URI for automatic redirection + */ +#define MHD_MAX_FIXED_URI_LEN (64 * 1024) +#endif /* ! MHD_MAX_FIXED_URI_LEN */ + +/** + * Send the automatic redirection to fixed URI when received URI with + * whitespaces. + * If URI is too large, close connection with error. + * + * @param c the connection to process + */ +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; + + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (0 != c->rq.hdrs.rq_line.num_ws_in_uri); + mhd_assert (c->rq.hdrs.rq_line.num_ws_in_uri <= \ + c->rq.req_target_len); + fixed_uri_len = c->rq.req_target_len + + 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 == (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 " \ + "in the URI and the URI is too large to " \ + "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; + + do + { + const char chr = c->rq.hdrs.rq_line.rq_tgt[i++]; + + mhd_assert ('\r' != chr); /* Replaced during request line parsing */ + mhd_assert ('\n' != chr); /* Rejected during request line parsing */ + mhd_assert (0 != chr); /* Rejected during request line parsing */ + switch (chr) + { + case ' ': + b[o++] = '%'; + b[o++] = '2'; + b[o++] = '0'; + break; + case '\t': + b[o++] = '%'; + b[o++] = '0'; + b[o++] = '9'; + break; + case 0x0B: /* VT (vertical tab) */ + b[o++] = '%'; + b[o++] = '0'; + b[o++] = 'B'; + break; + case 0x0C: /* FF (form feed) */ + b[o++] = '%'; + b[o++] = '0'; + b[o++] = 'C'; + break; + default: + b[o++] = chr; + break; + } + } while (i < c->rq.req_target_len); + mhd_assert (fixed_uri_len == o); + b[o] = 0; /* Zero-terminate the result */ + + mhd_RESPOND_WITH_ERROR_HEADER (c, + MHD_HTTP_STATUS_MOVED_PERMANENTLY, + ERR_RSP_RQ_TARGET_INVALID_CHAR, + o + hdr_prefix_len, + hdr_line); + + return; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_get_request_line (struct MHD_Connection *restrict c) +{ + const int discp_lvl = c->daemon->req_cfg.strictnees; + /* Parse whitespace in URI, special parsing of the request line */ + const bool wsp_in_uri = (0 >= discp_lvl); + /* Keep whitespace in URI, give app URI with whitespace instead of + automatic redirect to fixed URI */ + const bool wsp_in_uri_keep = (-2 >= discp_lvl); + + if (! get_request_line_inner (c)) + { + /* End of the request line has not been found yet */ + mhd_assert ((! wsp_in_uri) || NULL == c->rq.version); + if ((NULL != c->rq.version) && + (HTTP_VER_LEN < + (c->rq.hdrs.rq_line.proc_pos + - (size_t) (c->rq.version - c->read_buffer)))) + { + c->rq.http_ver = MHD_HTTP_VERSION_INVALID; + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_MALFORMED); + return true; /* Error in the request */ + } + return false; + } + if (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state) + return true; /* Error in the request */ + + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state); + mhd_assert (NULL == c->rq.url); + mhd_assert (0 == c->rq.url_len); + mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt); + if (0 != c->rq.hdrs.rq_line.num_ws_in_uri) + { + if (! wsp_in_uri) + { + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_RQ_TARGET_INVALID_CHAR); + return true; /* Error in the request */ + } + if (! wsp_in_uri_keep) + { + send_redirect_fixed_rq_target (c); + return true; /* Error in the request */ + } + } + if (! process_request_target (c)) + return true; /* Error in processing */ + + c->state = MHD_CONNECTION_REQ_LINE_RECEIVED; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_switch_to_rq_headers_proc (struct MHD_Connection *restrict c) +{ + c->rq.field_lines.start = c->read_buffer; + mhd_stream_reset_rq_hdr_proc_state (c); + c->state = MHD_CONNECTION_REQ_HEADERS_RECEIVING; +} + + +/** + * Send error reply when receive buffer space exhausted while receiving or + * storing the request headers + * @param c the connection to handle + * @param add_header the optional pointer to the current header string being + * processed or the header failed to be added. + * Could be not zero-terminated and can contain binary zeros. + * Can be NULL. + * @param add_header_size the size of the @a add_header + */ +MHD_static_inline_ +MHD_FN_PAR_NONNULL_ (1) void +handle_req_headers_no_space (struct MHD_Connection *restrict c, + const char *restrict add_header, + size_t add_header_size) +{ + unsigned int err_code; + + err_code = mhd_stream_get_no_space_err_status_code (c, + MHD_PROC_RECV_HEADERS, + add_header_size, + add_header); + mhd_RESPOND_WITH_ERROR_STATIC (c, + err_code, + ERR_RSP_REQUEST_HEADER_TOO_BIG); +} + + +/** + * Send error reply when receive buffer space exhausted while receiving or + * storing the request footers (for chunked requests). + * @param c the connection to handle + * @param add_footer the optional pointer to the current footer string being + * processed or the footer failed to be added. + * Could be not zero-terminated and can contain binary zeros. + * Can be NULL. + * @param add_footer_size the size of the @a add_footer + */ +MHD_static_inline_ +MHD_FN_PAR_NONNULL_ (1) void +handle_req_footers_no_space (struct MHD_Connection *restrict c, + const char *restrict add_footer, + size_t add_footer_size) +{ + (void) add_footer; (void) add_footer_size; /* Unused */ + mhd_assert (c->rq.have_chunked_upload); + + /* Footers should be optional */ + mhd_RESPOND_WITH_ERROR_STATIC ( + c, + MHD_HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE, + ERR_RSP_REQUEST_FOOTER_TOO_BIG); +} + + +/** + * Results of header line reading + */ +enum MHD_FIXED_ENUM_ mhd_HdrLineReadRes +{ + /** + * Not enough data yet + */ + MHD_HDR_LINE_READING_NEED_MORE_DATA = 0, + /** + * New header line has been read + */ + MHD_HDR_LINE_READING_GOT_HEADER, + /** + * Error in header data, error response has been queued + */ + MHD_HDR_LINE_READING_DATA_ERROR, + /** + * Found the end of the request header (end of field lines) + */ + MHD_HDR_LINE_READING_GOT_END_OF_HEADER +}; + + +/** + * Find the end of the request header line and make basic header parsing. + * Handle errors and header folding. + * @param c the connection to process + * @param process_footers if true then footers are processed, + * if false then headers are processed + * @param[out] hdr_name the name of the parsed header (field) + * @param[out] hdr_name the value of the parsed header (field) + * @return mhd_HdrLineReadRes value + */ +static enum mhd_HdrLineReadRes +get_req_header (struct MHD_Connection *restrict c, + bool process_footers, + struct MHD_String *restrict hdr_name, + struct MHD_String *restrict hdr_value) +{ + const int discp_lvl = c->daemon->req_cfg.strictnees; + /* Treat bare LF as the end of the line. + RFC 9112, section 2.2-3 + Note: MHD never replaces bare LF with space (RFC 9110, section 5.5-5). + Bare LF is processed as end of the line or rejected as broken request. */ + const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + /* Keep bare CR character as is. + Violates RFC 9112, section 2.2-4 */ + const bool bare_cr_keep = (-3 >= discp_lvl); + /* Treat bare CR as space; replace it with space before processing. + RFC 9112, section 2.2-4 */ + const bool bare_cr_as_sp = ((! bare_cr_keep) && (-1 >= discp_lvl)); + /* Treat NUL as space; replace it with space before processing. + RFC 9110, section 5.5-5 */ + const bool nul_as_sp = (-1 >= discp_lvl); + /* Allow folded header lines. + RFC 9112, section 5.2-4 */ + const bool allow_folded = (0 >= discp_lvl); + /* Do not reject headers with the whitespace at the start of the first line. + When allowed, the first line with whitespace character at the first + position is ignored (as well as all possible line foldings of the first + line). + RFC 9112, section 2.2-8 */ + const bool allow_wsp_at_start = allow_folded && (-1 >= discp_lvl); + /* Allow whitespace in header (field) name. + Violates RFC 9110, section 5.1-2 */ + const bool allow_wsp_in_name = (-2 >= discp_lvl); + /* Allow zero-length header (field) name. + Violates RFC 9110, section 5.1-2 */ + const bool allow_empty_name = (-2 >= discp_lvl); + /* Allow whitespace before colon. + Violates RFC 9112, section 5.1-2 */ + const bool allow_wsp_before_colon = (-3 >= discp_lvl); + /* Do not abort the request when header line has no colon, just skip such + bad lines. + RFC 9112, section 5-1 */ + const bool allow_line_without_colon = (-2 >= discp_lvl); + + size_t p; /**< The position of the currently processed character */ + + (void) process_footers; /* Unused parameter in non-debug and no messages */ + + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + + p = c->rq.hdrs.hdr.proc_pos; + + mhd_assert (p <= c->read_buffer_offset); + while (p < c->read_buffer_offset) + { + char *const restrict read_buffer = c->read_buffer; + const char chr = read_buffer[p]; + bool end_of_line; + + mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || \ + (c->rq.hdrs.hdr.name_len < p)); + mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || (0 != p)); + mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || \ + (c->rq.hdrs.hdr.name_end_found)); + mhd_assert ((0 == c->rq.hdrs.hdr.value_start) || \ + (c->rq.hdrs.hdr.name_len < c->rq.hdrs.hdr.value_start)); + mhd_assert ((0 == c->rq.hdrs.hdr.value_start) || \ + (0 != c->rq.hdrs.hdr.name_len)); + mhd_assert ((0 == c->rq.hdrs.hdr.ws_start) || \ + (0 == c->rq.hdrs.hdr.name_len) || \ + (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.name_len)); + mhd_assert ((0 == c->rq.hdrs.hdr.ws_start) || \ + (0 == c->rq.hdrs.hdr.value_start) || \ + (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.value_start)); + + /* Check for the end of the line */ + if ('\r' == chr) + { + if (0 != p) + { + /* Line is not empty, need to check for possible line folding */ + if (p + 2 >= c->read_buffer_offset) + break; /* Not enough data yet to check for folded line */ + } + else + { + /* Line is empty, no need to check for possible line folding */ + if (p + 2 > c->read_buffer_offset) + break; /* Not enough data yet to check for the end of the line */ + } + if ('\n' == read_buffer[p + 1]) + end_of_line = true; + else + { + /* Bare CR alone */ + /* Must be rejected or replaced with space char. + See RFC 9112, section 2.2-4 */ + if (bare_cr_as_sp) + { + read_buffer[p] = ' '; + c->rq.num_cr_sp_replaced++; + continue; /* Re-start processing of the current character */ + } + else if (! bare_cr_keep) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_CR_IN_HEADER); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_CR_IN_FOOTER); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + end_of_line = false; + } + } + else if ('\n' == chr) + { + /* Bare LF may be recognised as a line delimiter. + See RFC 9112, section 2.2-3 */ + if (bare_lf_as_crlf) + { + if (0 != p) + { + /* Line is not empty, need to check for possible line folding */ + if (p + 1 >= c->read_buffer_offset) + break; /* Not enough data yet to check for folded line */ + } + end_of_line = true; + } + else + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_LF_IN_HEADER); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_BARE_LF_IN_FOOTER); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + } + else + end_of_line = false; + + if (end_of_line) + { + /* Handle the end of the line */ + /** + * The full length of the line, including CRLF (or bare LF). + */ + const size_t line_len = p + (('\r' == chr) ? 2 : 1); + char next_line_char; + mhd_assert (line_len <= c->read_buffer_offset); + + if (0 == p) + { + /* Zero-length header line. This is the end of the request header + section. + RFC 9112, Section 2.1-1 */ + mhd_assert (! c->rq.hdrs.hdr.starts_with_ws); + mhd_assert (! c->rq.hdrs.hdr.name_end_found); + mhd_assert (0 == c->rq.hdrs.hdr.name_len); + mhd_assert (0 == c->rq.hdrs.hdr.ws_start); + mhd_assert (0 == c->rq.hdrs.hdr.value_start); + /* Consume the line with CRLF (or bare LF) */ + c->read_buffer += line_len; + c->read_buffer_offset -= line_len; + c->read_buffer_size -= line_len; + return MHD_HDR_LINE_READING_GOT_END_OF_HEADER; + } + + mhd_assert (line_len < c->read_buffer_offset); + mhd_assert (0 != line_len); + mhd_assert ('\n' == read_buffer[line_len - 1]); + next_line_char = read_buffer[line_len]; + if ((' ' == next_line_char) || + ('\t' == next_line_char)) + { + /* Folded line */ + if (! allow_folded) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_OBS_FOLD); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_OBS_FOLD_FOOTER); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + /* Replace CRLF (or bare LF) character(s) with space characters. + See RFC 9112, Section 5.2-4 */ + read_buffer[p] = ' '; + if ('\r' == chr) + read_buffer[p + 1] = ' '; + continue; /* Re-start processing of the current character */ + } + else + { + /* It is not a folded line, it's the real end of the non-empty line */ + bool skip_line = false; + mhd_assert (0 != p); + if (c->rq.hdrs.hdr.starts_with_ws) + { + /* This is the first line and it starts with whitespace. This line + must be discarded completely. + See RFC 9112, Section 2.2-8 */ + mhd_assert (allow_wsp_at_start); + + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_FIRST_HEADER_LINE_SPACE_PREFIXED, + "Whitespace-prefixed first header line " \ + "has been skipped."); + skip_line = true; + } + else if (! c->rq.hdrs.hdr.name_end_found) + { + if (! allow_line_without_colon) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_HEADER_WITHOUT_COLON); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_FOOTER_WITHOUT_COLON); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + /* Skip broken line completely */ + c->rq.skipped_broken_lines++; + skip_line = true; + } + if (skip_line) + { + /* Skip the entire line */ + c->read_buffer += line_len; + c->read_buffer_offset -= line_len; + c->read_buffer_size -= line_len; + p = 0; + /* Reset processing state */ + memset (&c->rq.hdrs.hdr, 0, sizeof(c->rq.hdrs.hdr)); + /* Start processing of the next line */ + continue; + } + else + { + /* This line should be valid header line */ + size_t value_len; + mhd_assert ((0 != c->rq.hdrs.hdr.name_len) || allow_empty_name); + + hdr_name->cstr = read_buffer + 0; /* The name always starts at the first character */ + hdr_name->len = c->rq.hdrs.hdr.name_len; + mhd_assert (0 == hdr_name->cstr[hdr_name->len]); + + if (0 == c->rq.hdrs.hdr.value_start) + { + c->rq.hdrs.hdr.value_start = p; + read_buffer[p] = 0; + value_len = 0; + } + else if (0 != c->rq.hdrs.hdr.ws_start) + { + mhd_assert (p > c->rq.hdrs.hdr.ws_start); + mhd_assert (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.value_start); + read_buffer[c->rq.hdrs.hdr.ws_start] = 0; + value_len = c->rq.hdrs.hdr.ws_start - c->rq.hdrs.hdr.value_start; + } + else + { + mhd_assert (p > c->rq.hdrs.hdr.ws_start); + read_buffer[p] = 0; + value_len = p - c->rq.hdrs.hdr.value_start; + } + hdr_value->cstr = read_buffer + c->rq.hdrs.hdr.value_start; + hdr_value->len = value_len; + mhd_assert (0 == hdr_value->cstr[hdr_value->len]); + /* Consume the entire line */ + c->read_buffer += line_len; + c->read_buffer_offset -= line_len; + c->read_buffer_size -= line_len; + return MHD_HDR_LINE_READING_GOT_HEADER; + } + } + } + else if ((' ' == chr) || ('\t' == chr)) + { + if (0 == p) + { + if (! allow_wsp_at_start) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_BEFORE_HEADER); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_BEFORE_FOOTER); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + c->rq.hdrs.hdr.starts_with_ws = true; + } + else if ((! c->rq.hdrs.hdr.name_end_found) && + (! c->rq.hdrs.hdr.starts_with_ws)) + { + /* Whitespace in header name / between header name and colon */ + if (allow_wsp_in_name || allow_wsp_before_colon) + { + if (0 == c->rq.hdrs.hdr.ws_start) + c->rq.hdrs.hdr.ws_start = p; + } + else + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_HEADER_NAME); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_FOOTER_NAME); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + } + else + { + /* Whitespace before/inside/after header (field) value */ + if (0 == c->rq.hdrs.hdr.ws_start) + c->rq.hdrs.hdr.ws_start = p; + } + } + else if (0 == chr) + { + if (! nul_as_sp) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_INVALID_CHR_IN_HEADER); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_INVALID_CHR_IN_FOOTER); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + read_buffer[p] = ' '; + continue; /* Re-start processing of the current character */ + } + else + { + /* Not a whitespace, not the end of the header line */ + mhd_assert ('\r' != chr); + mhd_assert ('\n' != chr); + mhd_assert ('\0' != chr); + if ((! c->rq.hdrs.hdr.name_end_found) && + (! c->rq.hdrs.hdr.starts_with_ws)) + { + /* Processing the header (field) name */ + if (':' == chr) + { + if (0 == c->rq.hdrs.hdr.ws_start) + c->rq.hdrs.hdr.name_len = p; + else + { + mhd_assert (allow_wsp_in_name || allow_wsp_before_colon); + if (! allow_wsp_before_colon) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_HEADER_NAME); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_FOOTER_NAME); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + c->rq.hdrs.hdr.name_len = c->rq.hdrs.hdr.ws_start; +#ifndef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* ! MHD_FAVOR_SMALL_CODE */ + } + if ((0 == c->rq.hdrs.hdr.name_len) && ! allow_empty_name) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_EMPTY_HEADER_NAME); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_EMPTY_FOOTER_NAME); + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } + c->rq.hdrs.hdr.name_end_found = true; + read_buffer[c->rq.hdrs.hdr.name_len] = 0; /* Zero-terminate the name */ + } + else + { + if (0 != c->rq.hdrs.hdr.ws_start) + { + /* End of the whitespace in header (field) name */ + mhd_assert (allow_wsp_in_name || allow_wsp_before_colon); + if (! allow_wsp_in_name) + { + if (! process_footers) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_HEADER_NAME); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_WSP_IN_FOOTER_NAME); + + return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */ + } +#ifndef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* ! MHD_FAVOR_SMALL_CODE */ + } + } + } + else + { + /* Processing the header (field) value */ + if (0 == c->rq.hdrs.hdr.value_start) + c->rq.hdrs.hdr.value_start = p; +#ifndef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* ! MHD_FAVOR_SMALL_CODE */ + } +#ifdef MHD_FAVOR_SMALL_CODE + c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */ +#endif /* MHD_FAVOR_SMALL_CODE */ + } + p++; + } + c->rq.hdrs.hdr.proc_pos = p; + return MHD_HDR_LINE_READING_NEED_MORE_DATA; /* Not enough data yet */ +} + + +/** + * Reset request header processing state. + * + * This function resets the processing state before processing the next header + * (or footer) line. + * @param c the connection to process + */ +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_reset_rq_hdr_proc_state (struct MHD_Connection *c) +{ + memset (&c->rq.hdrs.hdr, 0, sizeof(c->rq.hdrs.hdr)); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_get_request_headers (struct MHD_Connection *restrict c, + bool process_footers) +{ + do + { + struct MHD_String hdr_name; + struct MHD_String hdr_value; + enum mhd_HdrLineReadRes res; + + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + +#ifndef NDEBUG + hdr_name.cstr = NULL; + hdr_value.cstr = NULL; +#endif /* ! NDEBUG */ + res = get_req_header (c, process_footers, &hdr_name, &hdr_value); + if (MHD_HDR_LINE_READING_GOT_HEADER == res) + { + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + mhd_assert (NULL != hdr_name.cstr); + mhd_assert (NULL != hdr_value.cstr); + /* Values must be zero-terminated and must not have binary zeros */ + mhd_assert (strlen (hdr_name.cstr) == hdr_name.len); + mhd_assert (strlen (hdr_value.cstr) == hdr_value.len); + /* Values must not have whitespaces at the start or at the end */ + mhd_assert ((hdr_name.len == 0) || (hdr_name.cstr[0] != ' ')); + mhd_assert ((hdr_name.len == 0) || (hdr_name.cstr[0] != '\t')); + mhd_assert ((hdr_name.len == 0) || \ + (hdr_name.cstr[hdr_name.len - 1] != ' ')); + mhd_assert ((hdr_name.len == 0) || \ + (hdr_name.cstr[hdr_name.len - 1] != '\t')); + mhd_assert ((hdr_value.len == 0) || (hdr_value.cstr[0] != ' ')); + mhd_assert ((hdr_value.len == 0) || (hdr_value.cstr[0] != '\t')); + mhd_assert ((hdr_value.len == 0) || \ + (hdr_value.cstr[hdr_value.len - 1] != ' ')); + mhd_assert ((hdr_value.len == 0) || \ + (hdr_value.cstr[hdr_value.len - 1] != '\t')); + + if (! mhd_stream_add_field (c, + process_footers ? + MHD_VK_FOOTER : MHD_VK_HEADER, + &hdr_name, + &hdr_value)) + { + size_t add_element_size; + + mhd_assert (hdr_name.cstr < hdr_value.cstr); + + if (! process_footers) + mhd_LOG_MSG (c->daemon, MHD_SC_CONNECTION_POOL_MALLOC_FAILURE_REQ, \ + "Failed to allocate memory in the connection memory " \ + "pool to store header."); + else + mhd_LOG_MSG (c->daemon, MHD_SC_CONNECTION_POOL_MALLOC_FAILURE_REQ, \ + "Failed to allocate memory in the connection memory " \ + "pool to store footer."); + + add_element_size = hdr_value.len + + (size_t) (hdr_value.cstr - hdr_name.cstr); + + if (! process_footers) + handle_req_headers_no_space (c, hdr_name.cstr, add_element_size); + else + handle_req_footers_no_space (c, hdr_name.cstr, add_element_size); + + mhd_assert (MHD_CONNECTION_FULL_REQ_RECEIVED < c->state); + return true; + } + /* Reset processing state */ + mhd_stream_reset_rq_hdr_proc_state (c); + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + /* Read the next header (field) line */ + continue; + } + else if (MHD_HDR_LINE_READING_NEED_MORE_DATA == res) + { + mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \ + c->state); + return false; + } + else if (MHD_HDR_LINE_READING_DATA_ERROR == res) + { + mhd_assert ((process_footers ? \ + MHD_CONNECTION_FOOTERS_RECEIVING : \ + MHD_CONNECTION_REQ_HEADERS_RECEIVING) < c->state); + mhd_assert (c->stop_with_error); + mhd_assert (c->discard_request); + return true; + } + mhd_assert (MHD_HDR_LINE_READING_GOT_END_OF_HEADER == res); + break; + } while (1); + + if (1 == c->rq.num_cr_sp_replaced) + { + if (! process_footers) + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + "One bare CR character has been replaced with space " \ + "in the request line or in the request headers."); + else + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_FOOTER_CR_REPLACED, \ + "One bare CR character has been replaced with space " \ + "in the request footers."); + } + else if (0 != c->rq.num_cr_sp_replaced) + { + if (! process_footers) + mhd_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + mhd_LOG_FMT ("%" PRIuFAST64 " bare CR characters have " \ + "been replaced with spaces in the request " \ + "line and/or in the request headers."), \ + (uint_fast64_t) c->rq.num_cr_sp_replaced); + else + mhd_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + mhd_LOG_FMT ("%" PRIuFAST64 " bare CR characters have " \ + "been replaced with spaces in the request " \ + "footers."), \ + (uint_fast64_t) c->rq.num_cr_sp_replaced); + + + } + if (1 == c->rq.skipped_broken_lines) + { + if (! process_footers) + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_HEADER_LINE_NO_COLON, \ + "One header line without colon has been skipped."); + else + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_FOOTER_LINE_NO_COLON, \ + "One footer line without colon has been skipped."); + } + else if (0 != c->rq.skipped_broken_lines) + { + if (! process_footers) + mhd_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + mhd_LOG_FMT ("%" PRIu64 " header lines without colons " + "have been skipped."), + (uint_fast64_t) c->rq.skipped_broken_lines); + else + mhd_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \ + mhd_LOG_FMT ("%" PRIu64 " footer lines without colons " + "have been skipped."), + (uint_fast64_t) c->rq.skipped_broken_lines); + } + + mhd_assert (c->rq.method < c->read_buffer); + if (! process_footers) + { + c->rq.header_size = (size_t) (c->read_buffer - c->rq.method); + mhd_assert (NULL != c->rq.field_lines.start); + c->rq.field_lines.size = + (size_t) ((c->read_buffer - c->rq.field_lines.start) - 1); + if ('\r' == *(c->read_buffer - 2)) + c->rq.field_lines.size--; + c->state = MHD_CONNECTION_HEADERS_RECEIVED; + + if (mhd_BUF_INC_SIZE > c->read_buffer_size) + { + /* Try to re-use some of the last bytes of the request header */ + /* Do this only if space in the read buffer is limited AND + amount of read ahead data is small. */ + /** + * The position of the terminating NUL after the last character of + * the last header element. + */ + const char *last_elmnt_end; + size_t shift_back_size; + struct mhd_RequestField *header; + header = mhd_DLINKEDL_GET_LAST (&(c->rq), fields); + if (NULL != header) + last_elmnt_end = + header->field.nv.value.cstr + header->field.nv.value.len; + else + last_elmnt_end = c->rq.version + HTTP_VER_LEN; + mhd_assert ((last_elmnt_end + 1) < c->read_buffer); + shift_back_size = (size_t) (c->read_buffer - (last_elmnt_end + 1)); + if (0 != c->read_buffer_offset) + memmove (c->read_buffer - shift_back_size, + c->read_buffer, + c->read_buffer_offset); + c->read_buffer -= shift_back_size; + c->read_buffer_size += shift_back_size; + } + } + else + c->state = MHD_CONNECTION_FOOTERS_RECEIVED; + + return true; +} + + +#ifdef COOKIE_SUPPORT + +/** + * Cookie parsing result + */ +enum _MHD_ParseCookie +{ + MHD_PARSE_COOKIE_OK_LAX = 2 /**< Cookies parsed, but workarounds used */ + , + MHD_PARSE_COOKIE_OK = 1 /**< Success or no cookies in headers */ + , + MHD_PARSE_COOKIE_NO_MEMORY = 0 /**< Not enough memory in the pool */ + , + MHD_PARSE_COOKIE_MALFORMED = -1 /**< Invalid cookie header */ +}; + + +/** + * Parse the cookies string (see RFC 6265). + * + * Try to parse the cookies string even if it is not strictly formed + * as specified by RFC 6265. + * + * @param str the string to parse, without leading whitespaces + * @param str_len the size of the @a str, not including mandatory + * zero-termination + * @param connection the connection to add parsed cookies + * @return #MHD_PARSE_COOKIE_OK for success, error code otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_INOUT_SIZE_ (2,1) enum _MHD_ParseCookie +parse_cookies_string (const size_t str_len, + char *restrict str, + struct MHD_Connection *restrict connection) +{ + size_t i; + bool non_strict; + /* Skip extra whitespaces and empty cookies */ + const bool allow_wsp_empty = (0 >= connection->daemon->req_cfg.strictnees); + /* Allow whitespaces around '=' character */ + const bool wsp_around_eq = (-3 >= connection->daemon->req_cfg.strictnees); + /* Allow whitespaces in quoted cookie value */ + const bool wsp_in_quoted = (-2 >= connection->daemon->req_cfg.strictnees); + /* Allow tab as space after semicolon between cookies */ + const bool tab_as_sp = (0 >= connection->daemon->req_cfg.strictnees); + /* Allow no space after semicolon between cookies */ + const bool allow_no_space = (0 >= connection->daemon->req_cfg.strictnees); + + non_strict = false; + i = 0; + while (i < str_len) + { + size_t name_start; + size_t name_len; + size_t value_start; + size_t value_len; + bool val_quoted; + /* Skip any whitespaces and empty cookies */ + while (' ' == str[i] || '\t' == str[i] || ';' == str[i]) + { + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + i++; + if (i == str_len) + return non_strict? MHD_PARSE_COOKIE_OK_LAX : MHD_PARSE_COOKIE_OK; + } + /* 'i' must point to the first char of cookie-name */ + name_start = i; + /* Find the end of the cookie-name */ + do + { + const char l = str[i]; + if (('=' == l) || (' ' == l) || ('\t' == l) || ('"' == l) || (',' == l) || + (';' == l) || (0 == l)) + break; + } while (str_len > ++i); + name_len = i - name_start; + /* Skip any whitespaces */ + while (str_len > i && (' ' == str[i] || '\t' == str[i])) + { + if (! wsp_around_eq) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + i++; + } + if ((str_len == i) || ('=' != str[i]) || (0 == name_len)) + return MHD_PARSE_COOKIE_MALFORMED; /* Incomplete cookie name */ + /* 'i' must point to the '=' char */ + mhd_assert ('=' == str[i]); + i++; + /* Skip any whitespaces */ + while (str_len > i && (' ' == str[i] || '\t' == str[i])) + { + if (! wsp_around_eq) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + i++; + } + /* 'i' must point to the first char of cookie-value */ + if (str_len == i) + { + value_start = 0; + value_len = 0; +#ifndef NDEBUG + val_quoted = false; /* This assignment used in assert */ +#endif + } + else + { + bool valid_cookie; + val_quoted = ('"' == str[i]); + if (val_quoted) + i++; + value_start = i; + /* Find the end of the cookie-value */ + while (str_len > i) + { + const char l = str[i]; + if ((';' == l) || ('"' == l) || (',' == l) || (';' == l) || + ('\\' == l) || (0 == l)) + break; + if ((' ' == l) || ('\t' == l)) + { + if (! val_quoted) + break; + if (! wsp_in_quoted) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + i++; + } + value_len = i - value_start; + if (val_quoted) + { + if ((str_len == i) || ('"' != str[i])) + return MHD_PARSE_COOKIE_MALFORMED; /* Incomplete cookie value, no closing quote */ + i++; + } + /* Skip any whitespaces */ + if ((str_len > i) && ((' ' == str[i]) || ('\t' == str[i]))) + { + do + { + i++; + } while (str_len > i && (' ' == str[i] || '\t' == str[i])); + /* Whitespace at the end? */ + if (str_len > i) + { + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + } + if (str_len == i) + valid_cookie = true; + else if (';' == str[i]) + valid_cookie = true; + else + valid_cookie = false; + + if (! valid_cookie) + return MHD_PARSE_COOKIE_MALFORMED; /* Garbage at the end of the cookie value */ + } + mhd_assert (0 != name_len); + str[name_start + name_len] = 0; /* Zero-terminate the name */ + if (0 != value_len) + { + struct MHD_String name; + struct MHD_String value; + mhd_assert (value_start + value_len <= str_len); + name.cstr = str + name_start; + name.len = name_len; + str[value_start + value_len] = 0; /* Zero-terminate the value */ + value.cstr = str + value_start; + value.len = value_len; + if (! mhd_stream_add_field (connection, + MHD_VK_COOKIE, + &name, + &value)) + return MHD_PARSE_COOKIE_NO_MEMORY; + } + else + { + struct MHD_String name; + struct MHD_String value; + name.cstr = str + name_start; + name.len = name_len; + value.cstr = ""; + value.len = 0; + if (! mhd_stream_add_field (connection, + MHD_VK_COOKIE, + &name, + &value)) + return MHD_PARSE_COOKIE_NO_MEMORY; + } + if (str_len > i) + { + mhd_assert (0 == str[i] || ';' == str[i]); + mhd_assert (! val_quoted || ';' == str[i]); + mhd_assert (';' != str[i] || val_quoted || non_strict || 0 == value_len); + i++; + if (str_len == i) + { /* No next cookie after semicolon */ + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + else if (' ' != str[i]) + {/* No space after semicolon */ + if (('\t' == str[i]) && tab_as_sp) + i++; + else if (! allow_no_space) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + else + { + i++; + if (str_len == i) + { + if (! allow_wsp_empty) + return MHD_PARSE_COOKIE_MALFORMED; + non_strict = true; + } + } + } + } + return non_strict? MHD_PARSE_COOKIE_OK_LAX : MHD_PARSE_COOKIE_OK; +} + + +/** + * Parse the cookie header (see RFC 6265). + * + * @param connection connection to parse header of + * @param cookie_val the value of the "Cookie:" header + * @return #MHD_PARSE_COOKIE_OK for success, error code otherwise + */ +static enum _MHD_ParseCookie +parse_cookie_header (struct MHD_Connection *restrict connection, + struct MHD_StringNullable *restrict cookie_val) +{ + char *cpy; + size_t i; + enum _MHD_ParseCookie parse_res; + struct mhd_RequestField *const saved_tail = + connection->rq.fields.last; // FIXME: a better way? + const bool allow_partially_correct_cookie = + (1 >= connection->daemon->req_cfg.strictnees); + + if (NULL == cookie_val) + return MHD_PARSE_COOKIE_OK; + if (0 == cookie_val->len) + return MHD_PARSE_COOKIE_OK; + + cpy = mhd_stream_alloc_memory (connection, + cookie_val->len + 1); + if (NULL == cpy) + parse_res = MHD_PARSE_COOKIE_NO_MEMORY; + else + { + memcpy (cpy, + cookie_val->cstr, + cookie_val->len + 1); + mhd_assert (0 == cpy[cookie_val->len]); + + /* Must not have initial whitespaces */ + mhd_assert (' ' != cpy[0]); + mhd_assert ('\t' != cpy[0]); + + i = 0; + parse_res = parse_cookies_string (cookie_val->len - i, cpy + i, connection); + } + + switch (parse_res) + { + case MHD_PARSE_COOKIE_OK: + break; + case MHD_PARSE_COOKIE_OK_LAX: + if (saved_tail != connection->rq.fields.last) + mhd_LOG_MSG (connection->daemon, MHD_SC_REQ_COOKIE_PARSED_NOT_COMPLIANT, \ + "The Cookie header has been parsed, but it is not " + "fully compliant with specifications."); + break; + case MHD_PARSE_COOKIE_MALFORMED: + if (saved_tail != connection->rq.fields.last) // FIXME: a better way? + { + if (! allow_partially_correct_cookie) + { + /* Remove extracted values from partially broken cookie */ + /* Memory remains allocated until the end of the request processing */ + connection->rq.fields.last = saved_tail; // FIXME: a better way? + saved_tail->fields.next = NULL; // FIXME: a better way? + mhd_LOG_MSG ( \ + connection->daemon, MHD_SC_REQ_COOKIE_IGNORED_NOT_COMPLIANT, \ + "The Cookie header is ignored as it contains malformed data."); + } + else + mhd_LOG_MSG (connection->daemon, MHD_SC_REQ_COOKIE_PARSED_PARTIALLY, \ + "The Cookie header has been only partially parsed " \ + "as it contains malformed data."); + } + else + mhd_LOG_MSG (connection->daemon, MHD_SC_REQ_COOKIE_INVALID, + "The Cookie header has malformed data."); + break; + case MHD_PARSE_COOKIE_NO_MEMORY: + mhd_LOG_MSG (connection->daemon, MHD_SC_CONNECTION_POOL_NO_MEM_COOKIE, + "Not enough memory in the connection pool to " + "parse client cookies!\n"); + break; + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + break; + } + + return parse_res; +} + + +/** + * Send error reply when receive buffer space exhausted while receiving or + * storing the request headers + * @param c the connection to handle + * @param add_header the optional pointer to the current header string being + * processed or the header failed to be added. + * Could be not zero-terminated and can contain binary zeros. + * Can be NULL. + * @param add_header_size the size of the @a add_header + */ +MHD_static_inline_ void +handle_req_cookie_no_space (struct MHD_Connection *restrict c) +{ + unsigned int err_code; + + err_code = mhd_stream_get_no_space_err_status_code (c, + MHD_PROC_RECV_COOKIE, + 0, + NULL); + mhd_RESPOND_WITH_ERROR_STATIC (c, + err_code, + ERR_RSP_REQUEST_HEADER_TOO_BIG); +} + + +#endif /* COOKIE_SUPPORT */ + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_parse_request_headers (struct MHD_Connection *restrict c) +{ + bool has_host; + bool has_trenc; + bool has_cntnlen; + bool has_keepalive; + struct mhd_RequestField *f; + + /* The presence of the request body is indicated by "Content-Length:" or + "Transfer-Encoding:" request headers. + Unless one of these two headers is used, the request has no request body. + See RFC9112, Section 6, paragraph 4. */ + c->rq.have_chunked_upload = false; + c->rq.cntn.cntn_size = 0; + + has_host = false; + has_trenc = false; + has_cntnlen = false; + has_keepalive = true; + + for (f = mhd_DLINKEDL_GET_FIRST (&(c->rq), fields); + NULL != f; + f = mhd_DLINKEDL_GET_NEXT (f, fields)) + { + if (MHD_VK_HEADER != f->field.kind) + continue; + + /* "Host:" */ + if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_HOST, + f->field.nv.name.cstr, + f->field.nv.name.len)) + { + if ((has_host) + && (-3 < c->daemon->req_cfg.strictnees)) + { + mhd_LOG_MSG (c->daemon, MHD_SC_HOST_HEADER_SEVERAL, \ + "Received request with more than one 'Host' header."); + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_HAS_SEVERAL_HOSTS); + return; + } + has_host = true; + continue; + } + +#ifdef COOKIE_SUPPORT + /* "Cookie:" */ + if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_COOKIE, + f->field.nv.name.cstr, + f->field.nv.name.len)) + { + if (MHD_PARSE_COOKIE_NO_MEMORY == + parse_cookie_header (c, + &(f->field.nv.value))) + { + handle_req_cookie_no_space (c); + return; + } + continue; + } +#endif /* COOKIE_SUPPORT */ + + /* "Content-Length:" */ + if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_CONTENT_LENGTH, + f->field.nv.name.cstr, + f->field.nv.name.len)) + { + size_t num_digits; + uint_fast64_t cntn_size; + + num_digits = mhd_str_to_uint64_n (f->field.nv.value.cstr, + f->field.nv.value.len, + &cntn_size); + if (((0 == num_digits) && + (0 != f->field.nv.value.len) && + ('9' >= f->field.nv.value.cstr[0]) + && ('0' <= f->field.nv.value.cstr[0])) + || (MHD_SIZE_UNKNOWN == c->rq.cntn.cntn_size)) + { + mhd_LOG_MSG (c->daemon, MHD_SC_CONTENT_LENGTH_TOO_LARGE, \ + "Too large value of 'Content-Length' header. " \ + "Closing connection."); + mhd_RESPOND_WITH_ERROR_STATIC (c, \ + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, \ + ERR_RSP_REQUEST_CONTENTLENGTH_TOOLARGE); + return; + } + else if ((f->field.nv.value.len != num_digits) || + (0 == num_digits)) + { + mhd_LOG_MSG (c->daemon, MHD_SC_CONTENT_LENGTH_MALFORMED, \ + "Failed to parse 'Content-Length' header. " \ + "Closing connection."); + mhd_RESPOND_WITH_ERROR_STATIC (c, \ + MHD_HTTP_STATUS_BAD_REQUEST, \ + ERR_RSP_REQUEST_CONTENTLENGTH_MALFORMED); + return; + } + + if (has_cntnlen) + { + bool send_err; + send_err = false; + if (c->rq.cntn.cntn_size == cntn_size) + { + if (0 < c->daemon->req_cfg.strictnees) + { + mhd_LOG_MSG (c->daemon, MHD_SC_CONTENT_LENGTH_SEVERAL_SAME, \ + "Received request with more than one " \ + "'Content-Length' header with the same value."); + send_err = true; + } + } + else + { + mhd_LOG_MSG (c->daemon, MHD_SC_CONTENT_LENGTH_SEVERAL_DIFFERENT, \ + "Received request with more than one " \ + "'Content-Length' header with conflicting values."); + send_err = true; + } + + if (send_err) + { + mhd_RESPOND_WITH_ERROR_STATIC ( \ + c, \ + MHD_HTTP_STATUS_BAD_REQUEST, \ + ERR_RSP_REQUEST_CONTENTLENGTH_SEVERAL); + return; + } + } + mhd_assert ((0 == c->rq.cntn.cntn_size) || \ + (c->rq.cntn.cntn_size == cntn_size)); + c->rq.cntn.cntn_size = cntn_size; + has_cntnlen = true; + continue; + } + + /* "Connection:" */ + if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_CONNECTION, + f->field.nv.name.cstr, + f->field.nv.name.len)) + { + if (mhd_str_has_token_caseless (f->field.nv.value.cstr, // TODO: compare as size string + "close", + mhd_SSTR_LEN ("close"))) + { + mhd_assert (mhd_CONN_MUST_UPGRADE != c->conn_reuse); + c->conn_reuse = mhd_CONN_MUST_CLOSE; + } + else if ((MHD_HTTP_VERSION_1_0 == c->rq.http_ver) + && (mhd_CONN_MUST_CLOSE != c->conn_reuse)) + { + if (mhd_str_has_token_caseless (f->field.nv.value.cstr, // TODO: compare as size string + "keep-alive", + mhd_SSTR_LEN ("keep-alive"))) + has_keepalive = true; + } + + continue; + } + + /* "Transfer-Encoding:" */ + if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_TRANSFER_ENCODING, + f->field.nv.name.cstr, + f->field.nv.name.len)) + { + if (mhd_str_equal_caseless_n_st ("chunked", + f->field.nv.value.cstr, + f->field.nv.value.len)) + { + c->rq.have_chunked_upload = true; + c->rq.cntn.cntn_size = MHD_SIZE_UNKNOWN; + } + else + { + mhd_LOG_MSG (c->daemon, MHD_SC_CHUNKED_ENCODING_UNSUPPORTED, \ + "The 'Transfer-Encoding' used in request is " \ + "unsupported or invalid."); + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_UNSUPPORTED_TR_ENCODING); + return; + } + has_trenc = true; + continue; + } + } + + if (has_trenc && has_cntnlen) + { + if (0 < c->daemon->req_cfg.strictnees) + { + mhd_RESPOND_WITH_ERROR_STATIC ( \ + c, \ + MHD_HTTP_STATUS_BAD_REQUEST, \ + ERR_RSP_REQUEST_CNTNLENGTH_WITH_TR_ENCODING); + return; + } + /* Must close connection after reply to prevent potential attack */ + c->conn_reuse = mhd_CONN_MUST_CLOSE; + c->rq.cntn.cntn_size = MHD_SIZE_UNKNOWN; + mhd_assert (c->rq.have_chunked_upload); + mhd_LOG_MSG (c->daemon, MHD_SC_CONTENT_LENGTH_AND_TR_ENC, \ + "The 'Content-Length' request header is ignored " \ + "as chunked 'Transfer-Encoding' is used " \ + "for this request."); + } + + if (MHD_HTTP_VERSION_1_1 <= c->rq.http_ver) + { + if ((! has_host) && + (-3 < c->daemon->req_cfg.strictnees)) + { + mhd_LOG_MSG (c->daemon, MHD_SC_HOST_HEADER_MISSING, \ + "Received HTTP/1.1 request without 'Host' header."); + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_LACKS_HOST); + return; + } + } + else + { + if (! has_keepalive) + c->conn_reuse = mhd_CONN_MUST_CLOSE; /* Do not re-use HTTP/1.0 connection by default */ + if (has_trenc) + c->conn_reuse = mhd_CONN_MUST_CLOSE; /* Framing could be incorrect */ + } + + c->state = MHD_CONNECTION_HEADERS_PROCESSED; + return; +} + + +/** + * Is "100 CONTINUE" needed to be sent for current request? + * + * @param c the connection to check + * @return false 100 CONTINUE is not needed, + * true otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +need_100_continue (struct MHD_Connection *restrict c) +{ + const struct MHD_StringNullable *hvalue; + + mhd_assert (MHD_HTTP_VERSION_IS_SUPPORTED (c->rq.http_ver)); + mhd_assert (MHD_CONNECTION_BODY_RECEIVING > c->state); + + if (MHD_HTTP_VERSION_1_0 == c->rq.http_ver) + return false; + + if (0 != c->read_buffer_offset) + return false; /* Part of the content has been received already */ + + hvalue = mhd_request_get_value_st (&(c->rq), + MHD_VK_HEADER, + MHD_HTTP_HEADER_EXPECT); + if (NULL == hvalue) + return false; + + if (mhd_str_equal_caseless_n_st ("100-continue", \ + hvalue->cstr, hvalue->len)) + return true; + + return false; +} + + +/** + * Check whether special buffer is required to handle the upload content and + * try to allocate if necessary. + * Respond with error to the client if buffer cannot be allocated + * @param c the connection to + * @return true if succeed, + * false if error response is set + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +check_and_alloc_buf_for_upload_processing (struct MHD_Connection *restrict c) +{ + mhd_assert ((mhd_ACTION_UPLOAD == c->rq.app_act.head_act.act) || \ + (mhd_ACTION_POST_PROCESS == c->rq.app_act.head_act.act)); + + if (c->rq.have_chunked_upload) + return true; /* The size is unknown, buffers will be dynamically allocated + and re-allocated */ + mhd_assert (c->read_buffer_size > c->read_buffer_offset); +#if 0 // TODO: support processing full response in the connection buffer + if ((c->read_buffer_size - c->read_buffer_offset) >= + c->rq.cntn.cntn_size) + return true; /* No additional buffer needed */ +#endif + + if ((mhd_ACTION_UPLOAD == c->rq.app_act.head_act.act) && + (NULL == c->rq.app_act.head_act.data.upload.full.cb)) + return true; /* data will be processed only incrementally */ + + if (mhd_ACTION_UPLOAD != c->rq.app_act.head_act.act) + { + // TODO: add check for intermental-only POST processing */ + mhd_assert (0 && "Not implemented yet"); + return false; + } + + if ((c->rq.cntn.cntn_size > + c->rq.app_act.head_act.data.upload.large_buffer_size) || + ! mhd_daemon_get_lbuf (c->daemon, c->rq.cntn.cntn_size, + &(c->rq.cntn.lbuf))) + { + if (NULL != c->rq.app_act.head_act.data.upload.inc.cb) + { + c->rq.app_act.head_act.data.upload.full.cb = NULL; + return true; /* Data can be processed incrementally */ + } + + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + ERR_RSP_REQUEST_CONTENTLENGTH_TOOLARGE); + return false; + } + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_call_app_request_cb (struct MHD_Connection *restrict c) +{ + struct MHD_Daemon *restrict d = c->daemon; + struct MHD_String path; + const struct MHD_Action *a; + + mhd_assert (mhd_HTTP_METHOD_NO_METHOD != c->rq.http_mthd); + mhd_assert (NULL == c->rp.response); + + if (mhd_ACTION_NO_ACTION != c->rq.app_act.head_act.act) + MHD_PANIC ("MHD_Action has been set already"); + + path.cstr = c->rq.url; + path.len = c->rq.url_len; + + c->rq.app_aware = true; + a = d->req_cfg.cb (d->req_cfg.cb_cls, + &(c->rq), + &path, + (enum MHD_HTTP_Method) c->rq.http_mthd, + c->rq.cntn.cntn_size); + + if ((NULL != a) + && (((&(c->rq.app_act.head_act) != a)) + || ! mhd_ACTION_IS_VALID (c->rq.app_act.head_act.act))) + { + mhd_LOG_MSG (d, MHD_SC_ACTION_INVALID, \ + "Provided action is not a correct action generated " \ + "for the current request."); + a = NULL; + } + if (NULL == a) + c->rq.app_act.head_act.act = mhd_ACTION_ABORT; + + switch (c->rq.app_act.head_act.act) + { + case mhd_ACTION_RESPONSE: + c->rp.response = c->rq.app_act.head_act.data.response; + c->state = MHD_CONNECTION_REQ_RECV_FINISHED; + return true; + case mhd_ACTION_UPLOAD: + if (0 != c->rq.cntn.cntn_size) + { + if (! check_and_alloc_buf_for_upload_processing (c)) + return true; + if (need_100_continue (c)) + { + c->state = MHD_CONNECTION_CONTINUE_SENDING; + return true; + } + c->state = MHD_CONNECTION_BODY_RECEIVING; + return (0 != c->read_buffer_offset); + } + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return true; + case mhd_ACTION_POST_PROCESS: + mhd_assert (0 && "Not implemented yet"); + return true; + case mhd_ACTION_SUSPEND: + c->suspended = true; + return false; + case mhd_ACTION_ABORT: + mhd_conn_pre_close_app_abort (c); + return false; + case mhd_ACTION_NO_ACTION: + default: + mhd_assert (0 && "Impossible value"); + break; + } + MHD_UNREACHABLE_; + return false; +} + + +/** + * React on provided action for upload + * @param c the stream to use + * @param act the action provided by application + * @param final set to 'true' if this is final upload callback + * @return true if connection state has been changed, + * false otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) bool +process_upload_action (struct MHD_Connection *restrict c, + const struct MHD_UploadAction *act, + bool final) +{ + if (NULL != act) + { + if ((&(c->rq.app_act.upl_act) != act) || + ! mhd_UPLOAD_ACTION_IS_VALID (c->rq.app_act.upl_act.act) || + (final && + (mhd_UPLOAD_ACTION_CONTINUE == c->rq.app_act.upl_act.act))) + { + mhd_LOG_MSG (c->daemon, MHD_SC_UPLOAD_ACTION_INVALID, \ + "Provided action is not a correct action generated " \ + "for the current request."); + act = NULL; + } + } + if (NULL == act) + c->rq.app_act.upl_act.act = mhd_UPLOAD_ACTION_ABORT; + + switch (c->rq.app_act.upl_act.act) + { + case mhd_UPLOAD_ACTION_RESPONSE: + c->rp.response = c->rq.app_act.upl_act.data.response; + c->state = MHD_CONNECTION_REQ_RECV_FINISHED; + return true; + case mhd_UPLOAD_ACTION_CONTINUE: + memset (&(c->rq.app_act.upl_act), 0, sizeof(c->rq.app_act.upl_act)); + return false; + case mhd_UPLOAD_ACTION_SUSPEND: + c->suspended = true; + return false; + case mhd_UPLOAD_ACTION_ABORT: + mhd_conn_pre_close_app_abort (c); + return false; + case mhd_UPLOAD_ACTION_NO_ACTION: + default: + mhd_assert (0 && "Impossible value"); + break; + } + MHD_UNREACHABLE_; + return false; +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +process_request_chunked_body (struct MHD_Connection *restrict c) +{ + struct MHD_Daemon *restrict d = c->daemon; + size_t available; + bool has_more_data; + char *restrict buffer_head; + const int discp_lvl = d->req_cfg.strictnees; + /* Treat bare LF as the end of the line. + RFC 9112, section 2.2-3 + Note: MHD never replaces bare LF with space (RFC 9110, section 5.5-5). + Bare LF is processed as end of the line or rejected as broken request. */ + const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + /* Allow "Bad WhiteSpace" in chunk extension. + RFC 9112, Section 7.1.1, Paragraph 2 */ + const bool allow_bws = (2 < discp_lvl); + bool state_updated; + + mhd_assert (NULL == c->rp.response); + mhd_assert (c->rq.have_chunked_upload); + mhd_assert (MHD_SIZE_UNKNOWN == c->rq.cntn.cntn_size); + + buffer_head = c->read_buffer; + available = c->read_buffer_offset; + state_updated = false; + do + { + size_t cntn_data_ready; + bool need_inc_proc; + + has_more_data = false; + + if ( (c->rq.current_chunk_offset == + c->rq.current_chunk_size) && + (0 != c->rq.current_chunk_size) ) + { + size_t i; + mhd_assert (0 != available); + /* skip new line at the *end* of a chunk */ + i = 0; + if ( (2 <= available) && + ('\r' == buffer_head[0]) && + ('\n' == buffer_head[1]) ) + i += 2; /* skip CRLF */ + else if (bare_lf_as_crlf && ('\n' == buffer_head[0])) + i++; /* skip bare LF */ + else if (2 > available) + break; /* need more upload data */ + if (0 == i) + { + /* malformed encoding */ + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_CHUNKED_MALFORMED); + return true; + } + available -= i; + buffer_head += i; + c->rq.current_chunk_offset = 0; + c->rq.current_chunk_size = 0; + if (0 == available) + break; + } + if (0 != c->rq.current_chunk_size) + { + uint_fast64_t cur_chunk_left; + mhd_assert (c->rq.current_chunk_offset < \ + c->rq.current_chunk_size); + /* we are in the middle of a chunk, give + as much as possible to the client (without + crossing chunk boundaries) */ + cur_chunk_left + = c->rq.current_chunk_size + - c->rq.current_chunk_offset; + if (cur_chunk_left > available) + cntn_data_ready = available; + else + { /* cur_chunk_left <= (size_t)available */ + cntn_data_ready = (size_t) cur_chunk_left; + if (available > cntn_data_ready) + has_more_data = true; + } + } + else + { /* Need the parse the chunk size line */ + /** The number of found digits in the chunk size number */ + size_t num_dig; + uint_fast64_t chunk_size; + bool broken; + bool overflow; + + mhd_assert (0 != available); + + overflow = false; + chunk_size = 0; /* Mute possible compiler warning. + The real value will be set later. */ + + num_dig = mhd_strx_to_uint64_n (buffer_head, + available, + &chunk_size); + mhd_assert (num_dig <= available); + if (num_dig == available) + continue; /* Need line delimiter */ + + broken = (0 == num_dig); + if (broken) + /* Check whether result is invalid due to uint64_t overflow */ + overflow = ((('0' <= buffer_head[0]) && ('9' >= buffer_head[0])) || + (('A' <= buffer_head[0]) && ('F' >= buffer_head[0])) || + (('a' <= buffer_head[0]) && ('f' >= buffer_head[0]))); + else + { + /** + * The length of the string with the number of the chunk size, + * including chunk extension + */ + size_t chunk_size_line_len; + + chunk_size_line_len = 0; + if ((';' == buffer_head[num_dig]) || + (allow_bws && + ((' ' == buffer_head[num_dig]) || + ('\t' == buffer_head[num_dig])))) + { /* Chunk extension */ + size_t i; + + /* Skip bad whitespaces (if any) */ + for (i = num_dig; i < available; ++i) + { + if ((' ' != buffer_head[i]) && ('\t' != buffer_head[i])) + break; + } + if (i == available) + break; /* need more data */ + if (';' == buffer_head[i]) + { + for (++i; i < available; ++i) + { + if ('\n' == buffer_head[i]) + break; + } + if (i == available) + break; /* need more data */ + mhd_assert (i > num_dig); + mhd_assert (1 <= i); + /* Found LF position */ + if (bare_lf_as_crlf) + chunk_size_line_len = i; /* Don't care about CR before LF */ + else if ('\r' == buffer_head[i - 1]) + chunk_size_line_len = i; + } + else + { /* No ';' after "bad whitespace" */ + mhd_assert (allow_bws); + mhd_assert (0 == chunk_size_line_len); + } + } + else + { + mhd_assert (available >= num_dig); + if ((2 <= (available - num_dig)) && + ('\r' == buffer_head[num_dig]) && + ('\n' == buffer_head[num_dig + 1])) + chunk_size_line_len = num_dig + 2; + else if (bare_lf_as_crlf && + ('\n' == buffer_head[num_dig])) + chunk_size_line_len = num_dig + 1; + else if (2 > (available - num_dig)) + break; /* need more data */ + } + + if (0 != chunk_size_line_len) + { /* Valid termination of the chunk size line */ + mhd_assert (chunk_size_line_len <= available); + /* Start reading payload data of the chunk */ + c->rq.current_chunk_offset = 0; + c->rq.current_chunk_size = chunk_size; + + available -= chunk_size_line_len; + buffer_head += chunk_size_line_len; + + if (0 == chunk_size) + { /* The final (termination) chunk */ + c->rq.cntn.cntn_size = c->rq.cntn.recv_size; + c->state = MHD_CONNECTION_BODY_RECEIVED; + state_updated = true; + break; + } + if (available > 0) + has_more_data = true; + continue; + } + /* Invalid chunk size line */ + } + + if (! overflow) + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_REQUEST_CHUNKED_MALFORMED); + else + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + ERR_RSP_REQUEST_CHUNK_TOO_LARGE); + return true; + } + mhd_assert (c->rq.app_aware); + + if (mhd_ACTION_POST_PROCESS == c->rq.app_act.head_act.act) + { + mhd_assert (0 && "Not implemented yet"); // TODO: implement POST + return false; + } + + if (NULL != c->rq.app_act.head_act.data.upload.full.cb) + { + need_inc_proc = false; + + mhd_assert (0 == c->rq.cntn.proc_size); + if ((uint_fast64_t) c->rq.cntn.lbuf.size < + c->rq.cntn.recv_size + cntn_data_ready) + { + size_t grow_size; + + grow_size = (size_t) (c->rq.cntn.recv_size + cntn_data_ready + - c->rq.cntn.lbuf.size); + if (((size_t) (c->rq.cntn.recv_size + cntn_data_ready) < + cntn_data_ready) || (! mhd_daemon_grow_lbuf (d, + grow_size, + &(c->rq.cntn.lbuf)))) + { + /* Failed to grow the buffer, no space to put the new data */ + const struct MHD_UploadAction *act; + if (NULL != c->rq.app_act.head_act.data.upload.inc.cb) + { + mhd_RESPOND_WITH_ERROR_STATIC ( + c, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + ERR_RSP_MSG_REQUEST_TOO_BIG); + return true; + } + c->rq.app_act.head_act.data.upload.full.cb = NULL; /* Cannot process "full" content */ + /* Process previously buffered data */ + mhd_assert (c->rq.cntn.recv_size <= c->rq.cntn.lbuf.size); + act = c->rq.app_act.head_act.data.upload.inc.cb ( + c->rq.app_act.head_act.data.upload.inc.cls, + &(c->rq), + c->rq.cntn.recv_size, + c->rq.cntn.lbuf.buf); + c->rq.cntn.proc_size = c->rq.cntn.recv_size; + mhd_daemon_free_lbuf (d, &(c->rq.cntn.lbuf)); + if (process_upload_action (c, act, false)) + return true; + need_inc_proc = true; + } + } + if (! need_inc_proc) + { + memcpy (c->rq.cntn.lbuf.buf + c->rq.cntn.recv_size, + buffer_head, cntn_data_ready); + c->rq.cntn.recv_size += cntn_data_ready; + } + } + else + need_inc_proc = true; + + if (need_inc_proc) + { + const struct MHD_UploadAction *act; + mhd_assert (NULL != c->rq.app_act.head_act.data.upload.inc.cb); + + c->rq.cntn.recv_size += cntn_data_ready; + act = c->rq.app_act.head_act.data.upload.inc.cb ( + c->rq.app_act.head_act.data.upload.inc.cls, + &(c->rq), + cntn_data_ready, + buffer_head); + c->rq.cntn.proc_size += cntn_data_ready; + state_updated = process_upload_action (c, act, false); + } + + /* dh left "processed" bytes in buffer for next time... */ + buffer_head += cntn_data_ready; + available -= cntn_data_ready; + mhd_assert (MHD_SIZE_UNKNOWN == c->rq.cntn.cntn_size); + c->rq.current_chunk_offset += cntn_data_ready; + } while (has_more_data && ! state_updated); + /* TODO: optionally? zero out reused memory region */ + if ( (available > 0) && + (buffer_head != c->read_buffer) ) + memmove (c->read_buffer, + buffer_head, + available); + else + mhd_assert ((0 == available) || \ + (c->read_buffer_offset == available)); + c->read_buffer_offset = available; + + return state_updated; +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +process_request_nonchunked_body (struct MHD_Connection *restrict c) +{ + size_t cntn_data_ready; + bool read_buf_reuse; + bool state_updated; + + mhd_assert (NULL == c->rp.response); + mhd_assert (! c->rq.have_chunked_upload); + mhd_assert (MHD_SIZE_UNKNOWN != c->rq.cntn.cntn_size); + mhd_assert (c->rq.cntn.recv_size < c->rq.cntn.cntn_size); + mhd_assert (c->rq.app_aware); + + if ((c->rq.cntn.cntn_size - c->rq.cntn.recv_size) < c->read_buffer_offset) + cntn_data_ready = (size_t) (c->rq.cntn.cntn_size - c->rq.cntn.recv_size); + else + cntn_data_ready = c->read_buffer_offset; + + if (mhd_ACTION_POST_PROCESS == c->rq.app_act.head_act.act) + { + mhd_assert (0 && "Not implemented yet"); // TODO: implement POST + return false; + } + + mhd_assert (mhd_ACTION_UPLOAD == c->rq.app_act.head_act.act); + read_buf_reuse = false; + state_updated = false; + if (NULL != c->rq.app_act.head_act.data.upload.full.cb) + { + // TODO: implement processing in pool memory if buffer is large enough + mhd_assert ((c->rq.cntn.recv_size + cntn_data_ready) <= + (uint_fast64_t) c->rq.cntn.lbuf.size); + memcpy (c->rq.cntn.lbuf.buf + c->rq.cntn.recv_size, + c->read_buffer, cntn_data_ready); + c->rq.cntn.recv_size += cntn_data_ready; + read_buf_reuse = true; + if (c->rq.cntn.recv_size == c->rq.cntn.cntn_size) + { + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + state_updated = true; + } + } + else + { + const struct MHD_UploadAction *act; + mhd_assert (NULL != c->rq.app_act.head_act.data.upload.inc.cb); + + c->rq.cntn.recv_size += cntn_data_ready; + act = c->rq.app_act.head_act.data.upload.inc.cb ( + c->rq.app_act.head_act.data.upload.inc.cls, + &(c->rq), + cntn_data_ready, + c->read_buffer); + c->rq.cntn.proc_size += cntn_data_ready; + read_buf_reuse = true; + state_updated = process_upload_action (c, act, false); + } + + if (read_buf_reuse) + { + size_t data_left_size; + mhd_assert (c->read_buffer_offset >= cntn_data_ready); + data_left_size = c->read_buffer_offset - cntn_data_ready; + if (0 != data_left_size) + memmove (c->read_buffer, c->read_buffer + cntn_data_ready, data_left_size) + ; + c->read_buffer_offset = data_left_size; + } + + return state_updated; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_process_request_body (struct MHD_Connection *restrict c) +{ + if (c->rq.have_chunked_upload) + return process_request_chunked_body (c); + + return process_request_nonchunked_body (c); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_stream_call_app_final_upload_cb (struct MHD_Connection *restrict c) +{ + const struct MHD_UploadAction *act; + mhd_assert (mhd_ACTION_POST_PROCESS == c->rq.app_act.head_act.act || \ + mhd_ACTION_UPLOAD == c->rq.app_act.head_act.act); + + if (mhd_ACTION_POST_PROCESS == c->rq.app_act.head_act.act) + { + mhd_assert (0 && "Not implemented yet"); // TODO: implement POST + return false; + } + + if (NULL != c->rq.app_act.head_act.data.upload.full.cb) + { + mhd_assert (c->rq.cntn.recv_size == c->rq.cntn.cntn_size); + mhd_assert (0 == c->rq.cntn.proc_size); + mhd_assert (NULL != c->rq.cntn.lbuf.buf); + mhd_assert (c->rq.cntn.recv_size <= c->rq.cntn.lbuf.size); + // TODO: implement processing in pool memory if it is large enough + act = c->rq.app_act.head_act.data.upload.full.cb ( + c->rq.app_act.head_act.data.upload.full.cls, + &(c->rq), + c->rq.cntn.recv_size, + c->rq.cntn.lbuf.buf); + c->rq.cntn.proc_size = c->rq.cntn.recv_size; + } + else + { + mhd_assert (NULL != c->rq.app_act.head_act.data.upload.inc.cb); + mhd_assert (c->rq.cntn.cntn_size == c->rq.cntn.proc_size); + act = c->rq.app_act.head_act.data.upload.inc.cb ( + c->rq.app_act.head_act.data.upload.inc.cls, + &(c->rq), + 0, + NULL); + } + return process_upload_action (c, act, true); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +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 @@ -0,0 +1,200 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + 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/stream_process_request.h + * @brief The declarations of internal functions for requests parsing + * and processing + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other + * contributors. + */ + +#ifndef MHD_STREAM_PROCESS_REQUEST_H +#define MHD_STREAM_PROCESS_REQUEST_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_str_types.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Callback for iterating over GET parameters + * @param cls the iterator metadata + * @param name the name of the parameter + * @param value the value of the parameter + * @return bool to continue iterations, + * false to stop the iteration + */ +typedef bool +(MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) + *mhd_GetArgumentInter)(void *restrict cls, + const struct MHD_String *restrict name, + const struct MHD_StringNullable *restrict value); + +/** + * Parse and unescape the arguments given by the client + * as part of the HTTP request URI. + * + * @param args_len the function to call on each key-value pair found + * @param[in,out] args argument URI string (after "?" in URI), + * clobbered in the process! + * @param cb function to call on each key-value pair found + * @param cls the iterator context + * @param[out] enc_broken the pointer to get + * @return false on failure + * true on success (parsing succeeded, @a cb always returned true) + */ +MHD_INTERNAL bool +mhd_parse_get_args (size_t args_len, + char *restrict args, + mhd_GetArgumentInter cb, + void *restrict cls) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) MHD_FN_PAR_INOUT_ (2); + + +/** + * Find and parse the request line. + * @param c the connection to process + * @return true if request line completely processed (or unrecoverable error + * found) and state is changed, + * false if not enough data yet in the receive buffer + */ +MHD_INTERNAL bool +mhd_stream_get_request_line (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Switch to request headers (field lines) processing state. + * @param c the connection to process + */ +MHD_INTERNAL void +mhd_stream_switch_to_rq_headers_proc (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Reset request header processing state. + * + * This function resets the processing state before processing the header + * (or footer) line. + * @param c the connection to process + */ +MHD_INTERNAL void +mhd_stream_reset_rq_hdr_proc_state (struct MHD_Connection *c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Find the end of the request GET parameters. + * Advance to the next state when done, handle errors. + * @param c the connection to process + * @param process_footers if true then footers are processed, + * if false then headers are processed + * @return true if request headers reading finished (either successfully + * or with error), + * false if not enough data yet in the receive buffer + */ +MHD_INTERNAL bool +mhd_stream_get_request_headers (struct MHD_Connection *restrict c, + bool process_footers) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Parse the various request headers; figure out the size of the upload and + * make sure the headers follow the protocol. + * Advance to the appropriate state. + * + * @param c the connection to process + */ +MHD_INTERNAL void +mhd_stream_parse_request_headers (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Call application request handling callback, process action given by app. + * Advance to the next state when done, handle errors. + * @param c the connection to process + * @return true if advanced to the next state and the next state could + * be processes right now, + * false if connection is suspended or aborted or more data needed + * to process the next state + */ +MHD_INTERNAL bool +mhd_stream_call_app_request_cb (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Process non-chunked request body or upload chunking encoding. + * Call the upload handler of the application. + * Advance to the next state when done, handle errors. + * + * @param c the connection to process + * @return true if advanced to the next state, + * false if more data needed or connection is suspended or aborted + */ +MHD_INTERNAL bool +mhd_stream_process_request_body (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Call application final upload callback, process action given by app. + * Advance to the next state, handle errors. + * @param c the connection to process + * @return true if advanced to the next state, + * false if connection is suspended or aborted + */ +MHD_INTERNAL bool +mhd_stream_call_app_final_upload_cb (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Process finalisation of request receiving. + * Advance to the next state, handle errors. + * @param c the connection to process + * @return true if advanced to the next state, + * false if connection is suspended or aborted + */ +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 @@ -0,0 +1,503 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff + + 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/stream_process_states.h + * @brief The definitions of internal functions for processing + * stream states + * @author Karlson2k (Evgeny Grin) + * + * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other + * contributors. + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "mhd_str_macros.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" +#include "mhd_response.h" + +#include "stream_process_states.h" +#include "stream_funcs.h" +#include "stream_process_request.h" +#include "stream_process_reply.h" + +#include "conn_mark_ready.h" + +/** + * Update current processing state: need to receive, need to send. + * Mark stream as ready or not ready for processing. + * Grow the receive buffer if neccesary, close stream if no buffer space left, + * but connection needs to receive. + * @param c the connection to update + * @return true if connection states updated successfully, + * false if connection has been prepared for closing + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +update_active_state (struct MHD_Connection *restrict c) +{ + /* Do not update states of suspended connection */ + mhd_assert (! c->suspended); + + if (0 != (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY)) + { + mhd_assert (0 && "Should be handled earlier"); + mhd_conn_pre_close_skt_err (c); + return false; + } + +#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; + } + } + + /* Current MHD design assumes that data must be always processes when + * available. If it is not possible, connection must be suspended. */ + mhd_assert (MHD_EVENT_LOOP_INFO_PROCESS != c->event_loop_info); + + /* Sockets errors must be already handled */ + mhd_assert (0 == (c->sk_ready & mhd_SOCKET_NET_STATE_ERROR_READY)); + + if (0 != + (((unsigned int) c->sk_ready) & ((unsigned int) c->event_loop_info) + & (MHD_EVENT_LOOP_INFO_READ | MHD_EVENT_LOOP_INFO_WRITE))) + mhd_conn_mark_ready (c, c->daemon); + else + mhd_conn_mark_unready (c, c->daemon); + + break; /* Everything was processed. */ + } + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_conn_process_data (struct MHD_Connection *restrict c) +{ + struct MHD_Daemon *const d = c->daemon; + bool daemon_closing; + + /* 'daemon' is not used if epoll is not available and asserts are disabled */ + (void) d; /* Mute compiler warning */ + + if ((c->sk_rmt_shut_wr) && (MHD_CONNECTION_START_REPLY > c->state)) + { + if (0 == c->read_buffer_offset) + { /* Read buffer is empty, connection state is actual */ + mhd_conn_pre_close (c, + (MHD_CONNECTION_INIT == c->state) ? + mhd_CONN_CLOSE_HTTP_COMPLETED : + mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY, + NULL); + return false; + } + } + + mhd_assert (c->resuming || ! c->suspended); + if (c->resuming) + { + // TODO: finish resuming, update activity mark + // TODO: move to special function + } + + 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_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)) + c->sk_discnt_err = mhd_socket_error_get_from_socket (c->socket_fd); + mhd_conn_pre_close_skt_err (c); + return false; + } + + daemon_closing = (mhd_DAEMON_STATE_STOPPING == d->state); +#ifdef MHD_USE_THREADS + daemon_closing = daemon_closing || d->threading.stop_requested; +#endif /* MHD_USE_THREADS */ + if (daemon_closing) + { + mhd_conn_pre_close_d_shutdown (c); + return false; + } + + while (true) // TODO: support suspend + { +#ifdef HTTPS_SUPPORT + // TODO: support TLS, handshake +#endif /* HTTPS_SUPPORT */ + switch (c->state) + { + case MHD_CONNECTION_INIT: + case MHD_CONNECTION_REQ_LINE_RECEIVING: + if (mhd_stream_get_request_line (c)) + { + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state); + mhd_assert ((MHD_HTTP_VERSION_IS_SUPPORTED (c->rq.http_ver)) \ + || (c->discard_request)); + continue; + } + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING >= c->state); + break; + case MHD_CONNECTION_REQ_LINE_RECEIVED: + mhd_stream_switch_to_rq_headers_proc (c); + mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVED != c->state); + continue; + case MHD_CONNECTION_REQ_HEADERS_RECEIVING: + if (mhd_stream_get_request_headers (c, false)) + { + mhd_assert (MHD_CONNECTION_REQ_HEADERS_RECEIVING < c->state); + mhd_assert ((MHD_CONNECTION_HEADERS_RECEIVED == c->state) || \ + (c->discard_request)); + continue; + } + mhd_assert (MHD_CONNECTION_REQ_HEADERS_RECEIVING == c->state); + break; + case MHD_CONNECTION_HEADERS_RECEIVED: + mhd_stream_parse_request_headers (c); + mhd_assert (c->state != MHD_CONNECTION_HEADERS_RECEIVED); + continue; + case MHD_CONNECTION_HEADERS_PROCESSED: + if (mhd_stream_call_app_request_cb (c)) + { + mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED < c->state); + continue; + } + // TODO: add assert + break; + case MHD_CONNECTION_CONTINUE_SENDING: + if (c->continue_message_write_offset == + mhd_SSTR_LEN (mdh_HTTP_1_1_100_CONTINUE_REPLY)) + { + c->state = MHD_CONNECTION_BODY_RECEIVING; + continue; + } + break; + case MHD_CONNECTION_BODY_RECEIVING: + mhd_assert (c->rq.cntn.recv_size < c->rq.cntn.cntn_size); + mhd_assert (! c->discard_request); + mhd_assert (NULL == c->rp.response); + if (0 == c->read_buffer_offset) + break; /* Need more data to process */ + + if (mhd_stream_process_request_body (c)) + continue; + mhd_assert (! c->discard_request); + mhd_assert (NULL == c->rp.response); + break; + case MHD_CONNECTION_BODY_RECEIVED: + mhd_assert (! c->discard_request); + mhd_assert (NULL == c->rp.response); + mhd_assert (c->rq.have_chunked_upload); + /* Reset counter variables reused for footers */ + c->rq.num_cr_sp_replaced = 0; + c->rq.skipped_broken_lines = 0; + mhd_stream_reset_rq_hdr_proc_state (c); + c->state = MHD_CONNECTION_FOOTERS_RECEIVING; + continue; + case MHD_CONNECTION_FOOTERS_RECEIVING: + mhd_assert (c->rq.have_chunked_upload); + if (mhd_stream_get_request_headers (c, true)) + { + mhd_assert (MHD_CONNECTION_FOOTERS_RECEIVING < c->state); + mhd_assert ((MHD_CONNECTION_FOOTERS_RECEIVED == c->state) || \ + (c->discard_request)); + continue; + } + mhd_assert (MHD_CONNECTION_FOOTERS_RECEIVING == c->state); + break; + case MHD_CONNECTION_FOOTERS_RECEIVED: + mhd_assert (c->rq.have_chunked_upload); + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + continue; + case MHD_CONNECTION_FULL_REQ_RECEIVED: + if (mhd_stream_call_app_final_upload_cb (c)) + { + mhd_assert (MHD_CONNECTION_FOOTERS_RECEIVING != c->state); + continue; + } + break; + case MHD_CONNECTION_REQ_RECV_FINISHED: + if (mhd_stream_process_req_recv_finished (c)) + continue; + break; + // TODO: add stage for setup and full request buffers cleanup + case MHD_CONNECTION_START_REPLY: + mhd_assert (NULL != c->rp.response); + mhd_stream_switch_from_recv_to_send (c); + if (! mhd_stream_build_header_response (c)) + break; + mhd_assert (MHD_CONNECTION_START_REPLY != c->state); + break; + case MHD_CONNECTION_HEADERS_SENDING: + /* no default action, wait for sending all the headers */ + break; + case MHD_CONNECTION_HEADERS_SENT: +#if 0 // def UPGRADE_SUPPORT // TODO: upgrade support + if (NULL != c->rp.response->upgrade_handler) + { + mhd_assert (0 && "Not implemented yet"); + c->state = MHD_CONNECTION_UPGRADE; + /* This connection is "upgraded". Pass socket to application. */ + if (MHD_NO == + MHD_response_execute_upgrade_ (c->rp.response, + connection)) + { + /* upgrade failed, fail hard */ + CONNECTION_CLOSE_ERROR (connection, + NULL); + continue; + } + /* Response is not required anymore for this connection. */ + if (1) + { + struct MHD_Response *const resp = c->rp.response; + + c->rp.response = NULL; + MHD_destroy_response (resp); + } + continue; + } +#endif /* UPGRADE_SUPPORT */ + + if (c->rp.props.send_reply_body) + { + if (c->rp.props.chunked) + c->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY; + else + c->state = MHD_CONNECTION_UNCHUNKED_BODY_UNREADY; + } + else + c->state = MHD_CONNECTION_FULL_REPLY_SENT; + continue; + case MHD_CONNECTION_UNCHUNKED_BODY_READY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (! c->rp.props.chunked); + /* nothing to do here, send the data */ + break; + case MHD_CONNECTION_UNCHUNKED_BODY_UNREADY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (! c->rp.props.chunked); + if (0 == c->rp.response->cntn_size) + { /* a shortcut */ + c->state = MHD_CONNECTION_FULL_REPLY_SENT; + continue; + } + if (mhd_stream_prep_unchunked_body (c)) + continue; + break; + case MHD_CONNECTION_CHUNKED_BODY_READY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + /* nothing to do here */ + break; + case MHD_CONNECTION_CHUNKED_BODY_UNREADY: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + if ( (0 == c->rp.response->cntn_size) || + (c->rp.rsp_cntn_read_pos == + c->rp.response->cntn_size) ) + { + c->state = MHD_CONNECTION_CHUNKED_BODY_SENT; + continue; + } + if (mhd_stream_prep_chunked_body (c)) + continue; + break; + case MHD_CONNECTION_CHUNKED_BODY_SENT: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + mhd_assert (c->write_buffer_send_offset <= \ + c->write_buffer_append_offset); + mhd_stream_call_dcc_cleanup_if_needed (c); + mhd_stream_prep_chunked_footer (c); + break; + case MHD_CONNECTION_FOOTERS_SENDING: + mhd_assert (c->rp.props.send_reply_body); + mhd_assert (c->rp.props.chunked); + /* no default action */ + break; + case MHD_CONNECTION_FULL_REPLY_SENT: + // FIXME: support MHD_HTTP_STATUS_PROCESSING ? + /* Reset connection after complete reply */ + mhd_stream_finish_req_serving ( \ + c, + mhd_CONN_KEEPALIVE_POSSIBLE == c->conn_reuse + && ! c->discard_request + && ! c->sk_rmt_shut_wr); + continue; + case MHD_CONNECTION_CLOSED: + 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 ((c->sk_rmt_shut_wr) && (MHD_CONNECTION_START_REPLY > c->state)) + { + mhd_conn_pre_close (c, + (MHD_CONNECTION_INIT == c->state) ? + mhd_CONN_CLOSE_HTTP_COMPLETED : + mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY, + NULL); + return false; + } + + if (mhd_stream_check_timedout (c)) // TODO: centralise timeout checks + { + mhd_conn_pre_close_timedout (c); + return false; + } + update_active_state (c); + /* MHD_connection_update_event_loop_info (c);*/ + + return true; +} diff --git a/src/mhd2/stream_process_states.h b/src/mhd2/stream_process_states.h @@ -0,0 +1,48 @@ +/* + 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/stream_process_states.h + * @brief The declarations of internal functions for processing + * stream states + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_STREAM_PROCESS_STATES_H +#define MHD_STREAM_PROCESS_STATES_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Process states and the data for the connection + * For HTTP/1.1 connection is equal stream + * @param c the connection to process + * @return true if states and data has been successfully processed, + * false if connection needs to be closed + */ +MHD_INTERNAL bool +mhd_conn_process_data (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_STREAM_PROCESS_STATES_H */ diff --git a/src/mhd2/sys_base_types.h b/src/mhd2/sys_base_types.h @@ -0,0 +1,79 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_base_types.h + * @brief The header for basic system types and the NULL constant + * @author Karlson2k (Evgeny Grin) + * + * This header should provide macros or typedefs for uint_fastXX_t, int_fastXX_t + * size_t, ssize_t and NULL. + */ + +#ifndef MHD_SYS_BASE_TYPES_H +#define MHD_SYS_BASE_TYPES_H 1 + +#include "mhd_sys_options.h" + +#if defined(HAVE_SYS_TYPES_H) +# include <sys/types.h> /* ssize_t */ +#elif defined(HAVE_UNISTD_H) +# include <unistd.h> /* should provide ssize_t */ +#endif +#include <stdint.h> /* uint_fast_XXt, int_fast_XXt */ +#if defined(HAVE_STDDEF_H) +# include <stddef.h> /* size_t, NULL */ +#elif defined(HAVE_STDLIB_H) +# include <stdlib.h> /* should provide size_t, NULL */ +#else +# include <stdio.h> /* should provide size_t, NULL */ +#endif +#ifdef HAVE_CRTDEFS_H +# include <crtdefs.h> /* W32-specific header */ +#endif +#ifdef HAVE_INTTYPES_H +# include <inttypes.h> +#endif + +#ifndef HAVE_SSIZE_T +# if defined(HAVE_PTRDIFF_T) +typedef ptrdiff_t ssize_t; +# elif defined(HAVE_INTPTR_T) +/* Not an ideal choice, the size of the largest allocation may be smaller + than the total size of the addressable memory. */ +typedef intptr_t ssize_t; +# else +# error Cannot find suitable 'ssize_t' replacement +# endif +# define HAVE_SSIZE_T 1 +# ifdef _WIN32 +# define _SSIZE_T_DEFINED +# endif +#endif /* ! HAVE_SSIZE_T */ + +#ifndef PRIuFAST64 +# ifdef PRIu64 +# define PRIuFAST64 PRIu64 +# else +# define PRIuFAST64 "llu" +# endif +#endif + +#endif /* ! MHD_SYS_BASE_TYPES_H */ diff --git a/src/mhd2/sys_bool_type.h b/src/mhd2/sys_bool_type.h @@ -0,0 +1,38 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_bool_type.h + * @brief The header for the system 'bool' type + * @author Karlson2k (Evgeny Grin) + * + * This header provides 'bool' type and 'true' and 'false' values. + */ + +#ifndef MHD_BASE_BOOL_TYPE_H +#define MHD_BASE_BOOL_TYPE_H 1 + +#include "mhd_sys_options.h" + +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#endif + +#endif /* ! MHD_BASE_BOOL_TYPE_H */ diff --git a/src/mhd2/sys_errno.h b/src/mhd2/sys_errno.h @@ -0,0 +1,99 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_errno.h + * @brief The wrapper for system <errno.h>. Includes MHD helper macros. + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SYS_ERRNO_H +#define MHD_SYS_ERRNO_H 1 + +#include "mhd_sys_options.h" +#include <errno.h> + +#ifdef EBADF +# define mhd_EBADF_OR_ZERO EBADF +#else +# define mhd_EBADF_OR_ZERO (0) +#endif + +#ifdef EFAULT +# define mhd_EFAULT_OR_ZERO EFAULT +#else +# define mhd_EFAULT_OR_ZERO (0) +#endif + +#ifdef EINVAL +# define mhd_EINVAL_OR_ZERO EINVAL +#else +# define mhd_EINVAL_OR_ZERO (0) +#endif + +#ifdef EINTR +# define mhd_EINTR_OR_ZERO EINTR +#else +# define mhd_EINTR_OR_ZERO (0) +#endif + +#ifdef ENOMEM +# define mhd_ENOMEM_OR_ZERO ENOMEM +#else +# define mhd_ENOMEM_OR_ZERO (0) +#endif + +#ifdef EMFILE +# define mhd_EMFILE_OR_ZERO EMFILE +#else +# define mhd_EMFILE_OR_ZERO (0) +#endif + +#ifdef ENFILE +# define mhd_ENFILE_OR_ZERO ENFILE +#else +# define mhd_ENFILE_OR_ZERO (0) +#endif + +#ifdef ENOBUFS +# define mhd_ENOBUFS_OR_ZERO ENOBUFS +#else +# define mhd_ENOBUFS_OR_ZERO (0) +#endif + +#ifdef EHOSTUNREACH +# define mhd_EHOSTUNREACH_OR_ZERO EHOSTUNREACH +#else +# define mhd_EHOSTUNREACH_OR_ZERO (0) +#endif + +#ifdef ETIMEDOUT +# define mhd_ETIMEDOUT_OR_ZERO ETIMEDOUT +#else +# define mhd_ETIMEDOUT_OR_ZERO (0) +#endif + +#ifdef ENETUNREACH +# define mhd_ENETUNREACH_OR_ZERO ENETUNREACH +#else +# define mhd_ENETUNREACH_OR_ZERO (0) +#endif + +#endif /* ! MHD_SYS_ERRNO_H */ diff --git a/src/mhd2/sys_file_fd.h b/src/mhd2/sys_file_fd.h @@ -0,0 +1,45 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_file_fd.h + * @brief The system headers for file FD close, read, write functions. + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef SYS_FILE_FD_H +#define SYS_FILE_FD_H 1 + +#include "mhd_sys_options.h" + +#if defined(_WIN32) && ! defined(__CYGWIN__) +# include <io.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#else +# ifdef HAVE_STDLIB_H +# include <stdlib.h> +# endif +# include <stdio.h> +#endif + + +#endif /* ! SYS_FILE_FD_H */ diff --git a/src/mhd2/sys_ip_headers.h b/src/mhd2/sys_ip_headers.h @@ -0,0 +1,115 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_ip_headers.h + * @brief The header for system headers related to TCP/IP + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SYS_IP_HEADERS_H +#define MHD_SYS_IP_HEADERS_H 1 + +#include "mhd_sys_options.h" + +#include "mhd_socket_type.h" +#include "sys_sockets_headers.h" + +#ifdef MHD_POSIX_SOCKETS +# ifdef HAVE_INETLIB_H +# include <inetLib.h> +# endif /* HAVE_INETLIB_H */ +# ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +# endif /* HAVE_NETINET_IN_H */ +# ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +# endif +# if ! defined(HAVE_NETINET_IN_H) && ! defined(HAVE_ARPA_INET_H) \ + && defined(HAVE_NETDB_H) +# include <netdb.h> +# endif +# ifdef HAVE_NETINET_TCP_H +# include <netinet/tcp.h> +# endif +#else +# include <ws2tcpip.h> +#endif + +#ifdef IPPROTO_TCP +# if defined(TCP_CORK) +/** + * Value of TCP_CORK or TCP_NOPUSH + */ +# define mhd_TCP_CORK_NOPUSH TCP_CORK +# elif defined(TCP_NOPUSH) +/** + * Value of TCP_CORK or TCP_NOPUSH + */ +# define mhd_TCP_CORK_NOPUSH TCP_NOPUSH +# endif /* TCP_NOPUSH */ +#endif /* IPPROTO_TCP */ + +#ifdef mhd_TCP_CORK_NOPUSH +# ifdef __linux__ +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + */ +# define mhd_CORK_RESET_PUSH_DATA 1 +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + * even if TCP_CORK/TCP_NOPUSH was in switched off state. + */ +# define mhd_CORK_RESET_PUSH_DATA_ALWAYS 1 +#endif /* __linux__ */ +#if (defined(__FreeBSD__) && \ + ((__FreeBSD__ + 0) >= 5 || (__FreeBSD_version + 0) >= 450000)) || \ + (defined(__FreeBSD_kernel_version) && \ + (__FreeBSD_kernel_version + 0) >= 450000) +/* FreeBSD pushes data to the network with reset of TCP_NOPUSH + * starting from version 4.5. */ +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + */ +#define mhd_CORK_RESET_PUSH_DATA 1 +#endif /* __FreeBSD_version >= 450000 */ +#ifdef __OpenBSD__ +/* OpenBSD took implementation from FreeBSD */ +/** + * Indicate that reset of TCP_CORK / TCP_NOPUSH push data to the network + */ +#define mhd_CORK_RESET_PUSH_DATA 1 +#endif /* __OpenBSD__ */ +#endif /* MHD_TCP_CORK_NOPUSH */ + +#ifdef __linux__ +/** + * Indicate that set of TCP_NODELAY push data to the network + */ +# define mhd_NODELAY_SET_PUSH_DATA 1 +/** + * Indicate that set of TCP_NODELAY push data to the network even + * if TCP_DELAY was already set and regardless of TCP_CORK / TCP_NOPUSH state + */ +# define mhd_NODELAY_SET_PUSH_DATA_ALWAYS 1 +#endif /* __linux__ */ + + +#endif /* ! MHD_SYS_IP_HEADERS_H */ diff --git a/src/mhd2/sys_malloc.h b/src/mhd2/sys_malloc.h @@ -0,0 +1,45 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_malloc.h + * @brief The wrapper header for malloc() and free() system declarations + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SYS_MALLOC_H +#define MHD_SYS_MALLOC_H 1 + +#include "mhd_sys_options.h" + +#if defined(HAVE_STDLIB_H) +# include <stdlib.h> +#elif defined(HAVE_MALLOC_H) +# include <malloc.h> +#else +/* Try some set of headers, hoping the right header is included */ +# if defined(HAVE_UNISTD_H) +# include <unistd.h> +# endif +# include <stdio.h> +# include <string.h> +#endif + +#endif /* ! MHD_SYS_MALLOC_H */ diff --git a/src/mhd2/sys_null_macro.h b/src/mhd2/sys_null_macro.h @@ -0,0 +1,40 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_null_macro.h + * @brief The header for the system NULL constant + * @author Karlson2k (Evgeny Grin) + * + * This header tries to include the minimal header that defines NULL. + */ + +#ifndef MHD_SYS_NULL_MACRO_H +#define MHD_SYS_NULL_MACRO_H 1 + +#include "mhd_sys_options.h" + +#if defined(HAVE_STDDEF_H) +# include <stddef.h> /* NULL */ +#else +# include <string.h> /* should provide NULL */ +#endif + +#endif /* ! MHD_SYS_NULL_MACRO_H */ diff --git a/src/mhd2/sys_poll.h b/src/mhd2/sys_poll.h @@ -0,0 +1,91 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_poll.h + * @brief The header for the system 'poll()' function and related data types + * @author Karlson2k (Evgeny Grin) + * + * This header includes system macros for 'poll()' and also has related + * MHD macros. + */ + +#ifndef MHD_SYS_POLL_H +#define MHD_SYS_POLL_H 1 + +#include "mhd_sys_options.h" + +#ifdef MHD_USE_POLL +# include "mhd_socket_type.h" +# if defined(MHD_POSIX_SOCKETS) +# include <poll.h> +# define mhd_poll poll +# elif defined(MHD_WINSOCK_SOCKETS) +# include <winsock2.h> +# define mhd_poll WSAPoll +# else +#error Uknown sockets type +# endif + +# if (defined(HAVE_DECL_POLLRDNORM) && (0 != HAVE_DECL_POLLRDNORM + 0)) || \ + defined(POLLRDNORM) +# define MHD_POLL_IN POLLRDNORM +# else +# define MHD_POLL_IN POLLIN +# endif + +# if (defined(HAVE_DECL_POLLWRNORM) && (0 != HAVE_DECL_POLLWRNORM + 0)) || \ + defined(POLLWRNORM) +# define MHD_POLL_OUT POLLWRNORM +# else +# define MHD_POLL_OUT POLLOUT +# endif + +# if (defined(HAVE_DECL_POLLRDBAND) && (0 != HAVE_DECL_POLLRDBAND + 0)) || \ + defined(POLLRDBAND) +# define MHD_POLLRDBAND POLLRDBAND +# else +# define MHD_POLLRDBAND (0) +# endif + +# if (defined(HAVE_DECL_POLLWRBAND) && (0 != HAVE_DECL_POLLWRBAND + 0)) || \ + defined(POLLWRBAND) +# define MHD_POLLWRBAND POLLWRBAND +# else +# define MHD_POLLWRBAND (0) +# endif + +# if (defined(HAVE_DECL_POLLPRI) && (0 != HAVE_DECL_POLLPRI + 0)) || \ + defined(POLLWRBAND) +# define MHD_POLLPRI POLLPRI +# else +# define MHD_POLLPRI (0) +# endif + + +# if (defined(__APPLE__) && defined(__MACH__)) || defined(__CYGWIN__) +/* The platform incorrectly sets POLLHUP when remote use SHUT_WR. + The correct behaviour must be POLLHUP only on remote close/disconnect */ +# define MHD_POLLHUP_ON_REM_SHUT_WR 1 +# endif + +#endif /* MHD_USE_POLL */ + +#endif /* ! MHD_SYS_POLL_H */ diff --git a/src/mhd2/sys_select.h b/src/mhd2/sys_select.h @@ -0,0 +1,61 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_select.h + * @brief The header for the system 'select()' function and related data types + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SYS_SELECT_H +#define MHD_SYS_SELECT_H 1 + +#include "mhd_sys_options.h" + +#ifdef MHD_USE_SELECT +# include "mhd_socket_type.h" +# if defined(MHD_POSIX_SOCKETS) +# ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +# else +# ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +# endif +# ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +# ifdef HAVE_UNISTD_H +# include <unistd.h> +# else +# include <stdlib.h> +# endif +# ifdef HAVE_SELECTLIB_H +# include <selectLib.h> +# endif +# endif +# elif defined(MHD_WINSOCK_SOCKETS) +# include <winsock2.h> +# else +#error Uknown sockets type +# endif + +#endif /* MHD_USE_SELECT */ + +#endif /* ! MHD_SYS_SELECT_H */ diff --git a/src/mhd2/sys_sendfile.h b/src/mhd2/sys_sendfile.h @@ -0,0 +1,49 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_sendfile.h + * @brief The system headers for sendfile() function (if any) + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SYS_SENDFILE_H +#define MHD_SYS_SENDFILE_H 1 + +#include "mhd_sys_options.h" + +#if defined(HAVE_LINUX_SENDFILE) +# include <sys/sendfile.h> +#elif defined(HAVE_FREEBSD_SENDFILE) || defined(HAVE_DARWIN_SENDFILE) +# ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +# ifdef HAVE_UNISTD_H +# include <unistd.h> +# endif +# ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +# endif +# include <sys/uio.h> +#elif defined(MHD_USE_SENDFILE) +#error MHD_USE_SENDFILE is defined, while no HAVE_xxx_SENDFILE defined +#endif + +#endif /* ! MHD_SYS_SENDFILE_H */ diff --git a/src/mhd2/sys_sockets_headers.h b/src/mhd2/sys_sockets_headers.h @@ -0,0 +1,165 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_sockets_headers.h + * @brief The header for system headers for the sockets and some basic macros + * @author Karlson2k (Evgeny Grin) + * + * The macros are limited to simple local constants definitions + */ + +#ifndef MHD_SYS_SOCKETS_HEADERS_H +#define MHD_SYS_SOCKETS_HEADERS_H 1 + +#include "mhd_sys_options.h" + +#include "mhd_socket_type.h" + +#ifdef MHD_POSIX_SOCKETS +# include "sys_base_types.h" /* required on old platforms */ +# ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +# endif +# ifdef HAVE_SOCKLIB_H +# include <sockLib.h> +# endif /* HAVE_SOCKLIB_H */ +#elif defined(MHD_WINSOCK_SOCKETS) +# include <winsock2.h> +#endif +#ifdef HAVE_SYS_UN_H +# include <sys/un.h> +#endif + +#if defined(HAVE_SOCK_NONBLOCK) && ! defined(MHD_WINSOCK_SOCKETS) +# define mhd_SOCK_NONBLOCK SOCK_NONBLOCK +#else +# define mhd_SOCK_NONBLOCK (0) +#endif + +#if defined(SOCK_CLOEXEC) && ! defined(MHD_WINSOCK_SOCKETS) +# define mhd_SOCK_CLOEXEC SOCK_CLOEXEC +#else +# define mhd_SOCK_CLOEXEC (0) +#endif + +#if defined(SOCK_NOSIGPIPE) && ! defined(MHD_WINSOCK_SOCKETS) +# define mhd_SOCK_NOSIGPIPE SOCK_NOSIGPIPE +#else +# define mhd_SOCK_NOSIGPIPE (0) +#endif + +#if defined(MSG_NOSIGNAL) && ! defined(MHD_WINSOCK_SOCKETS) +# define mhd_MSG_NOSIGNAL MSG_NOSIGNAL +#else +# define mhd_MSG_NOSIGNAL (0) +#endif + +#ifdef MSG_MORE +# ifdef __linux__ +/* MSG_MORE signal kernel to buffer outbond data and works like + * TCP_CORK per call without actually setting TCP_CORK value. + * It's known to work on Linux. Add more OSes if they are compatible. */ +/** + * Indicate MSG_MORE is usable for buffered send(). + */ +# define mhd_USE_MSG_MORE 1 +# endif /* __linux__ */ +#endif /* MSG_MORE */ + +#ifdef mhd_USE_MSG_MORE +# define mhd_MSG_MORE MSG_MORE +#else +# define mhd_MSG_MORE (0) +#endif + + +/** + * mhd_SCKT_OPT_BOOL is the type for bool parameters + * for setsockopt()/getsockopt() functions + */ +#if defined(MHD_POSIX_SOCKETS) +# define mhd_SCKT_OPT_BOOL int +#elif defined(MHD_WINSOCK_SOCKETS) +# define mhd_SCKT_OPT_BOOL BOOL +#endif /* MHD_WINSOCK_SOCKETS */ + +/** + * mhd_SCKT_SEND_SIZE is type used to specify size for send() and recv() + * functions + */ +#if defined(MHD_POSIX_SOCKETS) +typedef size_t mhd_SCKT_SEND_SIZE; +#elif defined(MHD_WINSOCK_SOCKETS) +typedef int mhd_SCKT_SEND_SIZE; +#endif + +/** + * MHD_SCKT_SEND_MAX_SIZE_ is maximum send()/recv() size value. + */ +#if defined(MHD_POSIX_SOCKETS) +# define MHD_SCKT_SEND_MAX_SIZE_ SSIZE_MAX +#elif defined(MHD_WINSOCK_SOCKETS) +# define MHD_SCKT_SEND_MAX_SIZE_ (0x7FFFFFFF) /* INT_MAX */ +#endif + + +#if defined(AF_UNIX) || \ + (defined(HAVE_DECL_AF_UNIX) && (HAVE_DECL_AF_UNIX + 0 != 0)) +# define MHD_AF_UNIX AF_UNIX +#elif defined(AF_LOCAL) || \ + (defined(HAVE_DECL_AF_LOCAL) && (HAVE_DECL_AF_LOCAL + 0 != 0)) +# define MHD_AF_UNIX AF_LOCAL +#endif /* AF_UNIX */ + + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ + defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(MHD_WINSOCK_SOCKETS) || defined(__MACH__) || defined(__sun) || \ + defined(SOMEBSD) +/* Most of the OSes inherit nonblocking setting from the listen socket */ +# define MHD_ACCEPTED_INHERITS_NONBLOCK 1 +#elif defined(__gnu_linux__) || defined(__linux__) +# define MHD_ACCEPTED_DOES_NOT_INHERIT_NONBLOCK 1 +#endif + + +#if defined(MHD_socket_nosignal_) || \ + (defined(SOL_SOCKET) && defined(SO_NOSIGPIPE)) +/** + * Indicate that SIGPIPE can be suppressed by MHD for normal send() by flags + * or socket options. + * If this macro is undefined, MHD cannot suppress SIGPIPE for socket functions + * so sendfile() or writev() calls are avoided in application threads. + */ +# define mhd_SEND_SPIPE_SUPPRESS_POSSIBLE 1 +#endif /* MHD_WINSOCK_SOCKETS || MHD_socket_nosignal_ || MSG_NOSIGNAL */ + + +#if ! defined(MHD_WINSOCK_SOCKETS) +/** + * Indicate that suppression of SIGPIPE is required for some network + * system calls. + */ +# define mhd_SEND_SPIPE_SUPPRESS_NEEDED 1 +#endif + + +#endif /* ! MHD_SYS_SOCKETS_HEADERS_H */ diff --git a/src/mhd2/sys_sockets_types.h b/src/mhd2/sys_sockets_types.h @@ -0,0 +1,51 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_sockets_types.h + * @brief The header for system types related to sockets + * @author Karlson2k (Evgeny Grin) + * + * This header should provide declaration of 'struct sockaddr' and + * socklen_t (if it is present in headers). + */ + +#ifndef MHD_SYS_SOCKETS_TYPES_H +#define MHD_SYS_SOCKETS_TYPES_H 1 + +#include "mhd_sys_options.h" + +#include "mhd_socket_type.h" + +#ifdef MHD_POSIX_SOCKETS +# ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +# else +# include <sys/types.h> /* bad fallback */ +# ifdef HAVE_SOCKLIB_H +# include <sockLib.h> +# endif +# endif +#else +# include <winsock2.h> +# include <ws2tcpip.h> +#endif + +#endif /* ! MHD_SYS_SOCKETS_TYPES_H */ diff --git a/src/mhd2/sys_thread_entry_type.h b/src/mhd2/sys_thread_entry_type.h @@ -0,0 +1,49 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/sys_thread_entry_type.h + * @brief The type of the thread start routine + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_SYS_THREAD_ENTRY_TYPE_H +#define MHD_SYS_THREAD_ENTRY_TYPE_H 1 + +#include "mhd_sys_options.h" + +#if defined(MHD_USE_POSIX_THREADS) +# define mhd_THRD_RTRN_TYPE void* +# define mhd_THRD_CALL_SPEC +#elif defined(MHD_USE_W32_THREADS) +# define mhd_THRD_RTRN_TYPE unsigned +# define mhd_THRD_CALL_SPEC __stdcall +#endif + +/** + * Signature of the entrance function for a thread. + * + * @param cls the closure argument for the function + * @return the termination code/result from the thread + */ +typedef mhd_THRD_RTRN_TYPE +(mhd_THRD_CALL_SPEC *mhd_THREAD_START_ROUTINE)(void *cls); + +#endif /* ! MHD_SYS_THREAD_ENTRY_TYPE_H */ diff --git a/src/mhd2/w32_lib_res.rc.in b/src/mhd2/w32_lib_res.rc.in @@ -0,0 +1,51 @@ +/* W32 resources for .dll */ + +#include <winresrc.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +VS_VERSION_INFO VERSIONINFO + FILEVERSION @PACKAGE_VERSION_MAJOR@,@PACKAGE_VERSION_MINOR@,@PACKAGE_VERSION_SUBMINOR@,0 + PRODUCTVERSION @PACKAGE_VERSION_MAJOR@,@PACKAGE_VERSION_MINOR@,@PACKAGE_VERSION_SUBMINOR@,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#if defined(_DEBUG) + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0 +#endif + FILEOS VOS_NT_WINDOWS32 +#ifdef DLL_EXPORT + FILETYPE VFT_DLL +#else + FILETYPE VFT_STATIC_LIB +#endif + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "04090000" /* Lang = US English, Charset = ASCII */ + BEGIN + VALUE "ProductName", "GNU libmicrohttpd\0" + VALUE "ProductVersion", "@PACKAGE_VERSION@\0" + VALUE "FileVersion", "@PACKAGE_VERSION@\0" +#ifdef DLL_EXPORT + VALUE "FileDescription", "GNU libmicrohttpd2 DLL for Windows (MinGW build, @W32CRT@ run-time lib)\0" +#else + VALUE "FileDescription", "GNU libmicrohttpd2 static library for Windows (MinGW build, @W32CRT@ run-time lib)\0" +#endif + VALUE "InternalName", "libmicrohttpd2\0" +#ifdef DLL_EXPORT + VALUE "OriginalFilename", "libmicrohttpd2-@MHD_W32_DLL_SUFF@.dll\0" +#else + VALUE "OriginalFilename", "libmicrohttpd2.lib\0" +#endif + VALUE "CompanyName", "Free Software Foundation\0" + VALUE "LegalCopyright", "Copyright (C) 2007-2024 Christian Grothoff, Evgeny Grin, and project contributors\0" + VALUE "Comments", "http://www.gnu.org/software/libmicrohttpd/\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0 /* US English, ASCII */ + END +END + diff --git a/src/tests/.gitignore b/src/tests/.gitignore @@ -0,0 +1,3 @@ +test_*[a-z0-9_][a-z0-9_][a-z0-9_] +!*.c +!*.h diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am @@ -0,0 +1,5 @@ +# This Makefile.am is in the public domain + +SUBDIRS = basic + +.NOTPARALLEL: diff --git a/src/tests/basic/Makefile.am b/src/tests/basic/Makefile.am @@ -0,0 +1,180 @@ +# This Makefile.am is in the public domain +EMPTY_ITEM = + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/mhd2 \ + -DMHD_CPU_COUNT=$(CPU_COUNT) \ + $(CPPFLAGS_ac) + +AM_CFLAGS = $(CFLAGS_ac) + +AM_LDFLAGS = $(LDFLAGS_ac) + +AM_TESTS_ENVIRONMENT = $(TESTS_ENVIRONMENT_ac) + +if USE_COVERAGE + AM_CFLAGS += -fprofile-arcs -ftest-coverage +endif + +LDADD = $(top_builddir)/src/mhd2/libmicrohttpd2.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 + +check_PROGRAMS = \ + test_create_destroy \ + test_create_start_destroy \ + test_create_destroy_ipv4 \ + test_create_start_destroy_ipv4 \ + test_create_destroy_ipv6 \ + test_create_start_destroy_ipv6_ipv4 \ + test_create_destroy_ipbest \ + test_create_start_destroy_ipbest \ + $(EMPTY_ITEM) + +if MHD_USE_SELECT +check_PROGRAMS += \ + test_create_destroy_select \ + test_create_start_destroy_select \ + test_create_destroy_select_ipv4 \ + test_create_start_destroy_select_ipv4 \ + $(EMPTY_ITEM) +endif + +if MHD_USE_POLL +check_PROGRAMS += \ + test_create_destroy_poll \ + test_create_start_destroy_poll \ + test_create_destroy_poll_ipv4 \ + test_create_start_destroy_poll_ipv4 \ + $(EMPTY_ITEM) +endif + +if MHD_USE_EPOLL +check_PROGRAMS += \ + test_create_destroy_epoll \ + test_create_start_destroy_epoll \ + test_create_destroy_epoll_ipv4 \ + test_create_start_destroy_epoll_ipv4 \ + $(EMPTY_ITEM) +endif + +if MHD_USE_THREADS +check_PROGRAMS += \ + test_create_destroy_int_thread \ + test_create_start_destroy_int_thread \ + test_create_start_destroy_int_thread_ipv4 \ + test_create_destroy_thread_per_conn \ + test_create_start_destroy_thread_per_conn \ + test_create_start_destroy_thread_per_conn_ipv4 \ + test_create_destroy_thread_pool \ + test_create_start_destroy_thread_pool \ + test_create_start_destroy_thread_pool_ipv4 \ + $(EMPTY_ITEM) + +if MHD_USE_SELECT +check_PROGRAMS += \ + test_create_start_destroy_select_int_thread_ipv4 \ + test_create_start_destroy_select_thread_per_conn_ipv4 \ + $(EMPTY_ITEM) +endif + +if MHD_USE_POLL +check_PROGRAMS += \ + test_create_start_destroy_poll_int_thread_ipv4 \ + test_create_start_destroy_poll_thread_per_conn_ipv4 \ + $(EMPTY_ITEM) +endif + +if MHD_USE_EPOLL +check_PROGRAMS += \ + test_create_start_destroy_epoll_int_thread_ipv4 \ + test_create_start_destroy_epoll_thread_pool_ipv4 \ + $(EMPTY_ITEM) +endif + +endif + +if USE_IPV6_TESTING +check_PROGRAMS += \ + test_create_start_destroy_ipv6 +endif + +TESTS = $(check_PROGRAMS) + +# The universal sources used in all tests +basic_test_sources = test_basic_checks.c + +test_create_destroy_SOURCES = $(basic_test_sources) + +test_create_start_destroy_SOURCES = $(basic_test_sources) + +test_create_destroy_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_ipv4_SOURCES = $(basic_test_sources) + +test_create_destroy_ipv6_SOURCES = $(basic_test_sources) + +test_create_start_destroy_ipv6_SOURCES = $(basic_test_sources) + +test_create_start_destroy_ipv6_ipv4_SOURCES = $(basic_test_sources) + +test_create_destroy_ipbest_SOURCES = $(basic_test_sources) + +test_create_start_destroy_ipbest_SOURCES = $(basic_test_sources) + +test_create_destroy_select_SOURCES = $(basic_test_sources) + +test_create_start_destroy_select_SOURCES = $(basic_test_sources) + +test_create_destroy_select_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_select_ipv4_SOURCES = $(basic_test_sources) + +test_create_destroy_poll_SOURCES = $(basic_test_sources) + +test_create_start_destroy_poll_SOURCES = $(basic_test_sources) + +test_create_destroy_poll_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_poll_ipv4_SOURCES = $(basic_test_sources) + +test_create_destroy_epoll_SOURCES = $(basic_test_sources) + +test_create_start_destroy_epoll_SOURCES = $(basic_test_sources) + +test_create_destroy_epoll_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_epoll_ipv4_SOURCES = $(basic_test_sources) + +test_create_destroy_int_thread_SOURCES = $(basic_test_sources) + +test_create_start_destroy_int_thread_SOURCES = $(basic_test_sources) + +test_create_start_destroy_int_thread_ipv4_SOURCES = $(basic_test_sources) + +test_create_destroy_thread_per_conn_SOURCES = $(basic_test_sources) + +test_create_start_destroy_thread_per_conn_SOURCES = $(basic_test_sources) + +test_create_start_destroy_thread_per_conn_ipv4_SOURCES = $(basic_test_sources) + +test_create_destroy_thread_pool_SOURCES = $(basic_test_sources) + +test_create_start_destroy_thread_pool_SOURCES = $(basic_test_sources) + +test_create_start_destroy_thread_pool_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_select_int_thread_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_select_thread_per_conn_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_poll_int_thread_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_poll_thread_per_conn_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_epoll_int_thread_ipv4_SOURCES = $(basic_test_sources) + +test_create_start_destroy_epoll_thread_pool_ipv4_SOURCES = $(basic_test_sources) diff --git a/src/tests/basic/test_basic_checks.c b/src/tests/basic/test_basic_checks.c @@ -0,0 +1,362 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2016, 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 test_basic_checks.c + * @brief test for create, start and destroy + * @author Karlson2k (Evgeny Grin) + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <microhttpd2.h> + +/* Helper macros */ + +#define ERR_PRINT_LINE() \ + ((void) fprintf (stderr, "At the line number %u: ", \ + (unsigned) __LINE__)) + +/** + * Check whether SC code is OK, print error if not. + * @warning Do not use function call in the argument + * @param sc the status code to check + */ +#define tst_EXPECT_OK(sc) \ + ( (MHD_SC_OK == (sc)) ? (! 0) : \ + (ERR_PRINT_LINE (), \ + ((void) fprintf (stderr, "MHD function failed, returned: %u\n", \ + (unsigned int) (sc))), (0)) ) +#if 0 +#define tst_EXPECT_OK(sc) \ + ( (MHD_SC_OK == (sc)) ? (! 0) : \ + (ERR_PRINT_LINE (), \ + ((void) fprintf (stderr, \ + "MHD function failed, returned: %s\n", \ + MHD_status_code_to_string_lazy (sc))), (0)) ) +#endif + +/** + * Check whether SC code is OK, print error if not. + * @warning Do not use function call in the argument + * @param sc the status code to check + */ +#define tst_EXPECT_FAIL(sc) \ + ( (MHD_SC_OK != (sc)) ? (! 0) : \ + (ERR_PRINT_LINE (), \ + ((void) fprintf (stderr, "MHD function unexpectedly succeed.\n")), \ + (0)) ) + +/** + * Check whether SC code is success/failure as expected, print error if not. + * @warning Do not use function call in the argument + * @param sc the status code to check + * @param expect_ok non-zero if SC should be OK, zero is SC should NOT be OK + */ +#define tst_EXPECT_CHECK(sc,expect_ok) \ + ((expect_ok) ? tst_EXPECT_OK ((sc)) : tst_EXPECT_FAIL ((sc))) + +/* Helper functions */ + +/** + * Check whether program name contains specific @a marker string. + * Only last component in pathname is checked for marker presence, + * all leading directories names (if any) are ignored. Directories + * separators are handled correctly on both non-W32 and W32 + * platforms. + * @param prog_name the program name, may include path + * @param marker the marker to look for + * @return zero if any parameter is NULL or empty string or + * @a prog_name ends with slash or @a marker is not found in + * program name, non-zero if @a maker is found in program + * name. + */ +static int +has_in_name (const char *prog_name, const char *marker) +{ + const char *s; + const char *basename; + + if (! prog_name || ! marker || ! prog_name[0] || ! marker[0]) + return 0; + + basename = prog_name; + for (s = prog_name; *s; ++s) + { + if ('/' == *s) + basename = s + 1; +#if defined(_WIN32) || defined(__CYGWIN__) + else if ('\\' == *s) + basename = s + 1; +#endif /* _WIN32 || __CYGWIN__ */ + } + + return strstr (basename, marker) != NULL; +} + + +/* The test */ + +static int use_start = 0; + +static int use_ipv4 = 0; + +static int use_ipv6 = 0; + +static int use_ip_best = 0; + +static int use_select = 0; + +static int use_poll = 0; + +static int use_epoll = 0; + +static int use_int_thread = 0; + +static int use_thread_per_conn = 0; + +static int use_thread_pool = 0; + +/* Dynamic run-time variables */ + +static int err_flag = 0; + +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) static const struct MHD_Action * +my_req_process (void *cls, + struct MHD_Request *request, + const struct MHD_String *path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + (void) cls; (void) request; (void) path; (void) method; (void) upload_size; + fprintf (stderr, "Unexpected call of the request callback.\n"); + err_flag = ! 0; + return NULL; +} + + +static struct MHD_Daemon * +test_daemon_create (void) +{ + struct MHD_Daemon *d; + + d = MHD_daemon_create (my_req_process, NULL); + if (NULL == d) + { + err_flag = ! 0; + ERR_PRINT_LINE (); + fprintf (stderr, "MHD_daemon_create() failed, NULL returned.\n"); + return NULL; + } + return d; +} + + +static int +test_daemon_setup (struct MHD_Daemon *d, + int should_succeed) +{ + enum MHD_StatusCode sc; + int ret = ! 0; + + if (use_ipv6) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_BIND_PORT (MHD_AF_DUAL_v4_OPTIONAL, 0)); + if (! tst_EXPECT_CHECK (sc,should_succeed)) + ret = 0; + } + + if (use_ipv4) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_BIND_PORT (MHD_AF_DUAL_v6_OPTIONAL, 0)); + if (! tst_EXPECT_CHECK (sc,should_succeed)) + ret = 0; + } + + if (use_ip_best) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 0)); + if (! tst_EXPECT_CHECK (sc,should_succeed)) + ret = 0; + } + + if (use_select) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_SELECT)); + if (! tst_EXPECT_CHECK (sc,should_succeed)) + ret = 0; + } + + if (use_poll) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_POLL)); + if (! tst_EXPECT_CHECK (sc, should_succeed)) + ret = 0; + } + + if (use_epoll) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_EPOLL)); + if (! tst_EXPECT_CHECK (sc,should_succeed)) + ret = 0; + } + + if (use_int_thread) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_WORK_MODE (MHD_WM_OPTION_WORKER_THREADS (1))); + if (! tst_EXPECT_CHECK (sc, should_succeed)) + ret = 0; + } + + if (use_thread_per_conn) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_WORK_MODE (MHD_WM_OPTION_THREAD_PER_CONNECTION ())); + if (! tst_EXPECT_CHECK (sc, should_succeed)) + ret = 0; + } + + if (use_thread_pool) + { + sc = MHD_DAEMON_SET_OPTIONS ( \ + d, MHD_D_OPTION_WORK_MODE (MHD_WM_OPTION_WORKER_THREADS (4))); + if (! tst_EXPECT_CHECK (sc, should_succeed)) + ret = 0; + } + + if (! ret) + err_flag = ! 0; + + return ret; +} + + +static int +test_daemon_start (struct MHD_Daemon *d, + int should_succeed) +{ + enum MHD_StatusCode sc; + + sc = MHD_daemon_start (d); + if (! tst_EXPECT_CHECK (sc,should_succeed)) + { + err_flag = ! 0; + return 0; + } + + return ! 0; +} + + +static int +test_simple (void) +{ + struct MHD_Daemon *d; + int ret = ! 0; + + err_flag = 0; + + d = test_daemon_create (); + if (NULL == d) + return (ret && ! err_flag); + + test_daemon_setup (d, ! 0); + if (use_start) + test_daemon_start (d, ! 0); + + test_daemon_setup (d, ! use_start); + + if (use_start) + test_daemon_start (d, 0); /* Second "start" should fail */ + + MHD_daemon_destroy (d); + + return (ret && ! err_flag); +} + + +/** + * Initialise the test data + * @param prog_name the name of the this program + * @return non-zero if succeed, + * zero if failed + */ +static int +init_test (const char *prog_name) +{ + if (has_in_name (prog_name, "_start")) + use_start = ! 0; + + if (has_in_name (prog_name, "_ipv4")) + use_ipv4 = ! 0; + + if (has_in_name (prog_name, "_ipv6")) + use_ipv6 = ! 0; + + if (has_in_name (prog_name, "_ipbest")) + use_ip_best = ! 0; + + use_select = has_in_name (prog_name, "_select"); + + use_poll = has_in_name (prog_name, "_poll"); + + use_epoll = has_in_name (prog_name, "_epoll"); + + use_int_thread = has_in_name (prog_name, "_int_thread"); + + use_thread_per_conn = has_in_name (prog_name, "_thread_per_conn"); + + use_thread_pool = has_in_name (prog_name, "_thread_pool"); + + return ! 0; +} + + +int +main (int argc, char *argv[]) +{ + unsigned int num_err = 0; + (void) argc; /* Unused. Silence compiler warning. */ + + if (! init_test (argv[0])) + { + fprintf (stderr, "Failed to initialise the test!\n"); + return 77; + } + + if (! test_simple ()) + ++num_err; + + if (0 != num_err) + { + fprintf (stderr, "Number of failed checks: %u\n", num_err); + return 2; + } + + printf ("All checks succeed.\n"); + return 0; +}