libmicrohttpd2

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

commit c803eebd067b3e025cfa0da21712caf642ab73bc
parent dc426594a4224def23567add24ca9022204272ad
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Sun,  2 Nov 2025 19:41:33 +0100

HTTP/2: basic implementation, Implemented URI normalizer and HTTP/2 URI parameters parsing

Diffstat:
Msrc/examples2/demo.c | 2+-
Msrc/examples2/minimal_example2.c | 6++++++
Msrc/mhd2/Makefile.am | 41++++++++++++++++++++++++++++++++++-------
Msrc/mhd2/action.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/mhd2/conn_data_process.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/mhd2/conn_data_send.c | 15+++++++++++++++
Msrc/mhd2/conn_tls_check.c | 16++++++++--------
Msrc/mhd2/conn_tls_check.h | 31+++++--------------------------
Msrc/mhd2/daemon_add_conn.c | 20++++++++++++++++++++
Msrc/mhd2/daemon_start.c | 7+++++++
Msrc/mhd2/events_process.c | 13++++++++++++-
Asrc/mhd2/h2/h2_action.c | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_action.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_app_cb.c | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_app_cb.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_bit_masks.h | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_comm.c | 606+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_comm.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_conn_data.h | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_conn_streams.c | 710+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_conn_streams.h | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_err_codes.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_frame_codec.c | 1462+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_frame_codec.h | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_frame_init.h | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_frame_length.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_frame_types.h | 275+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_conn.c | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_conn.h | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_in.c | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_in.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_out.c | 267+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_out.h | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_settings.c | 323+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_proc_settings.h | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_reply_funcs.c | 599+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_reply_funcs.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_data.h | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_fields.c | 523+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_fields.h | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_get_items.c | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_get_items.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_item_kinds.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_item_struct.h | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_items_funcs.c | 498+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_req_items_funcs.h | 222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_resp_data.h | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_settings.h | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/h2_stream_data.h | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/h2/hpack/mhd_hpack_codec.c | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/mhd2/h2/hpack/mhd_hpack_codec.h | 28+++++++++++++++++++++++-----
Msrc/mhd2/h2/hpack/mhd_hpack_enc_types.h | 21++++++++++++++-------
Msrc/mhd2/mhd_action.h | 17+++++++++++++++++
Asrc/mhd2/mhd_comm_layer_state.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_connection.h | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/mhd2/mhd_daemon.h | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_http_layer_state.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_lib_init.c | 6++++++
Msrc/mhd2/mhd_request.h | 42+++++++++++++++++++++++++++---------------
Msrc/mhd2/mhd_response.h | 11+++++++++++
Msrc/mhd2/mhd_str.c | 368+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/mhd2/mhd_str.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_stream.h | 13++++++++++---
Msrc/mhd2/mhd_tls_enums.h | 20++++++++++++++++++++
Msrc/mhd2/mhd_tls_funcs.h | 7+++++++
Msrc/mhd2/request_get_value.c | 21+++++++++++++++++++++
Msrc/mhd2/response_add_header.c | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/mhd2/stream_funcs.c | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/mhd2/stream_funcs.h | 25+++++++++++++++++++++++++
Msrc/mhd2/stream_process_reply.c | 15+++------------
Msrc/mhd2/stream_process_reply.h | 13++++++++++++-
Msrc/mhd2/stream_process_request.c | 102++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/mhd2/stream_process_request.h | 12++++++++++++
Msrc/mhd2/stream_process_states.c | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/mhd2/tls_gnu_daemon_data.h | 14++++++++++++++
Msrc/mhd2/tls_gnu_funcs.c | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/mhd2/tls_gnu_funcs.h | 10++++++++++
Msrc/mhd2/tls_multi_funcs.c | 28++++++++++++++++++++++++++++
Msrc/mhd2/tls_multi_funcs.h | 9+++++++++
Msrc/mhd2/tls_open_daemon_data.h | 10++++++++++
Msrc/mhd2/tls_open_funcs.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/mhd2/tls_open_funcs.h | 9+++++++++
82 files changed, 11201 insertions(+), 309 deletions(-)

diff --git a/src/examples2/demo.c b/src/examples2/demo.c @@ -969,7 +969,7 @@ done_cb (struct MHD_Request *req, } /* create directories -- if they don't exist already */ #if ! defined(_WIN32) || defined(__CYGWIN__) - (void) mkdir (lang->cstr, + (void) mkdir (lang.cstr, S_IRWXU); #else (void) mkdir (lang.cstr); diff --git a/src/examples2/minimal_example2.c b/src/examples2/minimal_example2.c @@ -39,6 +39,12 @@ req_cb (void *cls, (void) method; (void) upload_size; /* Unused */ + struct MHD_StringNullable val; + MHD_request_get_value (request, + MHD_VK_URI_QUERY_PARAM, + "abc", + &val); + return MHD_action_from_response ( request, MHD_response_from_buffer_static ( diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am @@ -35,8 +35,8 @@ libmicrohttpd2_la_SOURCES = \ compat_calloc.h \ sys_w32_ver.h \ mhd_align.h mhd_bithelpers.h mhd_byteorder.h \ - mhd_constexpr.h mhd_assert.h mhd_unreachable.h \ - mhd_predict.h \ + mhd_assert.h mhd_assume.h mhd_unreachable.h \ + mhd_constexpr.h mhd_predict.h \ mhd_cntnr_ptr.h mhd_arr_num_elems.h \ mhd_tristate.h mhd_status_code_int.h \ mhd_socket_type.h mhd_sockets_macros.h \ @@ -47,6 +47,7 @@ libmicrohttpd2_la_SOURCES = \ mhd_str.c mhd_str.h \ mhd_str_macros.h mhd_str_types.h \ mhd_buffer.h \ + mhd_comm_layer_state.h \ mhd_limits.h \ mhd_iovec.h \ mhd_dbg_print.h \ @@ -107,9 +108,34 @@ libmicrohttpd2_la_SOURCES += \ compat_calloc.c endif +# httptwo_OPTSOURCES = \ + mhd_http_layer_state.h \ + h2/h2_bit_masks.h \ + h2/h2_req_data.h \ + h2/h2_stream_data.h \ + h2/h2_resp_data.h \ + h2/h2_conn_data.h h2/h2_err_codes.h h2/h2_frame_types.h \ + h2/h2_frame_length.h h2/h2_frame_init.h \ + h2/h2_frame_codec.c h2/h2_frame_codec.h \ + h2/h2_proc_settings.c h2/h2_proc_settings.h h2/h2_settings.h \ + h2/h2_req_item_kinds.h h2/h2_req_item_struct.h \ + h2/h2_req_items_funcs.c h2/h2_req_items_funcs.h \ + h2/h2_comm.c h2/h2_comm.h \ + h2/h2_proc_conn.c h2/h2_proc_conn.h \ + h2/h2_proc_in.c h2/h2_proc_in.h \ + h2/h2_proc_out.c h2/h2_proc_out.h \ + h2/h2_conn_streams.c h2/h2_conn_streams.h \ + h2/h2_req_fields.c h2/h2_req_fields.h \ + h2/h2_req_get_items.c h2/h2_req_get_items.h \ + h2/h2_app_cb.c h2/h2_app_cb.h \ + h2/h2_action.c h2/h2_action.h \ + h2/h2_reply_funcs.c h2/h2_reply_funcs.h \ + h2/hpack/h2_huffman_est.h \ h2/hpack/h2_huffman_codec.c \ h2/hpack/h2_huffman_codec.h \ + h2/hpack/mhd_hpack_dec_types.h \ + h2/hpack/mhd_hpack_enc_types.h \ h2/hpack/mhd_hpack_codec.c \ h2/hpack/mhd_hpack_codec.h @@ -316,11 +342,12 @@ $(builddir)/../incl_priv/config/mhd_config.h.in: $(top_srcdir)/configure.ac check-sources-missing: @$(am__cd) "$(srcdir)" && \ - echo $(ECHO_N) "Checking for missing sources" ; \ - res="" ; \ - listed_srcs='$(DIST_SOURCES)' ; \ - for src in *.[ch] ; do \ - case " $$listed_srcs " in \ + echo $(ECHO_N) "Checking for sources missing in Makefile" ; \ + res="" && \ + makefile_srcs='$(DIST_SOURCES)' && \ + fs_srcs="*.[ch] h2/*.[ch] h2/hpack/*.[ch]" && \ + for src in $$fs_srcs ; do \ + case " $$makefile_srcs " in \ *" $$src "*) echo $(ECHO_N) "." ;; \ *) res="$$res $$src" ;; \ esac ; \ diff --git a/src/mhd2/action.c b/src/mhd2/action.c @@ -59,6 +59,9 @@ #ifdef MHD_SUPPORT_UPGRADE # include "upgrade_prep.h" #endif +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_action.h" +#endif #include "mhd_public_api.h" @@ -67,7 +70,8 @@ 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); + struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request); + if (mhd_ACTION_NO_ACTION != head_act->act) return (const struct MHD_Action *) NULL; @@ -81,13 +85,26 @@ MHD_FN_PAR_NONNULL_ (1) const struct MHD_Action * MHD_action_from_response (struct MHD_Request *MHD_RESTRICT request, struct MHD_Response *MHD_RESTRICT response) { - struct MHD_Action *const restrict head_act = &(request->app_act.head_act); + struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request); if (NULL == response) return (const struct MHD_Action *) NULL; mhd_response_check_frozen_freeze (response); mhd_response_inc_use_count (response); +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_REQ_IS_HTTP2 (request)) + { + if (! mhd_h2_act_is_resp_h2_compatible ((struct mhd_H2RequestData*) request, + response)) + { + /* Clean-up unused response */ + mhd_response_dec_use_count (response); + return (const struct MHD_Action *) NULL; + } + } +#endif /* MHD_SUPPORT_HTTP2 */ + if (mhd_ACTION_NO_ACTION != head_act->act) { /* Clean-up unused response */ @@ -95,12 +112,14 @@ MHD_action_from_response (struct MHD_Request *MHD_RESTRICT request, return (const struct MHD_Action *) NULL; } #ifdef MHD_SUPPORT_AUTH_DIGEST - if (mhd_RESP_HAD_AUTH_DIGEST (response) && + if (mhd_RESP_HAS_AUTH_DIGEST (response) && ! mhd_D_HAS_AUTH_DIGEST ( \ mhd_CNTNR_CPTR (request, struct MHD_Connection, rq)->daemon)) { /* Clean-up unused response */ mhd_response_dec_use_count (response); + // TODO: get connection pointer for HTTP/2 + mhd_assert (! mhd_REQ_IS_HTTP2 (request)); mhd_LOG_MSG (mhd_CNTNR_PTR (request, struct MHD_Connection, rq)->daemon, \ MHD_SC_AUTH_DIGEST_UNSUPPORTED, \ "Attempted to use a response with Digest Auth challenge on " \ @@ -125,9 +144,11 @@ MHD_action_process_upload (struct MHD_Request *request, MHD_UploadCallback uc_inc, void *uc_inc_cls) { - struct MHD_Action *const restrict head_act = &(request->app_act.head_act); + struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request); + if (mhd_ACTION_NO_ACTION != head_act->act) return (const struct MHD_Action *) NULL; + if (0 == large_buffer_size) { if (NULL != uc_full) @@ -164,10 +185,11 @@ MHD_action_parse_post (struct MHD_Request *request, void *done_cb_cls) { #ifdef MHD_SUPPORT_POST_PARSER - struct MHD_Action *const restrict head_act = - &(request->app_act.head_act); + struct MHD_Action *const head_act = mhd_REQ_GET_ACT_HEAD (request); + if (mhd_ACTION_NO_ACTION != head_act->act) return (const struct MHD_Action *) NULL; + if (NULL == done_cb) return (const struct MHD_Action *) NULL; @@ -205,6 +227,9 @@ MHD_action_upgrade (struct MHD_Request *MHD_RESTRICT request, { struct MHD_Action *const restrict head_act = &(request->app_act.head_act); + + if (mhd_REQ_IS_HTTP2 (request)) + return (const struct MHD_Action *) NULL; if (mhd_ACTION_NO_ACTION != head_act->act) return (const struct MHD_Action *) NULL; if (NULL == upgrade_handler) @@ -241,6 +266,9 @@ MHD_upload_action_upgrade ( { struct MHD_UploadAction *const restrict upl_act = &(request->app_act.upl_act); + + if (mhd_REQ_IS_HTTP2 (request)) + return (const struct MHD_UploadAction *) NULL; if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) return (const struct MHD_UploadAction *) NULL; if (NULL == upgrade_handler) @@ -270,8 +298,7 @@ MHD_EXTERN_ 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); + struct MHD_UploadAction *const upl_act = mhd_REQ_GET_ACT_UPLD (request); if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) return (const struct MHD_UploadAction *) NULL; @@ -286,14 +313,26 @@ MHD_FN_PAR_NONNULL_ (1) const struct MHD_UploadAction * MHD_upload_action_from_response (struct MHD_Request *MHD_RESTRICT request, struct MHD_Response *MHD_RESTRICT response) { - struct MHD_UploadAction *const restrict upl_act = - &(request->app_act.upl_act); + struct MHD_UploadAction *const upl_act = mhd_REQ_GET_ACT_UPLD (request); if (NULL == response) return (const struct MHD_UploadAction *) NULL; mhd_response_check_frozen_freeze (response); mhd_response_inc_use_count (response); +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_REQ_IS_HTTP2 (request)) + { + if (! mhd_h2_act_is_resp_h2_compatible ((struct mhd_H2RequestData*) request, + response)) + { + /* Clean-up unused response */ + mhd_response_dec_use_count (response); + return (const struct MHD_UploadAction *) NULL; + } + } +#endif /* MHD_SUPPORT_HTTP2 */ + if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) { /* Clean-up unused response */ @@ -307,6 +346,8 @@ MHD_upload_action_from_response (struct MHD_Request *MHD_RESTRICT request, { /* Clean-up unused response */ mhd_response_dec_use_count (response); + // TODO: get connection pointer for HTTP/2 + mhd_assert (! mhd_REQ_IS_HTTP2 (request)); mhd_LOG_MSG (mhd_CNTNR_PTR (request, struct MHD_Connection, rq)->daemon, \ MHD_SC_AUTH_DIGEST_UNSUPPORTED, \ "Attempted to use a response with Digest Auth challenge on " \ @@ -326,8 +367,7 @@ MHD_EXTERN_ MHD_FN_PAR_NONNULL_ (1) const struct MHD_UploadAction * MHD_upload_action_continue (struct MHD_Request *request) { - struct MHD_UploadAction *const restrict upl_act = - &(request->app_act.upl_act); + struct MHD_UploadAction *const upl_act = mhd_REQ_GET_ACT_UPLD (request); if (mhd_UPLOAD_ACTION_NO_ACTION != upl_act->act) return (const struct MHD_UploadAction *) NULL; diff --git a/src/mhd2/conn_data_process.c b/src/mhd2/conn_data_process.c @@ -55,20 +55,38 @@ #include "mhd_assert.h" #include "mhd_unreachable.h" +#include "mhd_constexpr.h" + +#include <string.h> #include "mhd_daemon.h" #include "mhd_connection.h" #include "daemon_logger.h" +#include "mhd_comm_layer_state.h" +#ifdef MHD_SUPPORT_HTTPS +# include "conn_tls_check.h" +#endif /* MHD_SUPPORT_HTTPS */ + #include "conn_data_recv.h" #include "conn_data_send.h" #include "stream_process_states.h" +#include "mhd_comm_layer_state.h" + +mhd_static_inline enum mhd_CommLayerState +process_conn_layer (struct MHD_Connection *restrict c) +{ #ifdef MHD_SUPPORT_HTTPS -# include "conn_tls_check.h" + if (mhd_C_HAS_TLS (c)) + return mhd_conn_tls_check (c); #endif /* MHD_SUPPORT_HTTPS */ + return mhd_COMM_LAYER_OK; +} + + MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) { @@ -88,24 +106,18 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) data_processed = true; } -#ifdef MHD_SUPPORT_HTTPS - if (mhd_C_HAS_TLS (c)) + switch (process_conn_layer (c)) { - switch (mhd_conn_tls_check (c)) - { - case mhd_CONN_TLS_CHECK_OK: - break; /* Process HTTP data */ - case mhd_CONN_TLS_CHECK_HANDSHAKING: - return true; /* TLS is not yet ready */ - case mhd_CONN_TLS_CHECK_BROKEN: - return false; /* Connection is broken */ - default: - mhd_assert (0 && "Impossible value"); - mhd_UNREACHABLE (); - break; - } + case mhd_COMM_LAYER_OK: + break; /* Connected, the data */ + case mhd_COMM_LAYER_PROCESSING: + return true; /* Not yet fully connected, too early for the data */ + case mhd_COMM_LAYER_BROKEN: + return false; /* Connection is broken */ + default: + mhd_UNREACHABLE (); + return false; } -#endif /* MHD_SUPPORT_HTTPS */ /* The "send-ready" state is known if system polling call is edge-triggered (it always checks for both send- and recv-ready) or if connection needs @@ -128,9 +140,12 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) if (use_recv) { mhd_conn_data_recv (c, has_sock_err); - if (! mhd_conn_process_data (c)) - return false; - data_processed = true; + if (! mhd_C_IS_HTTP2 (c)) + { + if (! mhd_conn_process_data (c)) + return false; + data_processed = true; + } } } @@ -146,21 +161,30 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) 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.props.is_nonblck); - use_send = use_send || - (has_sock_err && c->sk.props.is_nonblck); + if (! mhd_C_IS_HTTP2 (c)) + { + use_send = use_send || + (data_processed && (! send_ready_state_known) + && c->sk.props.is_nonblck); + use_send = use_send || + (has_sock_err && c->sk.props.is_nonblck); + } if (use_send) { mhd_conn_data_send (c); - if (! mhd_conn_process_data (c)) - return false; - data_processed = true; + if (! mhd_C_IS_HTTP2 (c)) + { + if (! mhd_conn_process_data (c)) + return false; + data_processed = true; + } } } - if (! data_processed) + + if (! data_processed || + mhd_C_IS_HTTP2 (c)) return mhd_conn_process_data (c); + return true; } diff --git a/src/mhd2/conn_data_send.c b/src/mhd2/conn_data_send.c @@ -107,6 +107,21 @@ mhd_conn_data_send (struct MHD_Connection *restrict c) res = mhd_SOCKET_ERR_INTERNAL; /* Mute compiler warning */ mhd_assert (mhd_SOCKET_ERR_INTERNAL == res); /* Mute analyser warning */ +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_C_IS_HTTP2 (c)) + { + 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; + } + else +#endif /* MHD_SUPPORT_HTTP2 */ switch (c->stage) { case mhd_HTTP_STAGE_CONTINUE_SENDING: diff --git a/src/mhd2/conn_tls_check.c b/src/mhd2/conn_tls_check.c @@ -62,7 +62,7 @@ #include "stream_process_states.h" -MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_ConnTlsCheckResult +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_CommLayerState mhd_conn_tls_check (struct MHD_Connection *restrict c) { mhd_assert (mhd_C_HAS_TLS (c)); @@ -72,7 +72,7 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c) (mhd_CONN_STATE_TLS_CONNECTED == c->conn_state)); if (mhd_CONN_STATE_TLS_CONNECTED == c->conn_state) - return mhd_CONN_TLS_CHECK_OK; /* TLS is already connected */ + return mhd_COMM_LAYER_OK; /* TLS is already connected */ if (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk.ready)) { @@ -80,13 +80,13 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c) if (mhd_SOCKET_ERR_NO_ERROR == c->sk.state.discnt_err) c->sk.state.discnt_err = mhd_socket_error_get_from_socket (c->sk.fd); mhd_conn_start_closing_skt_err (c); - return mhd_CONN_TLS_CHECK_BROKEN; + return mhd_COMM_LAYER_BROKEN; } /* Check whether the socket is ready for the required send/recv operation */ if (0 == (((mhd_CONN_FLAG_RECV | mhd_CONN_FLAG_SEND) & ((unsigned int) c->conn_state) & ((unsigned int) c->sk.ready)))) - return mhd_CONN_TLS_CHECK_HANDSHAKING; + return mhd_COMM_LAYER_PROCESSING; switch (mhd_tls_conn_handshake (c->tls)) { @@ -107,7 +107,7 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c) Some early application-level data could be processing in this round. */ mhd_conn_event_loop_state_update (c); - return mhd_CONN_TLS_CHECK_OK; + return mhd_COMM_LAYER_OK; break; case mhd_TLS_PROCED_RECV_MORE_NEEDED: c->sk.ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ @@ -138,14 +138,14 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c) "Failed to perform TLS handshake on the new connection"); c->sk.state.discnt_err = mhd_SOCKET_ERR_TLS; mhd_conn_start_closing_skt_err (c); - return mhd_CONN_TLS_CHECK_BROKEN; + return mhd_COMM_LAYER_BROKEN; break; default: mhd_assert (0 && "Should be unreachable"); mhd_UNREACHABLE (); - return mhd_CONN_TLS_CHECK_BROKEN; + return mhd_COMM_LAYER_BROKEN; } mhd_conn_mark_ready_update (c); - return mhd_CONN_TLS_CHECK_HANDSHAKING; + return mhd_COMM_LAYER_PROCESSING; } diff --git a/src/mhd2/conn_tls_check.h b/src/mhd2/conn_tls_check.h @@ -49,40 +49,19 @@ #include "sys_bool_type.h" -struct MHD_Connection; /* forward declaration */ - -/** - * The results of connection TLS checking - */ -enum mhd_ConnTlsCheckResult -{ - /** - * The TLS layer is connected, the communication over TLS can be performed - */ - mhd_CONN_TLS_CHECK_OK = 0 - , - /** - * The TLS layer connection is in progress. - * Communication over TLS is not possible yet. - */ - mhd_CONN_TLS_CHECK_HANDSHAKING - , - /** - * The connection is broken and must be closed - */ - mhd_CONN_TLS_CHECK_BROKEN -}; +#include "mhd_comm_layer_state.h" +struct MHD_Connection; /* forward declaration */ /** * Check connection TLS status, perform TLS (re-)handshake if necessary, * update connection's recv()/send() event loop state and connection active * state if network operation has been performed. * @param c the connection to process - * @return #mhd_CONN_TLS_CHECK_OK if the connection can be used, - * other enum mhd_ConnTlsCheckResult values otherwise + * @return #mhd_COMM_LAYER_OK if the connection can be used, + * other enum mhd_CommLayerState values otherwise */ -MHD_INTERNAL enum mhd_ConnTlsCheckResult +MHD_INTERNAL enum mhd_CommLayerState mhd_conn_tls_check (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c @@ -89,6 +89,9 @@ #include "response_destroy.h" #include "conn_mark_ready.h" +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_comm.h" +#endif /* MHD_SUPPORT_HTTP2 */ #ifdef MHD_SUPPORT_HTTPS # include "mhd_tls_funcs.h" #endif @@ -97,6 +100,18 @@ #include "daemon_add_conn.h" +#ifdef MHD_SUPPORT_HTTP2 +static void +connection_set_http_layer_init_state (struct MHD_Connection *restrict c) +{ + c->h_layer.state = mhd_HTTP_LAYER_PREFACE; + c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_SET; +} + + +#else /* ! MHD_SUPPORT_HTTP2 */ +# define connection_set_http_layer_init_state(c) ((void) 0) +#endif /* ! MHD_SUPPORT_HTTP2 */ /** * Set initial internal states for the connection to start reading and @@ -251,6 +266,10 @@ new_connection_prepare_ (struct MHD_Daemon *restrict daemon, # endif #endif +#ifdef MHD_SUPPORT_HTTP2 + mhd_h2_blank_init (c); +#endif /* MHD_SUPPORT_HTTP2 */ + if (! external_add) { c->sk.state.corked = mhd_T_NO; @@ -398,6 +417,7 @@ new_connection_process_inner (struct MHD_Daemon *restrict daemon, mhd_DLINKEDL_INS_FIRST_D (&(daemon->conns.def_timeout), \ connection, by_timeout); + connection_set_http_layer_init_state (connection); connection_set_initial_state (connection); notify_app_conn (daemon, connection, false); diff --git a/src/mhd2/daemon_start.c b/src/mhd2/daemon_start.c @@ -153,6 +153,13 @@ daemon_set_basic_settings (struct MHD_Daemon *restrict d, { static const uint_fast64_t max_timeout_ms_value = ((uint_fast64_t) ~((uint_fast64_t) 0)) / 8; + +#ifdef MHD_SUPPORT_HTTP2 + // TODO: make it configurable + d->http_cfg.http1x = true; + d->http_cfg.http2 = true; +#endif /* MHD_SUPPORT_HTTP2 */ + d->req_cfg.strictness = s->protocol_strict_level.v_sl; #ifdef MHD_SUPPORT_COOKIES diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c @@ -86,6 +86,10 @@ # include "upgrade_proc.h" #endif /* MHD_SUPPORT_UPGRADE */ +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_comm.h" +#endif + #include "mhd_public_api.h" #ifdef mhd_DEBUG_POLLING_FDS @@ -566,7 +570,13 @@ mhd_daemon_close_all_conns (struct MHD_Daemon *d) else /* Combined with the next 'if' */ #endif if (1) + { +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_C_IS_HTTP2 (c)) + mhd_h2_conn_deinit (c); +#endif /* MHD_SUPPORT_HTTP2 */ mhd_conn_start_closing_d_shutdown (c); + } mhd_conn_pre_clean (c); mhd_conn_remove_from_daemon (c); mhd_conn_close_final (c); @@ -1055,7 +1065,8 @@ select_update_statuses_from_fdsets_and_resume_conn (struct MHD_Daemon *d, } #ifndef MHD_FAVOR_SMALL_CODE - mhd_assert ((0 == num_events) || resuming_conn); + // TODO: recheck functionality with HTTP/2 + // mhd_assert ((0 == num_events) || resuming_conn); #endif /* MHD_FAVOR_SMALL_CODE */ return true; } diff --git a/src/mhd2/h2/h2_action.c b/src/mhd2/h2/h2_action.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_action.c + * @brief Implementation of HTTP/2 action creators + * @author Karlson2k (Evgeny Grin) + */ + + +#include "mhd_sys_options.h" + +#include "mhd_assert.h" + +#include "mhd_action.h" +#include "mhd_response.h" + +#include "h2_req_data.h" + +#include "h2_action.h" + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_act_is_resp_h2_compatible (const struct mhd_H2RequestData *restrict req, + const struct MHD_Response *restrict response) +{ + mhd_assert (req->is_http2); + + // TODO: move new two checks to the unified (HTTP/1.x and HTTP/2) code + if ((mhd_HTTP_METHOD_CONNECT == req->method) && + (200 <= response->sc) && (299 >= response->sc)) + return false; + + if ((MHD_NO != response->cfg.head_only) && + (200 <= response->sc) && + (MHD_HTTP_STATUS_NOT_MODIFIED != response->sc) && + (MHD_HTTP_STATUS_NO_CONTENT != response->sc) && + (mhd_HTTP_METHOD_HEAD != req->method)) + return false; + + // TODO: support digest auth with HTTP/2 + if (mhd_RESP_HAS_AUTH_DIGEST (response)) + return false; + + // TODO: implement callback for the next response + if (MHD_HTTP_STATUS_OK > response->sc) + return false; + + // TODO: work with all types + if ((mhd_RESPONSE_CONTENT_DATA_BUFFER != response->cntn_dtype) + && (mhd_RESPONSE_CONTENT_DATA_FILE != response->cntn_dtype) + && (mhd_RESPONSE_CONTENT_DATA_IOVEC != response->cntn_dtype)) + return false; + if ((mhd_RESPONSE_CONTENT_DATA_FILE == response->cntn_dtype) && + response->cntn.file.is_pipe) + return false; + + return true; +} diff --git a/src/mhd2/h2/h2_action.h b/src/mhd2/h2/h2_action.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_action.h + * @brief Declarations of HTTP/2 action creators + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_ACTION_H +#define MHD_H2_ACTION_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct mhd_H2RequestData; /* Forward declaration */ +struct MHD_Response; /* Forward declaration */ + +MHD_INTERNAL bool +mhd_h2_act_is_resp_h2_compatible (const struct mhd_H2RequestData *restrict req, + const struct MHD_Response *restrict response) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_ACTION_H */ diff --git a/src/mhd2/h2/h2_app_cb.c b/src/mhd2/h2/h2_app_cb.c @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_app_cb.c + * @brief Implementation of HTTP/2 functions for calling application callbacks + * @author Karlson2k (Evgeny Grin) + */ + + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" +#include "mhd_unreachable.h" + +#include "mhd_str_types.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" +#include "h2_conn_data.h" +#include "h2_stream_data.h" + +#include "mhd_panic.h" +#include "daemon_logger.h" + +#include "response_destroy.h" + +#include "h2_req_items_funcs.h" +#include "h2_conn_streams.h" + +#include "h2_app_cb.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_stream_cb_early_uri (struct mhd_H2Stream *restrict s) +{ + mhd_assert (mhd_H2_REQ_STAGE_HEADERS_DECODING == s->req.stage); + mhd_assert (mhd_HTTP_METHOD_NO_METHOD != s->req.method); + mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib) + == s->stream_id); + mhd_assert (mhd_H2_REQ_ITEM_POS_INVALID != s->req.pos_path); + + if (NULL != s->c->daemon->req_cfg.uri_cb.cb) + { + struct MHD_EarlyUriCbData req_data; + bool res; + + req_data.request = (struct MHD_Request *) (void*) &(s->req); + res = mhd_h2_items_get_item_value (s->c->h2.mem.req_ib, + s->req.pos_path, + &(req_data.full_uri)); + mhd_assert (res); + (void) res; + + if (s->c->h2.state.top_proc_stream_id < s->stream_id) + s->c->h2.state.top_proc_stream_id = s->stream_id; + s->req.app_seen = true; + + s->c->daemon->req_cfg.uri_cb.cb (s->c->daemon->req_cfg.uri_cb.cls, + &req_data, + &(s->req.app_context)); + } + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_stream_cb_request (struct mhd_H2Stream *restrict s) +{ + struct MHD_Connection *restrict c = s->c; + struct MHD_Daemon *restrict d = c->daemon; + struct MHD_String path; + const struct MHD_Action *a; + + mhd_assert (mhd_C_IS_HTTP2 (c)); + mhd_assert (mhd_H2_REQ_STAGE_HEADERS_PROCESSING == s->req.stage); + mhd_assert (mhd_HTTP_METHOD_NO_METHOD != s->req.method); + mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib) + == s->stream_id); + mhd_assert (mhd_H2_REQ_ITEM_POS_INVALID != s->req.pos_path); + + mhd_assert (NULL == s->rpl.response); + + if (mhd_ACTION_NO_ACTION != s->req.app_act.head_act.act) + MHD_PANIC ("MHD_Action has been set already"); + + if (1) + { + bool res = + mhd_h2_items_get_item_value (s->c->h2.mem.req_ib, + s->req.pos_path, + &(path)); + mhd_assert (res); + (void) res; + } + + if (s->c->h2.state.top_proc_stream_id < s->stream_id) + s->c->h2.state.top_proc_stream_id = s->stream_id; + s->req.app_seen = true; + + a = d->req_cfg.cb (d->req_cfg.cb_cls, + (struct MHD_Request *) (void*) &(s->req), + &path, + (enum MHD_HTTP_Method) s->req.method, + s->req.cntn_size); + + if ((NULL != a) + && (((&(s->req.app_act.head_act) != a)) + || ! mhd_ACTION_IS_VALID (s->req.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."); + /* Perform cleanup of the created but now unused action */ + switch (s->req.app_act.head_act.act) + { + case mhd_ACTION_RESPONSE: + mhd_assert (NULL != s->req.app_act.head_act.data.response); + mhd_response_dec_use_count (s->req.app_act.head_act.data.response); + break; + case mhd_ACTION_UPLOAD: + case mhd_ACTION_SUSPEND: + /* No cleanup needed */ + break; +#ifdef MHD_SUPPORT_POST_PARSER + case mhd_ACTION_POST_PARSE: + /* No cleanup needed */ + break; +#endif /* MHD_SUPPORT_POST_PARSER */ +#ifdef MHD_SUPPORT_UPGRADE + case mhd_ACTION_UPGRADE: + /* No cleanup needed */ + break; +#endif /* MHD_SUPPORT_UPGRADE */ + case mhd_ACTION_ABORT: + mhd_UNREACHABLE (); + break; + case mhd_ACTION_NO_ACTION: + default: + break; + } + a = NULL; + } + if (NULL == a) + s->req.app_act.head_act.act = mhd_ACTION_ABORT; + + switch (s->req.app_act.head_act.act) + { + case mhd_ACTION_RESPONSE: + s->rpl.response = s->req.app_act.head_act.data.response; + return true; +#if 0 + case mhd_ACTION_UPLOAD: + if (0 != s->req.cntn_size) + { + if (! check_and_alloc_buf_for_upload_processing (c)) + return true; + c->stage = mhd_HTTP_STAGE_BODY_RECEIVING; + return (0 != c->read_buffer_offset); + } + c->stage = mhd_HTTP_STAGE_FULL_REQ_RECEIVED; + return true; +#ifdef MHD_SUPPORT_POST_PARSER + case mhd_ACTION_POST_PARSE: + if (0 == s->req.cntn.cntn_size) + { + s->req.u_proc.post.parse_result = MHD_POST_PARSE_RES_REQUEST_EMPTY; + c->stage = mhd_HTTP_STAGE_FULL_REQ_RECEIVED; + return true; + } + if (! mhd_stream_prepare_for_post_parse (c)) + { + mhd_assert (mhd_HTTP_STAGE_FOOTERS_RECEIVED < c->stage); + return true; + } + if (need_100_continue (c)) + { + c->stage = mhd_HTTP_STAGE_CONTINUE_SENDING; + return true; + } + c->stage = mhd_HTTP_STAGE_BODY_RECEIVING; + return true; +#endif /* MHD_SUPPORT_POST_PARSER */ + case mhd_ACTION_SUSPEND: + c->suspended = true; +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Suspending connection, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + s->req.app_act.head_act.act = mhd_ACTION_NO_ACTION; + return false; +#ifdef MHD_SUPPORT_UPGRADE + case mhd_ACTION_UPGRADE: + mhd_assert (0 == s->req.cntn.cntn_size); + c->stage = mhd_HTTP_STAGE_UPGRADE_HEADERS_SENDING; + return false; +#endif /* MHD_SUPPORT_UPGRADE */ + case mhd_ACTION_ABORT: + mhd_conn_start_closing_app_abort (c); + return true; + case mhd_ACTION_NO_ACTION: + default: + mhd_assert (0 && "Impossible value"); + mhd_UNREACHABLE (); + break; +#endif + } + + return mhd_h2_stream_abort (s, + mhd_H2_ERR_INTERNAL_ERROR); +} diff --git a/src/mhd2/h2/h2_app_cb.h b/src/mhd2/h2/h2_app_cb.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_app_cb.h + * @brief Declarations of HTTP/2 functions for calling application callbacks + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_APP_CB_H +#define MHD_H2_APP_CB_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ +struct mhd_H2Stream; /* forward declaration */ + +MHD_INTERNAL bool +mhd_h2_stream_cb_early_uri (struct mhd_H2Stream *restrict s) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL bool +mhd_h2_stream_cb_request (struct mhd_H2Stream *restrict s) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_APP_CB_H */ diff --git a/src/mhd2/h2/h2_bit_masks.h b/src/mhd2/h2/h2_bit_masks.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_bit_masks.h + * @brief Various bitmasks useful with HTTP/2 + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_BIT_MASKS_H +#define MHD_H2_BIT_MASKS_H 1 + +#include "mhd_sys_options.h" + + +/** + * Mask for 31-bit unsigned values + */ +#define mhd_MASK_31BITS (0x7FFFFFFFu) +/** + * Mask for 24-bit unsigned value + */ +#define mhd_MASK_24BITS (0xFFFFFFu) + +/** + * Mask for HTTP/2 Stream Identifier field + */ +#define mhd_H2_STREAM_ID_MASK mhd_MASK_31BITS +/** + * Mask for HTTP/2 frame length field + */ +#define mhd_H2_FR_LENGTH_MASK mhd_MASK_24BITS + + +#endif /* ! MHD_H2_BIT_MASKS_H */ diff --git a/src/mhd2/h2/h2_comm.c b/src/mhd2/h2/h2_comm.c @@ -0,0 +1,606 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_comm.c + * @brief Implementation of HTTP/2 connection communication functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include <string.h> + +#include "mhd_unreachable.h" +#include "mhd_constexpr.h" +#include "mhd_assert.h" + +#include "mhd_connection.h" +#include "mhd_daemon.h" + +#include "mempool_funcs.h" + +#ifdef MHD_SUPPORT_HTTPS +# include "mhd_tls_funcs.h" +#endif + +#include "respond_with_error.h" +#include "stream_funcs.h" +#include "daemon_logger.h" + +#include "h2_req_items_funcs.h" +#include "h2_frame_codec.h" +#include "h2_proc_settings.h" +#include "h2_proc_conn.h" +#include "h2_proc_in.h" +#include "h2_conn_streams.h" +#include "hpack/mhd_hpack_codec.h" + +#include "h2_comm.h" + +#ifndef HAVE_NULL_PTR_ALL_ZEROS +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_blank_init (struct MHD_Connection *restrict c) +{ + c->write_buffer = NULL; +} + + +#endif /* HAVE_NULL_PTR_ALL_ZEROS */ + +/** + * HTTP/2 connection preface + * + * Extracted from RFC 9113, Section 3.4 + */ +mhd_constexpr uint8_t mhd_h2_preface[mhd_H2_PREFACE_LEN] = { + 0x50u, 0x52u, 0x49u, 0x20u, 0x2Au, 0x20u, 0x48u, 0x54u, 0x54u, 0x50u, + 0x2Fu, 0x32u, 0x2Eu, 0x30u, 0x0Du, 0x0Au, 0x0Du, 0x0Au, 0x53u, 0x4Du, + 0x0Du, 0x0Au, 0x0Du, 0x0Au +}; + + +/** + * Result of HTTP/2 preface check + */ +enum MHD_FIXED_ENUM_ mhd_H2PrefaceCheckResult +{ + /** + * Received data matches HTTP/2 preface + */ + mhd_H2_PREFACE_CHECK_IS_HTTP2 + , + /** + * Received data does not match HTTP/2 preface + */ + mhd_H2_PREFACE_CHECK_IS_NOT_HTTP2 + , + /** + * Not enough data has been received + */ + mhd_H2_PREFACE_CHECK_NEED_MORE_DATA +}; + +/** + * Check HTTP/2 connection preface + * @param c the connection to process + * @return enum mhd_H2PrefaceCheckResult status code + */ +mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ enum mhd_H2PrefaceCheckResult +mhd_h2_check_preface (struct MHD_Connection *restrict c) +{ + bool have_enough_data; + + mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state); + mhd_assert (NULL != c->read_buffer); + mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_size); + + if (0u == c->read_buffer_offset) + return mhd_H2_PREFACE_CHECK_NEED_MORE_DATA; + + have_enough_data = (mhd_H2_PREFACE_LEN <= c->read_buffer_offset); + + if (0 != + memcmp (mhd_h2_preface, + c->read_buffer, + have_enough_data ? mhd_H2_PREFACE_LEN : c->read_buffer_offset)) + return mhd_H2_PREFACE_CHECK_IS_NOT_HTTP2; + + return (have_enough_data ? + mhd_H2_PREFACE_CHECK_IS_HTTP2 : mhd_H2_PREFACE_CHECK_NEED_MORE_DATA); +} + + +#define ERR_RSP_H2_NOT_SUPPORTED \ + "<html><head><title>HTTP/2 is not supported</title></head>" \ + "<body>HTTP/2 protocol is not supported.</body></html>" + +#define ERR_RSP_H2_WITH_ALPN_HTTP1 \ + "<html><head><title>HTTP/2 without matching ALPN</title></head>" \ + "<body>ALPN selected HTTP/1.x protocol, HTTP/2 cannot be used " \ + "over TLS if ALPN selected another application protocol.</body></html>" + +#define ERR_RSP_H2_WITHOUT_ALPN \ + "<html><head><title>HTTP/2 without ALPN on HTTPS</title></head>" \ + "<body>HTTP/2 cannot be used over TLS without ALPN.</body></html>" + +/** + * Perform switching connection to HTTP/2 mode + * @param c the connection to switch + * @return 'true' if switched successfully, + * 'false' if connection is broken and should be closed + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +h2_switch_to_h2 (struct MHD_Connection *restrict c) +{ + size_t buff_size; + mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state); + mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_offset); + mhd_assert (mhd_HTTP_VER_FAM_NOT_SET == c->h_layer.fam); + + mhd_DLINKEDL_INIT_LIST_D (&(c->h2.streams.active)); + mhd_DLINKEDL_INIT_LIST_D (&(c->h2.streams.send_q)); + c->h2.streams.num_streams = 0u; + + c->h2.state.init.got_setns = false; + c->h2.state.init.sent_setns = false; + + c->h2.state.sent_setns_noakc = 0u; + + c->h2.state.send_window = mhd_H2_STNG_DEF_INIT_WIN_SIZE; + c->h2.state.recv_window = mhd_H2_STNG_DEF_INIT_WIN_SIZE; + + c->h2.state.top_seen_stream_id = 0u; + + // TODO: make some parameters configurable + c->h2.rcv_cfg.stream_init_win_sz = 0x7FFFFFFFu; + c->h2.rcv_cfg.conn_full_win_sz = 0x7FFFFFFFu; + c->h2.rcv_cfg.max_frame_size = mhd_H2_STNG_DEF_MAX_FRAME_SIZE; + c->h2.rcv_cfg.max_header_list = 16u * 1024u; + c->h2.rcv_cfg.max_concur_streams = 100u; + + c->h2.peer.stream_init_win_sz = mhd_H2_STNG_DEF_INIT_WIN_SIZE; + c->h2.peer.max_frame_size = mhd_H2_STNG_DEF_MAX_FRAME_SIZE; + c->h2.peer.max_header_list = (uint_least32_t) (~((uint_least32_t) 0u)); + c->h2.peer.max_concur_streams = (uint_least32_t) (~((uint_least32_t) 0u)); + + buff_size = mhd_pool_get_size (c->pool); + c->read_buffer = + (char *) + mhd_pool_reset (c->pool, + c->read_buffer, + c->read_buffer_offset, + buff_size); + mhd_assert (NULL != c->read_buffer); + c->read_buffer_size = buff_size; + c->h2.buff.r_cur_frame = mhd_H2_PREFACE_LEN; + + c->h2.mem.send_pool = + mhd_pool_create (c->daemon->conns.cfg.mem_pool_size, + c->daemon->conns.cfg.mem_pool_zeroing); + if (NULL != c->h2.mem.send_pool) + { + buff_size = mhd_pool_get_size (c->h2.mem.send_pool); + c->write_buffer = + (char *) + mhd_pool_allocate (c->h2.mem.send_pool, + buff_size, + false); + mhd_assert (NULL != c->write_buffer); + c->write_buffer_size = buff_size; + c->write_buffer_append_offset = 0u; + c->write_buffer_send_offset = 0u; + + if (mhd_hpack_dec_init (&(c->h2.hk_dec))) + { + if (mhd_hpack_enc_init (&(c->h2.hk_enc))) + { + // TODO: make the size configurable + c->h2.mem.req_ib = mhd_h2_items_block_create (16u * 1024); + + if (NULL != c->h2.mem.req_ib) + { + c->h_layer.fam = mhd_HTTP_VER_FAM_2; + c->h_layer.state = mhd_HTTP_LAYER_CONNECTED; + + return true; /* Success exit point */ + } + /* Clean-up */ + mhd_hpack_enc_deinit (&(c->h2.hk_enc)); + } + mhd_hpack_dec_deinit (&(c->h2.hk_dec)); + } + c->write_buffer = NULL; + mhd_pool_destroy (c->h2.mem.send_pool); + c->h2.mem.send_pool = NULL; + mhd_LOG_MSG (c->daemon, MHD_SC_H2_CONN_MEM_ALLOC_FAILURE, \ + "Failed to allocate memory for the HTTP/2 " + "connection resources."); + } + else + mhd_LOG_MSG (c->daemon, MHD_SC_POOL_MEM_ALLOC_FAILURE, \ + "Failed to allocate memory for the HTTP/2 send buffer."); + + mhd_conn_start_closing_no_sys_res (c); + c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_SET; + c->h_layer.state = mhd_HTTP_LAYER_BROKEN; + return false; +} + + +/** + * Perform de-initialisation of HTTP/2-specific data + * @param c the connection to de-initialise + */ +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_conn_deinit (struct MHD_Connection *restrict c) +{ + mhd_assert (mhd_C_IS_HTTP2 (c)); + mhd_assert ((mhd_HTTP_LAYER_CONNECTED == c->h_layer.state) || + (mhd_HTTP_LAYER_CLOSING == c->h_layer.state) || + (mhd_HTTP_LAYER_BROKEN == c->h_layer.state)); + mhd_assert (! c->h2.dbg.h2_deinited); + + mhd_h2_conn_close_streams_all (c); + + mhd_h2_items_block_destroy (c->h2.mem.req_ib); + c->h2.mem.req_ib = NULL; + mhd_hpack_enc_deinit (&(c->h2.hk_enc)); + mhd_hpack_dec_deinit (&(c->h2.hk_dec)); + + mhd_assert (NULL != c->write_buffer); + mhd_assert (NULL != c->h2.mem.send_pool); + c->write_buffer_send_offset = 0u; + c->write_buffer_append_offset = 0u; + c->write_buffer_size = 0u; + c->write_buffer = NULL; + mhd_pool_destroy (c->h2.mem.send_pool); + c->h2.mem.send_pool = NULL; + +#ifndef NDEBUG + c->h2.dbg.h2_deinited = true; +#endif /* ! NDEBUG */ + + if ((mhd_HTTP_LAYER_BROKEN == c->h_layer.state) || + (mhd_SOCKET_ERR_NO_ERROR != c->sk.state.discnt_err) || + (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY))) + mhd_conn_start_closing_h2_hard (c); + else + mhd_conn_start_closing_h2_soft (c); + + if (mhd_HTTP_LAYER_CLOSED > c->h_layer.state) + c->h_layer.state = mhd_HTTP_LAYER_CLOSED; +} + + +/** + * Handle detection of HTTP/2 preface + * @param c the connection to process + * @return 'true' if connection should be continued to be processed as + * HTTP connection (possibly to send already scheduled error + * response), + * 'false' if connection is broken and should be closed + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +h2_handle_preface_found (struct MHD_Connection *restrict c) +{ + const bool allow_on_alpn_mismatch = + (c->daemon->req_cfg.strictness <= MHD_PSL_EXTRA_PERMISSIVE); + const bool allow_without_alpn = + (c->daemon->req_cfg.strictness <= MHD_PSL_PERMISSIVE); + + mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state); + mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_offset); + + if (! mhd_D_IS_HTTP2_ENABLED (c->daemon)) + { + c->h_layer.state = mhd_HTTP_LAYER_CONNECTED; + c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2; + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + ERR_RSP_H2_NOT_SUPPORTED); + return true; /* Send error response */ + } + +#ifdef MHD_SUPPORT_HTTPS + if (mhd_C_HAS_TLS (c)) + { + switch (mhd_tls_conn_get_alpn_prot (c->tls)) + { + case mhd_TLS_ALPN_PROT_HTTP1_0: + case mhd_TLS_ALPN_PROT_HTTP1_1: + if (! allow_on_alpn_mismatch) + { + c->h_layer.state = mhd_HTTP_LAYER_CONNECTED; + c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2; + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_H2_WITH_ALPN_HTTP1); + return true; /* Send error response */ + } + break; + case mhd_TLS_ALPN_PROT_HTTP2: + break; + case mhd_TLS_ALPN_PROT_NOT_SELECTED: + case mhd_TLS_ALPN_PROT_ERROR: + if (! allow_without_alpn) + { + c->h_layer.state = mhd_HTTP_LAYER_CONNECTED; + c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2; + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_BAD_REQUEST, + ERR_RSP_H2_WITHOUT_ALPN); + return true; /* Send error response */ + } + break; + default: + mhd_UNREACHABLE (); + return false; + } + } +#endif /* MHD_SUPPORT_HTTPS */ + return h2_switch_to_h2 (c); +} + + +#define ERR_RSP_H2_REQUIRED \ + "<html><head><title>HTTP/2 is required</title></head>" \ + "<body>HTTP/2 protocol is required</body></html>" + +#define ERR_RSP_NOT_H2_WITH_ALPN_H2 \ + "<html><head><title>ALPN selected HTTP/2</title></head>" \ + "<body>ALPN selected HTTP/2 protocol, " \ + "only HTTP/2 communication could be used.</body></html>" + +/** + * Handle absence of HTTP/2 preface + * @param c the connection to process + * @return 'true' if connection should be continued to be processed as + * HTTP connection (possibly to send already scheduled error + * response), + * 'false' if connection is broken and should be closed + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +h2_handle_preface_not_found (struct MHD_Connection *restrict c) +{ + const bool allow_on_alpn_mismatch = + (c->daemon->req_cfg.strictness <= MHD_PSL_EXTRA_PERMISSIVE); + + mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state); + mhd_assert (mhd_H2_PREFACE_LEN <= c->read_buffer_offset); + +#ifdef MHD_SUPPORT_HTTPS + if (mhd_C_HAS_TLS (c)) + { + switch (mhd_tls_conn_get_alpn_prot (c->tls)) + { + case mhd_TLS_ALPN_PROT_HTTP1_0: + case mhd_TLS_ALPN_PROT_HTTP1_1: + case mhd_TLS_ALPN_PROT_NOT_SELECTED: + case mhd_TLS_ALPN_PROT_ERROR: + break; + case mhd_TLS_ALPN_PROT_HTTP2: + if (! allow_on_alpn_mismatch) + { + c->h_layer.state = mhd_HTTP_LAYER_BROKEN; + c->h_layer.fam = mhd_HTTP_VER_FAM_INVALID; + mhd_conn_start_closing (c, + mhd_CONN_CLOSE_H2_PREFACE_MISSING, + mhd_MSG4LOG ("No valid HTTP/2 preface on " \ + "TLS connection with 'h2' " + "selected by ALPN")); + return false; + } + break; + default: + mhd_UNREACHABLE (); + c->h_layer.state = mhd_HTTP_LAYER_BROKEN; + c->h_layer.fam = mhd_HTTP_VER_FAM_INVALID; + return false; + break; + } + } +#endif /* MHD_SUPPORT_HTTPS */ + + if (! mhd_D_IS_HTTP1_ENABLED (c->daemon)) + { + c->h_layer.state = mhd_HTTP_LAYER_CONNECTED; + c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2; + mhd_RESPOND_WITH_ERROR_STATIC (c, + MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + ERR_RSP_H2_REQUIRED); + return true; /* Send error response */ + } + + c->h_layer.state = mhd_HTTP_LAYER_CONNECTED; + c->h_layer.fam = mhd_HTTP_VER_FAM_NOT_2; + /* The data in the receive buffer will be re-interpreted as HTTP/1.x request */ + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_INTERNAL enum mhd_CommLayerState +mhd_h2_process_preface (struct MHD_Connection *restrict c) +{ + mhd_assert (mhd_HTTP_LAYER_PREFACE == c->h_layer.state); + + switch (mhd_h2_check_preface (c)) + { + case mhd_H2_PREFACE_CHECK_IS_HTTP2: + return + h2_handle_preface_found (c) ? mhd_COMM_LAYER_OK : mhd_COMM_LAYER_BROKEN; + case mhd_H2_PREFACE_CHECK_IS_NOT_HTTP2: + return + h2_handle_preface_not_found (c) ? + mhd_COMM_LAYER_OK : mhd_COMM_LAYER_BROKEN; + case mhd_H2_PREFACE_CHECK_NEED_MORE_DATA: + return mhd_COMM_LAYER_PROCESSING; + default: + break; + } + mhd_UNREACHABLE (); + return mhd_COMM_LAYER_BROKEN; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_conn_state_update (struct MHD_Connection *restrict c) +{ + unsigned int new_state; + + mhd_assert (mhd_C_IS_HTTP2 (c)); + + mhd_assert (0u == (c->event_loop_info & MHD_EVENT_LOOP_INFO_CLEANUP)); +#ifdef MHD_SUPPORT_UPGRADE + mhd_assert (0u == (c->event_loop_info & MHD_EVENT_LOOP_INFO_UPGRADED)); +#endif /* MHD_SUPPORT_UPGRADE */ + + new_state = 0u; + + if (c->read_buffer_offset < c->read_buffer_size) + new_state |= (unsigned int) MHD_EVENT_LOOP_INFO_RECV; + + if (c->write_buffer_send_offset < c->write_buffer_append_offset) + new_state |= (unsigned int) MHD_EVENT_LOOP_INFO_SEND; + + c->event_loop_info = (enum MHD_ConnectionEventLoopInfo) new_state; +} + + +static MHD_FN_PAR_NONNULL_ALL_ void +h2_conn_manage_buff_out (struct MHD_Connection *restrict c) +{ + mhd_assert (c->write_buffer_send_offset <= c->write_buffer_append_offset); + if (c->write_buffer_send_offset == c->write_buffer_append_offset) + { + c->write_buffer_send_offset = 0u; + c->write_buffer_append_offset = 0u; + } + else if ((0u != c->write_buffer_append_offset) && + (c->write_buffer_send_offset > c->write_buffer_size / 128)) + { + const size_t left_unsent = + c->write_buffer_append_offset - c->write_buffer_send_offset; + memmove (c->write_buffer, + c->write_buffer + c->write_buffer_send_offset, + left_unsent); + c->write_buffer_send_offset = 0u; + c->write_buffer_append_offset = left_unsent; + } +} + + +static MHD_FN_PAR_NONNULL_ALL_ void +h2_conn_process_data_inner (struct MHD_Connection *restrict c) +{ + /* Check whether first SETTINGS frame was send (queued). + It is a part of connection initialisation. */ + if (! c->h2.state.init.sent_setns) + { + if (! mhd_h2_q_settings_first_fr (c)) + return; + } + /* Check whether first peer SETTINGS frame was received. + It is a part of connection initialisation. */ + if (! c->h2.state.init.got_setns) + { + if (! mhd_h2_conn_process_first_fr (c)) + return; /* HTTP/2 cannot be processed */ + } + + h2_conn_manage_buff_out (c); + + /* Process incoming data. + When incoming frames are processed, short control frames (such as + PING ACK, SETTINGS ACK, RST_STREAM) could be added to the sending buffer. + If connection is broken or output buffer has no space to add required + frame, this connection processing is stopped here until the output buffer + got more space. */ + if (! mhd_h2_conn_process_in_data (c)) + return; + + /* Close broken streams, update receive windows. + Short control frames, like WINDOW_UPDATE, RST_STREAM could be added to the + sending buffer. + If connection is broken or output buffer has no space to add required + frame, this connection processing is stopped here until the output buffer + got more space. */ + if (! mhd_h2_conn_process_changes (c)) + return; + + /* Finally send the replies (if output buffer still have any space). */ + mhd_h2_conn_process_streams_sending_queue (c); + + return; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_process_data (struct MHD_Connection *restrict c) +{ + mhd_assert (mhd_C_IS_HTTP2 (c)); + + mhd_assert (mhd_HTTP_LAYER_CONNECTED <= c->h_layer.state); + mhd_assert (mhd_HTTP_LAYER_CLOSING >= c->h_layer.state); + + if (mhd_HTTP_LAYER_CLOSING == c->h_layer.state) + { + if (c->write_buffer_append_offset == c->write_buffer_send_offset) + { + mhd_h2_conn_deinit (c); + return false; + } + return true; + } + + h2_conn_process_data_inner (c); + + mhd_assert (mhd_HTTP_LAYER_CLOSED != c->h_layer.state); + if ((mhd_SOCKET_ERR_NO_ERROR != c->sk.state.discnt_err) || + (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)) || + (mhd_HTTP_LAYER_CLOSING == c->h_layer.state)) + { + mhd_h2_conn_deinit (c); + return false; + } + + return true; +} diff --git a/src/mhd2/h2/h2_comm.h b/src/mhd2/h2/h2_comm.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_comm.h + * @brief Declarations of HTTP/2 connection communication functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_COMM_H +#define MHD_H2_COMM_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "mhd_comm_layer_state.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Perform initial (blank) initialisation of HTTP/2 connection data + * @param c the connection to initialise + */ +#ifndef HAVE_NULL_PTR_ALL_ZEROS +MHD_INTERNAL void +mhd_h2_blank_init (struct MHD_Connection *restrict c); + +MHD_FN_PAR_NONNULL_ALL_; +#else /* ! HAVE_NULL_PTR_ALL_ZEROS */ +#define mhd_h2_blank_init(c) ((void) 0) +#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ + +/** + * Perform handshaking of HTTP communication layer. + * + * Update @a c->h_layer and switch to HTTP/2 mode as necessary. + * @param c the connection to use + * @return #mhd_COMM_LAYER_PROCESSING if preface processing is not completed, + * #mhd_COMM_LAYER_OK if HTTP communication can be performed now, + * #mhd_COMM_LAYER_BROKEN if connection is broken and should be closed. + */ +MHD_INTERNAL enum mhd_CommLayerState +mhd_h2_process_preface (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +MHD_INTERNAL void +mhd_h2_conn_state_update (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +MHD_INTERNAL bool +mhd_h2_conn_process_data (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL void +mhd_h2_conn_deinit (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_COMM_H */ diff --git a/src/mhd2/h2/h2_conn_data.h b/src/mhd2/h2/h2_conn_data.h @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_conn_data.h + * @brief Definition of HTTP/2 connection-specific data + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_CONN_DATA_H +#define MHD_H2_CONN_DATA_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_dlinked_list.h" + +#include "hpack/mhd_hpack_dec_types.h" +#include "hpack/mhd_hpack_enc_types.h" + +/** + * The size in bytes of HTTP/2 preface + */ +#define mhd_H2_PREFACE_LEN (24u) + +struct mhd_MemoryPool; /* Forward declaration */ +struct mhd_H2ReqItemsBlock; /* Forward declaration */ +struct mhd_H2Stream; /* Forward declaration */ + +mhd_DLINKEDL_LIST_DEF (mhd_H2Stream); + +struct mhd_H2ConnRecvCtrlData +{ + uint_least32_t stream_init_win_sz; + + uint_least32_t conn_full_win_sz; + + uint_least32_t max_frame_size; // TODO: implement update when ACKed by peer + + uint_least32_t max_header_list; + + uint_least32_t max_concur_streams; +}; + +struct mhd_H2ConnMemData +{ + struct mhd_MemoryPool *send_pool; + + /** + * Request items block (headers, footers, URI arguments, cookies etc.) + */ + struct mhd_H2ReqItemsBlock *req_ib; +}; + +struct mhd_H2GoawayData +{ + bool occurred; + + uint_least32_t code; +}; + +struct mhd_H2ConnStateInitData +{ + /** + * 'true' if the first peer settings has been received and processed + */ + bool got_setns; + /** + * 'true' if the first settings frame has been sent + */ + bool sent_setns; +}; + +struct mhd_H2ConnStateData +{ + struct mhd_H2ConnStateInitData init; + /** + * Number of sent settings not ACKed yet by peer + */ + uint_fast64_t sent_setns_noakc; + + int_least32_t send_window; + + int_least32_t recv_window; + /** + * Highest Stream ID of any received stream + */ + uint_least32_t top_seen_stream_id; + /** + * Highest Stream ID of the stream for which application callback is called + */ + uint_least32_t top_proc_stream_id; + /** + * Highest Stream ID of the stream for which RST_STREAM was sent + */ + uint_least32_t top_rst_stream_id; + /** + * If set to non-zero then the next incoming frame must be a CONTINUATION + * frame with specified Stream ID. + */ + uint_least32_t continuation_stream_id; + + struct mhd_H2GoawayData sent_goaway; + + struct mhd_H2GoawayData recvd_goaway; +}; + +struct mhd_H2ConnPeerData +{ + uint_least32_t stream_init_win_sz; + + /** + * Maximum outgoing frame size to use. + * If peer set value smaller than allowed by the settings, then this value + * is in this field. Otherwise, this field indicates maximum size allowed + * by the settings. + */ + uint_least32_t max_frame_size; + + uint_least32_t max_header_list; + + uint_least32_t max_concur_streams; + + /** + * Stream ID specified as the last stream in peer GOAWAY message + */ + uint_least32_t stream_id_limit; // TODO check value when getting new streams +}; + +struct mhd_H2ConnStreams +{ + /** + * The list of all not finished streams + */ + mhd_DLNKDL_LIST (mhd_H2Stream, active); + + /** + * The sending queue + */ + mhd_DLNKDL_LIST (mhd_H2Stream, send_q); + + /** + * The total number streams in the @a active list + */ + size_t num_streams; +}; + +struct mhd_H2ConnBuffData +{ + /** + * The offset in the read buffer of the frame to be processes / being processed + */ + size_t r_cur_frame; + + /** + * Position of unprocessed part of HEADERS/CONTINUATION payload. + * Used only when the next frame is CONTINUATION. + */ + size_t unproc_hdrs_pos; + + /** + * Size of unprocessed part of HEADERS/CONTINUATION payload. + * Must be non-zero only when the next frame is CONTINUATION. + */ + size_t unproc_hdrs_size; +}; + +#ifndef NDEBUG +struct mhd_H2ConnDebug +{ + volatile bool w_buff_updating; + volatile bool h2_deinited; +}; +#endif /* ! NDEBUG */ + +struct mhd_H2ConnData +{ + struct mhd_H2ConnRecvCtrlData rcv_cfg; + + struct mhd_H2ConnMemData mem; + + struct mhd_H2ConnStateData state; + + struct mhd_H2ConnPeerData peer; + + struct mhd_HpackDecContext hk_dec; + + struct mhd_HpackEncContext hk_enc; + + struct mhd_H2ConnStreams streams; + + struct mhd_H2ConnBuffData buff; +#ifndef NDEBUG + struct mhd_H2ConnDebug dbg; +#endif /* ! NDEBUG */ +}; + +#ifndef NDEBUG +# define mhd_H2_W_BUFF_UPDATING_SET(h2data) \ + do {(h2data)->dbg.w_buff_updating = true;} while (0) +# define mhd_H2_W_BUFF_UPDATING_CLEAR(h2data) \ + do {(h2data)->dbg.w_buff_updating = false;} while (0) +#else /* NDEBUG */ +# define mhd_H2_W_BUFF_UPDATING_SET(h2data) ((void) 0) +# define mhd_H2_W_BUFF_UPDATING_CLEAR(h2data) ((void) 0) +#endif /* NDEBUG */ + +#endif /* ! MHD_H2_CONN_DATA_H */ diff --git a/src/mhd2/h2/h2_conn_streams.c b/src/mhd2/h2/h2_conn_streams.c @@ -0,0 +1,710 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_conn_streams.c + * @brief Implementation of HTTP/2 connection streams processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" +#include "mhd_unreachable.h" + +#include "mhd_constexpr.h" + +#include "compat_calloc.h" +#include "sys_malloc.h" + +#include "mhd_dlinked_list.h" + +#include "mhd_response.h" +#include "mhd_connection.h" + +#include "mempool_funcs.h" + +#include "response_destroy.h" + +#include "h2_bit_masks.h" +#include "h2_err_codes.h" + +#include "h2_stream_data.h" +#include "h2_proc_out.h" +#include "h2_proc_conn.h" +#include "h2_app_cb.h" + +#include "h2_req_items_funcs.h" +#include "h2_req_fields.h" +#include "h2_reply_funcs.h" + +#include "h2_conn_streams.h" + +#include <string.h> + + +static MHD_FN_PAR_NONNULL_ALL_ struct mhd_H2Stream * +conn_add_new_stream (struct MHD_Connection *restrict c, + uint_least32_t stream_id) +{ + struct mhd_H2Stream *s; + + mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id); + mhd_assert (c->h2.rcv_cfg.max_concur_streams > c->h2.streams.num_streams); + + s = (struct mhd_H2Stream *) mhd_calloc (1u, + sizeof(struct mhd_H2Stream)); + if (NULL == s) + return NULL; + + s->is_h2 = true; + s->stream_id = stream_id; + +#ifndef HAVE_NULL_PTR_ALL_ZEROS + mhd_DLINKEDL_INIT_LINKS (s, streams); + mhd_DLINKEDL_INIT_LINKS (s, send_q); + s->req.app_context = NULL; +#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ + + s->c = c; + + s->req.is_http2 = true; + s->req.stage = mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE; + s->req.pos_method = mhd_H2_REQ_ITEM_POS_INVALID; + s->req.pos_path = mhd_H2_REQ_ITEM_POS_INVALID; + s->req.pos_authority = mhd_H2_REQ_ITEM_POS_INVALID; + + s->state.recv_window = (int_least32_t) c->h2.rcv_cfg.stream_init_win_sz; + mhd_assert (0 < s->state.recv_window); + s->state.send_window = (int_least32_t) c->h2.peer.stream_init_win_sz; + mhd_assert (0 < s->state.send_window); + + mhd_DLINKEDL_INS_LAST_D (&(c->h2.streams.active), s, streams); + mhd_assert (0u != ~(c->h2.streams.num_streams)); + ++(c->h2.streams.num_streams); + + return s; +} + + +static MHD_FN_PAR_NONNULL_ALL_ void +conn_remove_stream (struct MHD_Connection *c, + struct mhd_H2Stream *restrict s) +{ + mhd_assert (s->c == c); + + if (NULL != s->rpl.response) + mhd_response_dec_use_count (s->rpl.response); + mhd_DLINKEDL_DEL_D (&(c->h2.streams.active), s, streams); + + free (s); + mhd_assert (0u != c->h2.streams.num_streams); + --(c->h2.streams.num_streams); +} + + +static MHD_FN_PAR_NONNULL_ALL_ struct mhd_H2Stream * +conn_find_stream (struct MHD_Connection *restrict c, + uint_least32_t stream_id) +{ + struct mhd_H2Stream *s; + + // TODO: improve search. Binary tree or linear array? + for (s = mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.active)); + NULL != s; + s = mhd_DLINKEDL_GET_NEXT (s, streams)) + { + if (stream_id == s->stream_id) + return s; + } + + return NULL; +} + + +static void +stream_start_replying (struct mhd_H2Stream *restrict s) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_stream_req_problem (struct mhd_H2Stream *restrict s, + enum mhd_H2RequestProblemType problem_type) +{ + // TODO: send error reply + (void) problem_type; + return mhd_h2_stream_abort (s, + mhd_H2_ERR_PROTOCOL_ERROR); // TODO: use correct error code +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +stream_send_rst_stream (struct mhd_H2Stream *restrict s, + enum mhd_H2ErrorCode err) +{ + mhd_assert ((! s->state.sent_rst_stream) && + "RST_STREAM must not be sent more than once for active stream"); + + if (! mhd_h2_q_rst_stream (s->c, + s->stream_id, + err)) + return false; + + s->state.sent_rst_stream = true; + s->state.mhd_err = err; + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_stream_abort (struct mhd_H2Stream *restrict s, + enum mhd_H2ErrorCode err) +{ + s->req.stage = mhd_H2_REQ_STAGE_BROKEN; + // TODO: Handle correctly by RST_STREAM + mhd_h2_conn_finish (s->c, + err, + true); + return false; +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +stream_proc_complete_headers (struct mhd_H2Stream *restrict s) +{ + mhd_assert (mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE == s->req.stage); + mhd_assert (0u == s->c->h2.state.continuation_stream_id); + mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib) + == s->stream_id); + + s->req.stage = mhd_H2_REQ_STAGE_HEADERS_DECODING; + s->req.cntn_size = (s->req.got_end_stream ? 0u : MHD_SIZE_UNKNOWN); + + if (! mhd_h2_req_headers_preprocess (s)) + return false; + + if (! mhd_h2_stream_cb_early_uri (s)) + return false; + + if (! mhd_h2_req_uri_parse (s)) + return false; + + if (! mhd_h2_req_cookie_parse (s)) + return false; + + s->req.stage = mhd_H2_REQ_STAGE_HEADERS_PROCESSING; + + if (! mhd_h2_stream_cb_request (s)) + return false; + + mhd_assert (mhd_H2_REQ_STAGE_BROKEN != s->req.stage); + + if (s->req.got_end_stream) + { + s->req.stage = mhd_H2_REQ_STAGE_END_STREAM; + mhd_assert (NULL != s->rpl.response); + } + else + s->req.stage = mhd_H2_REQ_STAGE_HEADERS_COMPLETE; + + if (NULL != s->rpl.response) + stream_start_replying (s); + + return true; +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +stream_proc_in_headers (struct mhd_H2Stream *restrict s, + bool end_headers, + struct mhd_Buffer *restrict payload) +{ + struct MHD_Connection *const c = s->c; + size_t unprocessed; + + switch (mhd_h2_req_fields_decode (&(c->h2.hk_dec), + payload, + false, + c->h2.mem.req_ib, + &unprocessed)) + { + case mhd_H2_DEC_FIELDS_OK: + break; + case mhd_H2_DEC_FIELDS_NO_SPACE: + // TODO: Send error response before closing, use RST_STREAM + case mhd_H2_DEC_FIELDS_INT_ERR: + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_INT_ERROR); + case mhd_H2_DEC_FIELDS_BROKEN_DATA: + s->req.stage = mhd_H2_REQ_STAGE_BROKEN; + mhd_h2_conn_finish (c, + mhd_H2_ERR_COMPRESSION_ERROR, + true); + return false; + case mhd_H2_DEC_FIELDS_PROT_ABUSE: + s->req.stage = mhd_H2_REQ_STAGE_BROKEN; + mhd_h2_conn_finish (c, + mhd_H2_ERR_ENHANCE_YOUR_CALM, + true); + return false; + default: + mhd_UNREACHABLE (); + s->req.stage = mhd_H2_REQ_STAGE_BROKEN; + mhd_h2_conn_finish (c, + mhd_H2_ERR_INTERNAL_ERROR, + true); + return false; + } + + if (end_headers && (0u != unprocessed)) + return ! mhd_h2_conn_finish (c, + mhd_H2_ERR_COMPRESSION_ERROR, + true); + + if (! end_headers) + { + if (0u != unprocessed) + { + const size_t payload_offset = (size_t) (payload->data - c->read_buffer); + + /* Unprocessed part may contain only a single field line. + Stop stream if it is larger than 3/4 of the max headers size. */ + if ((c->h2.rcv_cfg.max_header_list - c->h2.rcv_cfg.max_header_list / 4) <= + unprocessed) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_HEADERS_TOO_LARGE); + + mhd_assert (c->h2.rcv_cfg.max_frame_size < mhd_pool_get_size (c->pool)); + if (((mhd_pool_get_size (c->pool) - c->h2.rcv_cfg.max_frame_size) / 2) <= + unprocessed) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_HEADERS_TOO_LARGE); + + c->h2.buff.unproc_hdrs_pos = payload_offset + payload->size - unprocessed; + c->h2.buff.unproc_hdrs_size = unprocessed; + } + + c->h2.state.continuation_stream_id = s->stream_id; + } + else + { + stream_proc_complete_headers (s); + } + + return true; +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +conn_proc_new_in_stream (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + bool end_stream, + bool end_headers, + struct mhd_Buffer *restrict payload) +{ + struct mhd_H2Stream *s; + + mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id); + mhd_assert (c->h2.state.top_seen_stream_id >= c->h2.state.top_proc_stream_id); + mhd_assert (c->h2.state.top_seen_stream_id < stream_id); + mhd_assert (0u == c->h2.state.continuation_stream_id); + mhd_assert (NULL == conn_find_stream (c, stream_id)); + mhd_assert (0u == c->h2.buff.unproc_hdrs_size); + mhd_assert (c->read_buffer <= payload->data); + mhd_assert ((c->read_buffer_size + c->read_buffer) >= + (payload->data + payload->size)); + + if (c->h2.streams.num_streams >= c->h2.rcv_cfg.max_concur_streams) + return mhd_h2_q_rst_stream (c, + stream_id, + mhd_H2_ERR_REFUSED_STREAM); + + s = conn_add_new_stream (c, + stream_id); + + if (NULL == s) + return mhd_h2_q_rst_stream (c, /* REFUSED_STREAM indicates that stream has not been processed at all */ + stream_id, + mhd_H2_ERR_REFUSED_STREAM); + mhd_h2_items_block_reset (c->h2.mem.req_ib); + + + mhd_h2_items_debug_set_streamid (c->h2.mem.req_ib, + stream_id); + + s->req.got_end_stream = end_stream; + + /* The next call process frame data. Current function must not return + 'false' (unless the connection is broken) beyond this point as the + connection data (HPACK) has been modified . */ + return stream_proc_in_headers (s, + end_headers, + payload); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_streamid_in_headers (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + bool end_stream, + bool end_headers, + struct mhd_Buffer *restrict payload) +{ + mhd_assert (0u != stream_id); + mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id); + mhd_assert (c->h2.state.top_seen_stream_id >= c->h2.state.top_proc_stream_id); + + if (0u == (stream_id & 1u)) + return mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + false); + + if (c->h2.state.top_seen_stream_id < stream_id) + return conn_proc_new_in_stream (c, + stream_id, + end_stream, + end_headers, + payload); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_streamid_in_continuation (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + bool end_headers, + struct mhd_Buffer *payload) +{ + struct mhd_Buffer combined_payload; + struct mhd_H2Stream *s; + + mhd_assert (0u != stream_id); + mhd_assert ((stream_id & mhd_H2_STREAM_ID_MASK) == stream_id); + mhd_assert (c->h2.state.top_seen_stream_id >= c->h2.state.top_proc_stream_id); + + if (c->h2.state.continuation_stream_id != stream_id) + return mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + false); + s = conn_find_stream (c, + stream_id); + mhd_assert (NULL != s); + + if (0u == c->h2.buff.unproc_hdrs_size) + combined_payload = *payload; + else + { + /* Concatenate previously unprocessed part and the new part. + This will break CONTINUATION frame header, but the frame header is not + needed as all data from the header has been decoded. + However, unless connection is broken, 'false' must not be + returned by this function beyond this point as the same frame + cannot be decoded again. */ + memmove (payload->data - c->h2.buff.unproc_hdrs_size, + c->read_buffer + c->h2.buff.unproc_hdrs_pos, + c->h2.buff.unproc_hdrs_size); + combined_payload.data = payload->data - c->h2.buff.unproc_hdrs_size; + combined_payload.size = c->h2.buff.unproc_hdrs_size + payload->size; + } + + return stream_proc_in_headers (s, + end_headers, + &combined_payload); +} + + +static MHD_FN_PAR_NONNULL_ALL_ void +stream_set_reply_props (struct mhd_H2Stream *restrict s) +{ + mhd_assert (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage); + mhd_assert ((mhd_H2_REQ_STAGE_END_STREAM == s->req.stage) || + (mhd_H2_REQ_STAGE_HEADERS_COMPLETE == s->req.stage)); + mhd_assert (NULL != s->rpl.response); +#if 0 // TODO: implement chained replies + if (199 >= s->rpl.response->sc) + { + s->rpl.fields.auto_cntn_len = false; + s->rpl.send_content = false; + s->rpl.chained = true; + } + else +#endif + if (MHD_HTTP_STATUS_NO_CONTENT == s->rpl.response->sc) + { + s->rpl.fields.auto_cntn_len = false; + s->rpl.send_content = false; + } + else if ((mhd_HTTP_METHOD_HEAD == s->req.method) || + (MHD_HTTP_STATUS_NOT_MODIFIED == s->rpl.response->sc)) + { + s->rpl.fields.auto_cntn_len = + (MHD_NO == s->rpl.response->cfg.head_only) + && (MHD_SIZE_UNKNOWN != s->rpl.response->cntn_size); + s->rpl.send_content = false; + } + else + { + s->rpl.fields.auto_cntn_len = (MHD_SIZE_UNKNOWN != s->rpl.response-> + cntn_size); + s->rpl.send_content = (0u != s->rpl.response->cntn_size); + } + s->rpl.cntn_read_pos = 0u; +} + + +static MHD_FN_PAR_NONNULL_ALL_ void +stream_start_replying (struct mhd_H2Stream *restrict s) +{ + struct MHD_Connection *c = s->c; + + /* The stream must not be in the sending queue */ + mhd_assert (NULL == mhd_DLINKEDL_GET_NEXT (s, send_q)); + mhd_assert (s != mhd_DLINKEDL_GET_LAST_D (&(c->h2.streams.send_q))); + mhd_assert (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage); + mhd_assert (NULL != s->rpl.response); + + stream_set_reply_props (s); + + mhd_DLINKEDL_INS_LAST (&(c->h2.streams), s, send_q); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_streamid_in_rst_stream (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + enum mhd_H2ErrorCode err) +{ + +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_streamid_window_incr (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + uint_least32_t incr) +{ + struct mhd_H2Stream *s = conn_find_stream (c, + stream_id); + if (NULL == s) + { + if ((0u == (stream_id & 1u)) || + (c->h2.state.top_rst_stream_id < stream_id)) + return mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + false); + + return true; /* Just ignore the frame */ + } + if ((0 < s->state.send_window) + && (0 > s->state.send_window + (int_least32_t) stream_id)) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_FLOW_CONTROL); + s->state.send_window += (int_least32_t) stream_id; + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_streamid_abort (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + enum mhd_H2ErrorCode err) +{ + +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +stream_maintain_rcv_window (struct mhd_H2Stream *restrict s) +{ + struct MHD_Connection *restrict c = s->c; + + mhd_assert (0 <= s->state.recv_window); + /* Dumb algorithm: if receive windows is less than three quarters of the full + * window size, then bump to the full size. */ + + if ((c->h2.rcv_cfg.stream_init_win_sz - c->h2.rcv_cfg.stream_init_win_sz / 4) + <= (uint_least32_t) s->state.recv_window) + { + uint_least32_t incr = + (uint_least32_t) + (c->h2.rcv_cfg.stream_init_win_sz + - (uint_least32_t) s->state.recv_window); + mhd_assert (0x7FFFFFFFu >= incr); + if (! mhd_h2_q_window_update (c, + s->stream_id, + incr)) + return false; + + s->state.recv_window = (int_least32_t) c->h2.rcv_cfg.stream_init_win_sz; + } + + return true; +} + + +mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ bool +stream_is_closed (const struct mhd_H2Stream *restrict s) +{ + /* If END_STREAM flag has been both send and received then stream is closed */ + if ((mhd_H2_REQ_STAGE_END_STREAM == s->req.stage) + && (mhd_H2_RPL_STAGE_END_STREAM == s->rpl.stage)) + return true; + + if ((s->state.rcvd_rst_stream) || (s->state.sent_rst_stream)) + return true; + + return false; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_maintain_streams_all (struct MHD_Connection *c) +{ + struct mhd_H2Stream *next; + struct mhd_H2Stream *s; + mhd_assert ((! c->h2.state.sent_goaway.occurred) || + (mhd_H2_ERR_NO_ERROR == c->h2.state.sent_goaway.code)); + + next = mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.active)); + while (NULL != (s = next)) + { + next = mhd_DLINKEDL_GET_NEXT (s, streams); + + /* Send RST_STREAM is needed */ + if ((! s->state.rcvd_rst_stream) + && (! s->state.sent_rst_stream)) + { + if ((mhd_H2_REQ_STAGE_BROKEN == s->req.stage) || + (mhd_H2_RPL_STAGE_BROKEN == s->rpl.stage)) + { + enum mhd_H2ErrorCode err; + err = s->state.mhd_err; + if (mhd_H2_ERR_NO_ERROR == err) + err = mhd_H2_ERR_INTERNAL_ERROR; + if (! stream_send_rst_stream (s, + err)) + return false; + } + } + + /* Close and remove stream if it is finished */ + if (stream_is_closed (s)) + { + conn_remove_stream (c, + s); + continue; + } + + if (mhd_H2_REQ_STAGE_END_STREAM > s->req.stage) + { + if (! stream_maintain_rcv_window (s)) + return false; + } + } + + return true; +} + + +mhd_constexpr size_t min_usable_buff = 32u; + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_process_streams_sending_queue (struct MHD_Connection *c) +{ + struct mhd_H2Stream *already_processed; + mhd_assert ((! c->h2.state.sent_goaway.occurred) || + (mhd_H2_ERR_NO_ERROR == c->h2.state.sent_goaway.code)); + + already_processed = NULL; + while (! 0) + { + struct mhd_H2Stream *const s = + mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.send_q)); + + if (NULL == s) + break; + + if (already_processed == s) + break; + + mhd_assert (! stream_is_closed (s)); + + mhd_h2_stream_reply_send (s); + if (mhd_H2_ERR_NO_ERROR != c->h2.state.sent_goaway.code) + return false; + + mhd_DLINKEDL_DEL_D (&(c->h2.streams.send_q), s, send_q); + if (stream_is_closed (s)) + conn_remove_stream (c, + s); + else if (mhd_H2_RPL_STAGE_END_STREAM > s->rpl.stage) + { + /* Still sending, move the stream to the end of the queue */ + mhd_DLINKEDL_INS_LAST_D (&(c->h2.streams.send_q), s, send_q); + if (NULL == already_processed) + already_processed = s; + } + if ((c->write_buffer_size - c->write_buffer_append_offset) < + min_usable_buff) + return false; + } + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_conn_close_streams_all (struct MHD_Connection *restrict c) +{ + while (! 0) + { + struct mhd_H2Stream *const s = + mhd_DLINKEDL_GET_FIRST_D (&(c->h2.streams.send_q)); + + if (NULL == s) + break; + + mhd_assert (! stream_is_closed (s)); + + mhd_DLINKEDL_DEL_D (&(c->h2.streams.send_q), s, send_q); + conn_remove_stream (c, + s); + } + mhd_assert (0u == c->h2.streams.num_streams); +} diff --git a/src/mhd2/h2/h2_conn_streams.h b/src/mhd2/h2/h2_conn_streams.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_conn_streams.h + * @brief Declarations of HTTP/2 connection streams processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_CONN_STREAMS_H +#define MHD_H2_CONN_STREAMS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "h2_err_codes.h" + +struct MHD_Connection; /* forward declaration */ +struct mhd_Buffer; /* forward declaration */ +struct mhd_H2Stream; /* forward declaration */ + +enum mhd_H2RequestProblemType +{ + mhd_H2_REQ_PRBLM_FIELD_CHARS + , + mhd_H2_REQ_PRBLM_FORBIDDEN_HEADER + , + mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG + , + mhd_H2_REQ_PRBLM_HOST_HDR_WRONG_EXTRA + , + mhd_H2_REQ_PRBLM_HEADERS_NO_ENDSTREAM + , + mhd_H2_REQ_PRBLM_PSEUDOHDR_IN_TRAILER + , + mhd_H2_REQ_PRBLM_UNKNOWN_PSEUDOHDR + , + mhd_H2_REQ_PRBLM_PSEUDOHDR_AFTER_HDR + , + mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING + , + mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP + , + mhd_H2_REQ_PRBLM_PSEUDOHDR_EXTRA + , + mhd_H2_REQ_PRBLM_FLOW_CONTROL + , + + mhd_H2_REQ_PRBLM_HEADERS_TOO_LARGE + , + mhd_H2_REQ_PRBLM_INT_ERROR +}; + +/** + * Handle malformed request. + * Send error reply, if possible; abort the request. + * @param s the stream to handle + * @param problem_type the type of the problem + * @return always 'false' + */ +MHD_INTERNAL bool +mhd_h2_stream_req_problem (struct mhd_H2Stream *restrict s, + enum mhd_H2RequestProblemType problem_type) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Perform early abort of the stream, typically due to some error in processing. + * @param s the stream to abort + * @param err the error code to use + * @return always 'false' + */ +MHD_INTERNAL bool +mhd_h2_stream_abort (struct mhd_H2Stream *restrict s, + enum mhd_H2ErrorCode err) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL bool +mhd_h2_conn_streamid_in_headers (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + bool end_stream, + bool end_headers, + struct mhd_Buffer *restrict payload) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL bool +mhd_h2_conn_streamid_in_continuation (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + bool end_headers, + struct mhd_Buffer *payload) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL bool +mhd_h2_conn_streamid_in_data (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + bool end_stream, + struct mhd_Buffer *restrict payload) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL bool +mhd_h2_conn_streamid_in_rst_stream (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + enum mhd_H2ErrorCode err) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL bool +mhd_h2_conn_streamid_window_incr (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + uint_least32_t incr) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL bool +mhd_h2_conn_streamid_abort (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + enum mhd_H2ErrorCode err) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Process maintenance of all streams in the connection + * @param c the connection to use + * @return 'true' if all connections have been processed, + * 'false' is output buffer has no space for required frame or + * if connection is broken + */ +MHD_INTERNAL bool +mhd_h2_conn_maintain_streams_all (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Process sending queue, send replies. + * @param c the connection to use + * @return 'true' if all connections have been processed, + * 'false' is output buffer has no space for required frame or + * if connection is broken + */ +MHD_INTERNAL bool +mhd_h2_conn_process_streams_sending_queue (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Force close of all connection's streams. + * @param c the connection to use + */ +MHD_INTERNAL void +mhd_h2_conn_close_streams_all (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_CONN_STREAMS_H */ diff --git a/src/mhd2/h2/h2_err_codes.h b/src/mhd2/h2/h2_err_codes.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_err_codes.h + * @brief Definition of HTTP/2 error codes + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_ERR_CODES_H +#define MHD_H2_ERR_CODES_H 1 + +#include "mhd_sys_options.h" + +#ifdef mhd_USE_ENUM_BASE_T +# include "sys_base_types.h" +#endif + +/** + * HTTP/2 error codes + * + * Extracted from RFC 9113, Section 7 + */ +enum mhd_H2ErrorCode +mhd_ENUM_BASE_T (uint_least32_t) +{ + mhd_H2_ERR_NO_ERROR = 0x00u + , + mhd_H2_ERR_PROTOCOL_ERROR = 0x01u + , + mhd_H2_ERR_INTERNAL_ERROR = 0x02u + , + mhd_H2_ERR_FLOW_CONTROL_ERROR = 0x03u + , + mhd_H2_ERR_SETTINGS_TIMEOUT = 0x04u + , + mhd_H2_ERR_STREAM_CLOSED = 0x05u + , + mhd_H2_ERR_FRAME_SIZE_ERROR = 0x06u + , + mhd_H2_ERR_REFUSED_STREAM = 0x07u + , + mhd_H2_ERR_CANCEL = 0x08u + , + mhd_H2_ERR_COMPRESSION_ERROR = 0x09u + , + mhd_H2_ERR_CONNECT_ERROR = 0x0Au + , + mhd_H2_ERR_ENHANCE_YOUR_CALM = 0x0Bu + , + mhd_H2_ERR_INADEQUATE_SECURITY = 0x0Cu + , + mhd_H2_ERR_HTTP_1_1_REQUIRED = 0x0Du +#ifndef mhd_USE_ENUM_BASE_T + , + /** + * Not a real error code, no not use + */ + mhd_H2_ERR_SENTINEL = 0x7FFFFFFFu +#endif /* ! mhd_USE_ENUM_BASE_T */ +}; + + +#endif /* ! MHD_H2_ERR_CODES_H */ diff --git a/src/mhd2/h2/h2_frame_codec.c b/src/mhd2/h2/h2_frame_codec.c @@ -0,0 +1,1462 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_frame_codec.c + * @brief Implementation of HTTP/2 frame decoding and encoding functions + * @author Karlson2k (Evgeny Grin) + * + * Details of HTTP/2 frame layout are defined in RFC 9113. + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" +#include "mhd_unreachable.h" + +#include <string.h> + +#include "mhd_bithelpers.h" + +#include "h2_bit_masks.h" + +#include "h2_frame_length.h" + +#include "h2_frame_codec.h" + + +/** + * HTTP/2 frame flag PRIORITY (0x20) used by HEADERS frames + */ +#define mhd_FFLAG_PRIORITY (0x20u) +/** + * HTTP/2 frame flag PADDED (0x08) used by DATA, HEADERS and PUSH_PROMISE frames + */ +#define mhd_FFLAG_PADDED (0x08u) +/** + * HTTP/2 frame flag END_HEADERS (0x04) used by HEADERS and CONTINUATION frames + */ +#define mhd_FFLAG_END_HEADERS (0x04u) +/** + * HTTP/2 frame flag END_STREAM (0x01) used by DATA and HEADERS frames + */ +#define mhd_FFLAG_END_STREAM (0x01u) +/** + * HTTP/2 frame flag ACK (0x01) used by SETTINGS and PING frames + */ +#define mhd_FFLAG_ACK (0x01u) + +/** + * Exclusive bit (0x80000000) for the HEADERS/PRIORITY dependency field. + */ +#define mhd_FFLAG_HEADERS_EXCLUSIVE (0x80000000u) + + +/** + * Decode a DATA frame. + * + * Validates sizes data and flags data for valid combinations. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param flags the frame flags byte + * @param[in,out] frame_info the frame information; updated with DATA details + * @param[out] frame_payload set to the final payload slice that starts after + * any extra header fields and ends before padding + * bytes (if any) + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) +enum mhd_H2FrameDecodeResult +frame_decode_data (size_t payload_buff_size, + uint8_t *restrict payload_buff, + uint_least8_t flags, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + struct mhd_H2FrameDataInfo *const data = + &(frame_info->data); + size_t real_payload_pos; + + mhd_assert (mhd_H2_FRAME_DATA_ID == data->type); + + if (0u == data->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + + data->padded = (0 != (flags & mhd_FFLAG_PADDED)); + data->end_stream = (0 != (flags & mhd_FFLAG_END_STREAM)); + + real_payload_pos = 0u; + if (data->padded) + { + if (real_payload_pos >= data->length) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (real_payload_pos >= payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + data->pad_length = (uint_least8_t) payload_buff[real_payload_pos++]; + } + else + data->pad_length = 0u; + + if (data->length < (real_payload_pos + data->pad_length)) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (payload_buff_size < data->length) + return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE; + + frame_payload->data = (char *) payload_buff + real_payload_pos; + frame_payload->size = + (size_t) (data->length - real_payload_pos - data->pad_length); + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a HEADERS frame. + * + * Validates sizes data and flags data for valid combinations. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param flags the frame flags byte + * @param[in,out] frame_info the frame information; + * updated with HEADERS details + * @param[out] frame_payload set to the final payload slice that starts after + * any extra header fields and ends before padding + * bytes (if any) + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) +enum mhd_H2FrameDecodeResult +frame_decode_headers (size_t payload_buff_size, + uint8_t *restrict payload_buff, + uint_least8_t flags, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + struct mhd_H2FrameHeadersInfo *const headers = + &(frame_info->headers); + size_t real_payload_pos; + + mhd_assert (mhd_H2_FRAME_HEADERS_ID == headers->type); + + if (0u == headers->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + + headers->priority = (0 != (flags & mhd_FFLAG_PRIORITY)); + headers->padded = (0 != (flags & mhd_FFLAG_PADDED)); + headers->end_headers = (0 != (flags & mhd_FFLAG_END_HEADERS)); + headers->end_stream = (0 != (flags & mhd_FFLAG_END_STREAM)); + + real_payload_pos = 0u; + if (headers->padded) + { + if (real_payload_pos >= headers->length) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (real_payload_pos >= payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + headers->pad_length = (uint_least8_t) payload_buff[real_payload_pos++]; + } + else + headers->pad_length = 0u; + + if (headers->priority) + { + uint_least32_t stream_dep_id; + if ((real_payload_pos + 5u) > headers->length) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if ((real_payload_pos + 5u) > payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + stream_dep_id = mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos); + real_payload_pos += 4u; + headers->exclusive = (0 != (stream_dep_id & mhd_FFLAG_HEADERS_EXCLUSIVE)); + headers->stream_dependency = (uint_least32_t) + (stream_dep_id & mhd_H2_STREAM_ID_MASK); + if (headers->stream_id == headers->stream_dependency) + return mhd_H2_F_DEC_STREAM_ERR_PROT; + /* Use "on-wire" 'weight' format. Add one to get a real number. + Do not bother handling calculations here as it is deprecated anyway. */ + headers->weight = payload_buff[real_payload_pos++]; + } + else + { + headers->exclusive = false; + headers->stream_dependency = 0u; + headers->weight = 0u; + } + + if (headers->length < (real_payload_pos + headers->pad_length)) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (payload_buff_size < headers->length) + return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE; + + frame_payload->data = (char *) payload_buff + real_payload_pos; + frame_payload->size = + (size_t) (headers->length - real_payload_pos - headers->pad_length); + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a PRIORITY frame. + * + * Validates the fixed size of the frame. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param[in,out] frame_info the frame information; + * updated with PRIORITY details + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (3) +enum mhd_H2FrameDecodeResult +frame_decode_priority (size_t payload_buff_size, + uint8_t *restrict payload_buff, + union mhd_H2FrameUnion *restrict frame_info) +{ + struct mhd_H2FramePriorityInfo *const priority = + &(frame_info->priority); + uint_least32_t stream_dep_id; + + mhd_assert (mhd_H2_FRAME_PRIORITY_ID == priority->type); + + if (0u == priority->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (5u != priority->length) + return mhd_H2_F_DEC_STREAM_ERR_F_SIZE; + if (5u > payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + stream_dep_id = mhd_GET_32BIT_BE_UNALIGN (payload_buff + 0u); + priority->exclusive = (0 != (stream_dep_id & mhd_FFLAG_HEADERS_EXCLUSIVE)); + priority->stream_dependency = (uint_least32_t) + (stream_dep_id & mhd_H2_STREAM_ID_MASK); + /* Use "on-wire" 'weight' format. Add one to get a real number. + Do not bother handling calculations here as it is deprecated anyway. */ + priority->weight = payload_buff[4]; + + if (priority->stream_id == priority->stream_dependency) + return mhd_H2_F_DEC_STREAM_ERR_PROT; + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode an RST_STREAM frame. + * + * Validates the fixed size of the frame. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param[in,out] frame_info the frame information; + * updated with RST_STREAM details + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (3) +enum mhd_H2FrameDecodeResult +frame_decode_rst_stream (size_t payload_buff_size, + uint8_t *restrict payload_buff, + union mhd_H2FrameUnion *restrict frame_info) +{ + struct mhd_H2FrameRstStreamInfo *const rst_stream = + &(frame_info->rst_stream); + uint_fast32_t load_buff32b; + + mhd_assert (mhd_H2_FRAME_RST_STREAM_ID == rst_stream->type); + + if (0u == rst_stream->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (4u != rst_stream->length) + return mhd_H2_F_DEC_STREAM_ERR_F_SIZE; + if (4u > payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + load_buff32b = mhd_GET_32BIT_BE_UNALIGN (payload_buff + 0u); + rst_stream->error_code = (enum mhd_H2ErrorCode) load_buff32b; + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a SETTINGS frame. + * + * Validates stream identifier (must be zero), ACK semantics and that the + * length is a multiple of 6; exposes raw SETTINGS pairs via @a frame_payload + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param flags the frame flags byte + * @param[in,out] frame_info the frame information; + * updated with SETTINGS details + * @param[out] frame_payload set to the contiguous sequence of 6-byte pairs + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) +enum mhd_H2FrameDecodeResult +frame_decode_settings (size_t payload_buff_size, + uint8_t *restrict payload_buff, + uint_least8_t flags, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + struct mhd_H2FrameSettingsInfo *const settings = + &(frame_info->settings); + + mhd_assert (mhd_H2_FRAME_SETTINGS_ID == settings->type); + + if (0u != settings->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + + settings->ack = (0 != (flags & mhd_FFLAG_ACK)); + + if (settings->ack && (0u != settings->length)) + return mhd_H2_F_DEC_CONN_ERR_F_SIZE; + + if (0u != (settings->length % 6)) + return mhd_H2_F_DEC_CONN_ERR_F_SIZE; + + if (payload_buff_size < settings->length) + return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE; + + frame_payload->data = (char *) payload_buff; + frame_payload->size = settings->length; + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a PUSH_PROMISE frame. + * + * Validates sizes and exposes the header block fragment via @a frame_payload. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param flags the frame flags byte + * @param[in,out] frame_info the frame information; + * updated with PUSH_PROMISE details + * @param[out] frame_payload set to the header block fragment slice within + * payload + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) +enum mhd_H2FrameDecodeResult +frame_decode_push_promise (size_t payload_buff_size, + uint8_t *restrict payload_buff, + uint_least8_t flags, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + struct mhd_H2FramePushPromiseInfo *const push_promise = + &(frame_info->push_promise); + size_t real_payload_pos; + + mhd_assert (mhd_H2_FRAME_PUSH_PROMISE_ID == push_promise->type); + + push_promise->padded = (0 != (flags & mhd_FFLAG_PADDED)); + push_promise->end_headers = (0 != (flags & mhd_FFLAG_END_HEADERS)); + + real_payload_pos = 0u; + if (push_promise->padded) + { + if (real_payload_pos >= push_promise->length) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (real_payload_pos >= payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + push_promise->pad_length = + (uint_least8_t) payload_buff[real_payload_pos++]; + } + else + push_promise->pad_length = 0u; + + if (real_payload_pos + 4u > push_promise->length) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (real_payload_pos + 4u > payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + push_promise->promised_stream_id = + (uint_least32_t) + (mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos) + & mhd_H2_STREAM_ID_MASK); + real_payload_pos += 4u; + + if (push_promise->length < (real_payload_pos + push_promise->pad_length)) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (0u == push_promise->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (0u == push_promise->promised_stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (payload_buff_size < push_promise->length) + return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE; + + frame_payload->data = (char *) payload_buff + real_payload_pos; + frame_payload->size = + (size_t) + (push_promise->length - real_payload_pos - push_promise->pad_length); + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a PING frame. + * + * Validates stream identifier (must be zero) and fixed size (8). + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param flags the frame flags byte + * @param[in,out] frame_info the frame information; + * updated with PING details + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (4) +enum mhd_H2FrameDecodeResult +frame_decode_ping (size_t payload_buff_size, + uint8_t *restrict payload_buff, + uint_least8_t flags, + union mhd_H2FrameUnion *restrict frame_info) +{ + struct mhd_H2FramePingInfo *const ping = + &(frame_info->ping); + + mhd_assert (mhd_H2_FRAME_PING_ID == ping->type); + + ping->ack = (0 != (flags & mhd_FFLAG_ACK)); + + if (0u != ping->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (8u != ping->length) + return mhd_H2_F_DEC_CONN_ERR_F_SIZE; + if (8u > payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + memcpy (ping->opaque_data, + payload_buff, + 8u); + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a GOAWAY frame. + * + * Validates stream identifier (must be zero) and that length is at least 8. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param[in,out] frame_info the frame information; + * updated with GOAWAY details + * @param[out] frame_payload set to the optional debug data slice + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (3) MHD_FN_PAR_OUT_ (4) +enum mhd_H2FrameDecodeResult +frame_decode_goaway (size_t payload_buff_size, + uint8_t *restrict payload_buff, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + struct mhd_H2FrameGoawayInfo *const goaway = + &(frame_info->goaway); + size_t real_payload_pos; + uint_fast32_t load_buff32b; + + mhd_assert (mhd_H2_FRAME_GOAWAY_ID == goaway->type); + + if (0u != goaway->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + if (8u > goaway->length) + return mhd_H2_F_DEC_CONN_ERR_F_SIZE; + if (8u > payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + real_payload_pos = 0u; + goaway->last_stream_id = + (uint_least32_t) + (mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos) + & mhd_MASK_31BITS); + real_payload_pos += 4u; + load_buff32b = mhd_GET_32BIT_BE_UNALIGN (payload_buff + real_payload_pos); + goaway->error_code = (enum mhd_H2ErrorCode) load_buff32b; + real_payload_pos += 4u; + + frame_payload->data = (char *) payload_buff + real_payload_pos; + frame_payload->size = (size_t) (goaway->length - real_payload_pos); + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a WINDOW_UPDATE frame. + * + * Validates fixed size (4) and that the increment is non-zero. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param[in,out] frame_info the frame information; + * updated with WINDOW_UPDATE details + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (3) +enum mhd_H2FrameDecodeResult +frame_decode_window_update (size_t payload_buff_size, + uint8_t *restrict payload_buff, + union mhd_H2FrameUnion *restrict frame_info) +{ + struct mhd_H2FrameWindowUpdateInfo *const window_update = + &(frame_info->window_update); + + mhd_assert (mhd_H2_FRAME_WINDOW_UPDATE_ID == window_update->type); + + if (4u != window_update->length) + return mhd_H2_F_DEC_CONN_ERR_F_SIZE; + if (4u > payload_buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + window_update->window_size_increment = + (uint_least32_t) + (mhd_GET_32BIT_BE_UNALIGN (payload_buff + 0u) & mhd_MASK_31BITS); + + if (0u == window_update->window_size_increment) + return (0u == window_update->stream_id) ? + mhd_H2_F_DEC_CONN_ERR_PROT : mhd_H2_F_DEC_STREAM_ERR_PROT; + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode a CONTINUATION frame. + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param flags the frame flags byte + * @param[in,out] frame_info the frame information; + * updated with CONTINUATION details + * @param[out] frame_payload set to the continuation fragment slice + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) +enum mhd_H2FrameDecodeResult +frame_decode_continuation (size_t payload_buff_size, + uint8_t *restrict payload_buff, + uint_least8_t flags, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + struct mhd_H2FrameContinuationInfo *const continuation = + &(frame_info->continuation); + + mhd_assert (mhd_H2_FRAME_CONTINUATION_ID == continuation->type); + + if (0u == continuation->stream_id) + return mhd_H2_F_DEC_CONN_ERR_PROT; + + continuation->end_headers = (0 != (flags & mhd_FFLAG_END_HEADERS)); + + if (payload_buff_size < continuation->length) + return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE; + + frame_payload->data = (char *) payload_buff; + frame_payload->size = continuation->length; + + return mhd_H2_F_DEC_OK; +} + + +/** + * Decode an unknown frame type as opaque bytes. + * + * Exposes the entire payload via @a frame_payload if fully available + * + * @param payload_buff_size the available input bytes in the @a payload_buff + * @param payload_buff the pointer to the beginning of the frame payload + * @param[in,out] frame_info the frame information; + * only selector fields are used + * @param[out] frame_payload set to the raw payload slice + * @return #mhd_H2_F_DEC_OK on success, + * or a relevant decode error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_INOUT_ (3) MHD_FN_PAR_OUT_ (4) +enum mhd_H2FrameDecodeResult +frame_decode_unknown_type (size_t payload_buff_size, + uint8_t *restrict payload_buff, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + if (payload_buff_size < frame_info->selector.length) + return mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE; + + frame_payload->data = (char *) payload_buff; + frame_payload->size = frame_info->selector.length; + + return mhd_H2_F_DEC_OK; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_OUT_ (5) enum mhd_H2FrameDecodeResult +mhd_h2_frame_decode (size_t buff_size, + uint8_t *restrict buff, + uint_least32_t max_frame_size, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +{ + uint_fast32_t len_and_type; + uint_least32_t length; + uint_least8_t type; + uint_least8_t flags; + uint_least32_t stream_id; + + if (mhd_H2_FR_SIZE_MIN > buff_size) + return mhd_H2_F_DEC_F_HEADER_INCOMPLETE; + + len_and_type = mhd_GET_32BIT_BE_UNALIGN (buff + 0u); + + length = (uint_least32_t) (len_and_type >> 8u); + type = (uint_least8_t) (len_and_type & 0xFFu); + + flags = buff[4]; + + stream_id = (mhd_GET_32BIT_BE_UNALIGN (buff + 5u) & mhd_H2_STREAM_ID_MASK); + + frame_info->selector.length = length; + frame_info->selector.type = (enum mhd_H2FrameIDs) type; + frame_info->selector.stream_id = stream_id; + + if (max_frame_size < length) + return mhd_H2_F_DEC_CONN_ERR_F_SIZE; + + switch ((enum mhd_H2FrameIDs) type) + { + case mhd_H2_FRAME_IDS_DATA_ID: + return frame_decode_data (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + flags, + frame_info, + frame_payload); + case mhd_H2_FRAME_IDS_HEADERS_ID: + return frame_decode_headers (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + flags, + frame_info, + frame_payload); + case mhd_H2_FRAME_IDS_PRIORITY_ID: + frame_payload->size = 0u; + frame_payload->data = NULL; + return frame_decode_priority (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + frame_info); + case mhd_H2_FRAME_IDS_RST_STREAM_ID: + frame_payload->size = 0u; + frame_payload->data = NULL; + return frame_decode_rst_stream (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + frame_info); + case mhd_H2_FRAME_IDS_SETTINGS_ID: + return frame_decode_settings (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + flags, + frame_info, + frame_payload); + case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID: + return frame_decode_push_promise (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + flags, + frame_info, + frame_payload); + case mhd_H2_FRAME_IDS_PING_ID: + frame_payload->size = 0u; + frame_payload->data = NULL; + return frame_decode_ping (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + flags, + frame_info); + case mhd_H2_FRAME_IDS_GOAWAY_ID: + return frame_decode_goaway (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + frame_info, + frame_payload); + case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID: + frame_payload->size = 0u; + frame_payload->data = NULL; + return frame_decode_window_update (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + frame_info); + case mhd_H2_FRAME_IDS_CONTINUATION_ID: + return frame_decode_continuation (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + flags, + frame_info, + frame_payload); + default: + break; + } + return frame_decode_unknown_type (buff_size - mhd_H2_FR_SIZE_MIN, + buff + mhd_H2_FR_SIZE_MIN, + frame_info, + frame_payload); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +size_t +mhd_h2_frame_get_extra_hdr_size ( + const union mhd_H2FrameUnion *restrict frame_info) +{ + size_t extra; + extra = 0u; + switch (frame_info->selector.type) + { + case mhd_H2_FRAME_IDS_DATA_ID: + if (frame_info->data.padded) + extra += 1u; + break; + case mhd_H2_FRAME_IDS_HEADERS_ID: + if (frame_info->headers.padded) + extra += 1u; + if (frame_info->headers.priority) + extra += 5u; + break; + case mhd_H2_FRAME_IDS_PRIORITY_ID: + extra += 5u; + break; + case mhd_H2_FRAME_IDS_RST_STREAM_ID: + extra += 4u; + break; + case mhd_H2_FRAME_IDS_SETTINGS_ID: + break; + case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID: + if (frame_info->push_promise.padded) + extra += 1u; + extra += 4u; + break; + case mhd_H2_FRAME_IDS_PING_ID: + extra += mhd_H2_FR_FIXED_LEN_PING; + break; + case mhd_H2_FRAME_IDS_GOAWAY_ID: + extra += 8u; + break; + case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID: + extra += mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE; + break; + case mhd_H2_FRAME_IDS_CONTINUATION_ID: + break; + default: + break; + } + return extra; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +size_t +mhd_h2_frame_get_padding_size ( + const union mhd_H2FrameUnion *restrict frame_info) +{ + switch (frame_info->selector.type) + { + case mhd_H2_FRAME_IDS_DATA_ID: + mhd_assert ((0u == frame_info->data.pad_length) || \ + frame_info->data.padded); + return frame_info->data.pad_length; + case mhd_H2_FRAME_IDS_HEADERS_ID: + mhd_assert ((0u == frame_info->headers.pad_length) || \ + frame_info->headers.padded); + return frame_info->headers.pad_length; + case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID: + mhd_assert ((0u == frame_info->push_promise.pad_length) || \ + frame_info->push_promise.padded); + return frame_info->push_promise.pad_length; + case mhd_H2_FRAME_IDS_PRIORITY_ID: + case mhd_H2_FRAME_IDS_RST_STREAM_ID: + case mhd_H2_FRAME_IDS_SETTINGS_ID: + case mhd_H2_FRAME_IDS_PING_ID: + case mhd_H2_FRAME_IDS_GOAWAY_ID: + case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID: + case mhd_H2_FRAME_IDS_CONTINUATION_ID: + default: + break; + } + return 0u; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) size_t +mhd_h2_frame_set_payload_size (union mhd_H2FrameUnion *restrict frame_info, + size_t payload_size) +{ + uint_least32_t fr_length; + + mhd_assert (frame_info->selector.length == \ + (frame_info->selector.length & mhd_H2_FR_LENGTH_MASK)); + mhd_assert (frame_info->selector.type == \ + (((uint_least64_t) frame_info->selector.type) & 0xFFu)); +#ifndef NDEBUG + switch (frame_info->selector.type) + { + case mhd_H2_FRAME_IDS_PRIORITY_ID: + case mhd_H2_FRAME_IDS_RST_STREAM_ID: + case mhd_H2_FRAME_IDS_PING_ID: + case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID: + mhd_assert (0u == payload_size); + break; + case mhd_H2_FRAME_IDS_DATA_ID: + case mhd_H2_FRAME_IDS_HEADERS_ID: + case mhd_H2_FRAME_IDS_SETTINGS_ID: + case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID: + case mhd_H2_FRAME_IDS_GOAWAY_ID: + case mhd_H2_FRAME_IDS_CONTINUATION_ID: + default: + break; + } +#endif /* ! NDEBUG */ + mhd_assert (frame_info->selector.length == \ + (frame_info->selector.length & mhd_H2_FR_LENGTH_MASK)); + + fr_length = (uint_least32_t) + (mhd_h2_frame_get_extra_hdr_size (frame_info) + + mhd_h2_frame_get_padding_size (frame_info) + + payload_size); + + mhd_assert (fr_length == (fr_length & mhd_H2_FR_LENGTH_MASK)); + + frame_info->selector.length = fr_length; + + return (size_t) (mhd_H2_FR_HDR_BASE_SIZE + (size_t) fr_length); +} + + +/** + * Encode DATA extra header and flags. + * + * Does not write payload or trailing pad bytes. + * + * @param frame_info the DATA frame information + * @param[out] flags the pointer to the header flags byte to update + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for extra header bytes + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t +frame_hdr_encode_data (const union mhd_H2FrameUnion *restrict frame_info, + uint8_t *restrict flags, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FrameDataInfo *const data = + &(frame_info->data); + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_DATA_ID == data->type); + + mhd_assert (0u != data->stream_id); + mhd_assert ((0u == data->pad_length) || + (data->padded)); + + *flags = (uint8_t) (data->padded ? mhd_FFLAG_PADDED : 0u); + *flags |= (uint8_t) (data->end_stream ? mhd_FFLAG_END_STREAM : 0u); + + extra_hdr_pos = 0u; + if (data->padded) + { + if (out_extra_hdr_size <= extra_hdr_pos) + return 0u; + out_extra_hdr[extra_hdr_pos++] = (uint8_t) data->pad_length; + } + + mhd_assert (data->length >= (extra_hdr_pos + data->pad_length)); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode HEADERS extra header and flags. + * + * @param frame_info the HEADERS frame information + * @param[out] flags the pointer to the header flags byte to update + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for extra header bytes + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t +frame_hdr_encode_headers (const union mhd_H2FrameUnion *restrict frame_info, + uint8_t *restrict flags, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FrameHeadersInfo *const headers = + &(frame_info->headers); + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_HEADERS_ID == headers->type); + + mhd_assert (0u != headers->stream_id); + mhd_assert ((0u == headers->pad_length) || + (headers->padded)); + + *flags = (uint8_t) (headers->priority ? mhd_FFLAG_PRIORITY : 0u); + *flags |= (uint8_t) (headers->padded ? mhd_FFLAG_PADDED : 0u); + *flags |= (uint8_t) (headers->end_headers ? mhd_FFLAG_END_HEADERS : 0u); + *flags |= (uint8_t) (headers->end_stream ? mhd_FFLAG_END_STREAM : 0u); + + extra_hdr_pos = 0u; + if (headers->padded) + { + if (out_extra_hdr_size <= extra_hdr_pos) + return 0u; + out_extra_hdr[extra_hdr_pos++] = (uint8_t) headers->pad_length; + } + + if (headers->priority) + { + uint32_t excl_n_strm_dep; + if (out_extra_hdr_size < (extra_hdr_pos + 5u)) + return 0u; + + excl_n_strm_dep = (uint32_t) + (headers->stream_dependency & mhd_H2_STREAM_ID_MASK); + excl_n_strm_dep |= (uint32_t) + (headers->exclusive ? mhd_FFLAG_HEADERS_EXCLUSIVE : 0u); + mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos, + excl_n_strm_dep); + extra_hdr_pos += 4u; + + /* Use "on-wire" 'weight' format. */ + out_extra_hdr[extra_hdr_pos++] = (uint8_t) headers->weight; + } + + mhd_assert (headers->length >= (extra_hdr_pos + headers->pad_length)); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode PRIORITY payload into the extra header area. + * + * @param frame_info the PRIORITY frame information + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for the 5-byte payload + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_SIZE_ (3,2) size_t +frame_hdr_encode_priority (const union mhd_H2FrameUnion *restrict frame_info, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FramePriorityInfo *const priority = + &(frame_info->priority); + uint32_t excl_n_strm_dep; + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_PRIORITY_ID == priority->type); + + mhd_assert (0u != priority->stream_id); + mhd_assert (priority->stream_id != priority->stream_dependency); + + extra_hdr_pos = 0u; + + if (out_extra_hdr_size < (extra_hdr_pos + 5u)) + return 0u; + + excl_n_strm_dep = (uint32_t) + (priority->stream_dependency & mhd_H2_STREAM_ID_MASK); + excl_n_strm_dep |= (uint32_t) + (priority->exclusive ? mhd_FFLAG_HEADERS_EXCLUSIVE : 0u); + mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos, + excl_n_strm_dep); + extra_hdr_pos += 4u; + + /* Use "on-wire" 'weight' format. */ + out_extra_hdr[extra_hdr_pos++] = (uint8_t) priority->weight; + + mhd_assert (priority->length == extra_hdr_pos); + mhd_assert (mhd_H2_FR_FIXED_LEN_PRIORITY == extra_hdr_pos); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode RST_STREAM payload into the extra header area. + * + * @param frame_info the RST_STREAM frame information + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for the 4-byte payload + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_SIZE_ (3,2) size_t +frame_hdr_encode_rst_stream (const union mhd_H2FrameUnion *restrict frame_info, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FrameRstStreamInfo *const rst_stream = + &(frame_info->rst_stream); + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_RST_STREAM_ID == rst_stream->type); + + mhd_assert (0u != rst_stream->stream_id); + + extra_hdr_pos = 0u; + + if (out_extra_hdr_size < (extra_hdr_pos + 4u)) + return 0u; + + mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos, + (uint32_t) rst_stream->error_code); + extra_hdr_pos += 4u; + + mhd_assert (rst_stream->length == extra_hdr_pos); + mhd_assert (mhd_H2_FR_FIXED_LEN_RST_STREAM == extra_hdr_pos); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode SETTINGS header flags. + * + * SETTINGS has no extra header bytes. Any SETTINGS parameters belong to the + * payload and are not written by this function. + * + * @param frame_info the SETTINGS frame information + * @param[out] flags the pointer to the header flags byte to update + * @return the size of the frame basic header plus the size of the frame extra + * header written + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) size_t +frame_hdr_encode_settings (const union mhd_H2FrameUnion *restrict frame_info, + uint8_t *restrict flags) +{ + const struct mhd_H2FrameSettingsInfo *const settings = + &(frame_info->settings); + + mhd_assert (mhd_H2_FRAME_SETTINGS_ID == settings->type); + + mhd_assert (0u == settings->stream_id); + mhd_assert ((! settings->ack) || (0u == settings->length)); + mhd_assert (0u == (settings->length % 6)); + + *flags = (uint8_t) (settings->ack ? mhd_FFLAG_ACK : 0u); + + return mhd_H2_FR_HDR_BASE_SIZE; +} + + +/** + * Encode PUSH_PROMISE extra header and flags. + * + * This function does not write the header block fragment or trailing + * pad bytes. + * + * @param frame_info the PUSH_PROMISE frame information + * @param[out] flags the pointer to the header flags byte to update + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for extra header bytes + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t +frame_hdr_encode_push_promise ( + const union mhd_H2FrameUnion *restrict frame_info, + uint8_t *restrict flags, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FramePushPromiseInfo *const push_promise = + &(frame_info->push_promise); + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_PUSH_PROMISE_ID == push_promise->type); + + mhd_assert (0u != push_promise->stream_id); + mhd_assert ((0u == push_promise->pad_length) || + (push_promise->padded)); + mhd_assert (0u != push_promise->promised_stream_id); + mhd_assert (push_promise->promised_stream_id == + (push_promise->promised_stream_id & mhd_H2_STREAM_ID_MASK)); + + *flags = (uint8_t) (push_promise->padded ? mhd_FFLAG_PADDED : 0u); + *flags |= (uint8_t) (push_promise->end_headers ? mhd_FFLAG_END_HEADERS : 0u); + + extra_hdr_pos = 0u; + if (push_promise->padded) + { + if (out_extra_hdr_size <= extra_hdr_pos) + return 0u; + out_extra_hdr[extra_hdr_pos++] = (uint8_t) push_promise->pad_length; + } + + if (out_extra_hdr_size < (extra_hdr_pos + 4u)) + return 0u; + + mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos, + (uint32_t) (push_promise->promised_stream_id + & mhd_H2_STREAM_ID_MASK)); + extra_hdr_pos += 4u; + + mhd_assert (push_promise->length >= + (extra_hdr_pos + push_promise->pad_length)); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode PING payload and flags. + * + * @param frame_info the PING frame information + * @param[out] flags the pointer to the header flags byte to update + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for the 8-byte payload + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_SIZE_ (4,3) size_t +frame_hdr_encode_ping (const union mhd_H2FrameUnion *restrict frame_info, + uint8_t *restrict flags, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FramePingInfo *const ping = + &(frame_info->ping); + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_PING_ID == ping->type); + + mhd_assert (0u == ping->stream_id); + + *flags = (uint8_t) (ping->ack ? mhd_FFLAG_ACK : 0u); + + extra_hdr_pos = 0u; + + if (out_extra_hdr_size < (extra_hdr_pos + 8u)) + return 0u; + + memcpy (out_extra_hdr, + ping->opaque_data, + 8u); + extra_hdr_pos += 8u; + + mhd_assert (ping->length == extra_hdr_pos); + mhd_assert (mhd_H2_FR_FIXED_LEN_PING == extra_hdr_pos); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode GOAWAY fixed fields into the extra header area. + * + * Optional debug data belongs to the payload and is not written by this + * function. + * + * @param frame_info the GOAWAY frame information + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for the 8-byte fixed fields + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_SIZE_ (3,2) size_t +frame_hdr_encode_goaway (const union mhd_H2FrameUnion *restrict frame_info, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FrameGoawayInfo *const goaway = + &(frame_info->goaway); + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_GOAWAY_ID == goaway->type); + + mhd_assert (0u == goaway->stream_id); + + extra_hdr_pos = 0u; + + if (out_extra_hdr_size < (extra_hdr_pos + 8u)) + return 0u; + + mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos, + (uint32_t) (goaway->last_stream_id + & mhd_H2_STREAM_ID_MASK)); + extra_hdr_pos += 4u; + + mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos, + (uint32_t) goaway->error_code); + extra_hdr_pos += 4u; + + mhd_assert (goaway->length >= extra_hdr_pos); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode WINDOW_UPDATE payload into the extra header area. + * + * @param frame_info the WINDOW_UPDATE frame information + * @param out_extra_hdr_size available space in @a out_extra_hdr + * @param[out] out_extra_hdr the output buffer for the 4-byte payload + * @return the size of the frame basic header plus the size of the frame extra + * header written, + * or 0 if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_SIZE_ (3,2) size_t +frame_hdr_encode_window_update ( + const union mhd_H2FrameUnion *restrict frame_info, + const size_t out_extra_hdr_size, + uint8_t *restrict out_extra_hdr) +{ + const struct mhd_H2FrameWindowUpdateInfo *const window_update = + &(frame_info->window_update); + size_t extra_hdr_pos; + + mhd_assert (mhd_H2_FRAME_WINDOW_UPDATE_ID == window_update->type); + mhd_assert (0u != window_update->window_size_increment); + mhd_assert (window_update->window_size_increment == + (window_update->window_size_increment & mhd_MASK_31BITS)); + + extra_hdr_pos = 0u; + + if (out_extra_hdr_size < (extra_hdr_pos + 4u)) + return 0u; + + mhd_PUT_32BIT_BE_UNALIGN (out_extra_hdr + extra_hdr_pos, + (uint32_t) (window_update->window_size_increment + & mhd_MASK_31BITS)); + extra_hdr_pos += 4u; + + mhd_assert (window_update->length >= extra_hdr_pos); + mhd_assert (mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE == extra_hdr_pos); + + return mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_pos; +} + + +/** + * Encode CONTINUATION header flags. + * + * CONTINUATION has no extra header bytes. + * + * @param frame_info the CONTINUATION frame information + * @param[out] flags the pointer to the header flags byte to update + * @return the size of the frame basic header plus the size of the frame extra + * header written + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) size_t +frame_hdr_encode_continuation ( + const union mhd_H2FrameUnion *restrict frame_info, + uint8_t *restrict flags) +{ + const struct mhd_H2FrameContinuationInfo *const continuation = + &(frame_info->continuation); + + mhd_assert (mhd_H2_FRAME_CONTINUATION_ID == continuation->type); + + mhd_assert (0u != continuation->stream_id); + + *flags = (uint8_t) (continuation->end_headers ? mhd_FFLAG_END_HEADERS : 0u); + + return mhd_H2_FR_HDR_BASE_SIZE; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_SIZE_ (3,2) size_t +mhd_h2_frame_hdr_encode (const union mhd_H2FrameUnion *restrict frame_info, + size_t out_buff_size, + uint8_t *restrict out_buff) +{ + uint32_t len_and_type; + + mhd_assert (frame_info->selector.length == \ + (frame_info->selector.length & mhd_H2_FR_LENGTH_MASK)); + mhd_assert (frame_info->selector.type == \ + (((uint_least64_t) frame_info->selector.type) & 0xFFu)); + mhd_assert (frame_info->selector.stream_id == \ + (frame_info->selector.stream_id & mhd_H2_STREAM_ID_MASK)); + + if (mhd_H2_FR_SIZE_MIN > out_buff_size) + return 0u; + + len_and_type = (uint_least8_t) (frame_info->selector.type & 0xFFu); + len_and_type |= + (uint32_t) + ((((uint_least32_t) frame_info->selector.length) << 8u) + & mhd_H2_FR_LENGTH_MASK); + + mhd_PUT_32BIT_BE_UNALIGN (out_buff + 0u, + len_and_type); + out_buff[4] = 0u; /* flags */ + mhd_PUT_32BIT_BE_UNALIGN (out_buff + 5u, + frame_info->selector.stream_id + & mhd_H2_STREAM_ID_MASK); + + switch (frame_info->selector.type) + { + case mhd_H2_FRAME_IDS_DATA_ID: + return frame_hdr_encode_data (frame_info, + out_buff + 4u, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_HEADERS_ID: + return frame_hdr_encode_headers (frame_info, + out_buff + 4u, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_PRIORITY_ID: + return frame_hdr_encode_priority (frame_info, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_RST_STREAM_ID: + return frame_hdr_encode_rst_stream (frame_info, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_SETTINGS_ID: + return frame_hdr_encode_settings (frame_info, + out_buff + 4u); + case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID: + return + frame_hdr_encode_push_promise (frame_info, + out_buff + 4u, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_PING_ID: + return frame_hdr_encode_ping (frame_info, + out_buff + 4u, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_GOAWAY_ID: + return frame_hdr_encode_goaway (frame_info, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID: + return + frame_hdr_encode_window_update (frame_info, + out_buff_size - mhd_H2_FR_HDR_BASE_SIZE, + out_buff + mhd_H2_FR_HDR_BASE_SIZE); + case mhd_H2_FRAME_IDS_CONTINUATION_ID: + return + frame_hdr_encode_continuation (frame_info, + out_buff + 4u); + default: + break; + } + mhd_UNREACHABLE_D ("Unknown frame types should not be sent"); + return mhd_H2_FR_HDR_BASE_SIZE; +} diff --git a/src/mhd2/h2/h2_frame_codec.h b/src/mhd2/h2/h2_frame_codec.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_frame_codec.h + * @brief Declarations of HTTP/2 frame decoding and encoding functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_FRAME_CODEC_H +#define MHD_H2_FRAME_CODEC_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#include "mhd_buffer.h" +#include "h2_frame_types.h" + +#if defined(_MSC_FULL_VER) +#pragma warning(push) +/* Disable C4505 "unreferenced local function has been removed" */ +#pragma warning(disable:4505) +#endif /* _MSC_FULL_VER */ + +/** + * The basic (mandatory) HTTP/2 frame header size + */ +#define mhd_H2_FR_HDR_BASE_SIZE (9u) + +/** + * The minimum HTTP/2 frame size + */ +#define mhd_H2_FR_SIZE_MIN mhd_H2_FR_HDR_BASE_SIZE + +/** + * Maximum extra frame header size for known frame types. + */ +#define mhd_H2_FR_HDR_EXTRA_SIZE_MAX (8u) + +/** + * Result of frame decode + */ +enum MHD_FIXED_ENUM_ mhd_H2FrameDecodeResult +{ + /** + * Frame is successfully decoded + */ + mhd_H2_F_DEC_OK = 0 + , + /** + * Frame header is completely decoded, but frame payload is incomplete + * Frame information is valid. + */ + mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE + , + /** + * Frame header information is not complete. + * Not enough data to decode frame header. + */ + mhd_H2_F_DEC_F_HEADER_INCOMPLETE + , + /** + * Stream error of type "Frame size error" + */ + mhd_H2_F_DEC_STREAM_ERR_F_SIZE + , + /** + * Stream error of type "Protocol error" + */ + mhd_H2_F_DEC_STREAM_ERR_PROT + , + /** + * Connection error of type "Frame size error" + */ + mhd_H2_F_DEC_CONN_ERR_F_SIZE + , + /** + * Connection error of type "Protocol error" + */ + mhd_H2_F_DEC_CONN_ERR_PROT +}; + +#define mhd_H2_FRAME_DEC_ERR_IS_HARD(res) \ + (mhd_H2_F_DEC_F_HEADER_INCOMPLETE < (res)) + +/** + * Decode an HTTP/2 frame. + * + * If result is #mhd_H2_F_DEC_OK or #mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE then + * @a frame_info has full information about known frame types. + * When result is not #mhd_H2_F_DEC_OK, #mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE or + * #mhd_H2_F_DEC_F_HEADER_INCOMPLETE, the only valid data in the @a frame_info + * is "length", "type" and "stream_id". + * @param buff_size the size of the data available to decode + * @param buff the data to decode + * @param max_frame_size the maximum allowed frame size + * @param[out] frame_info set to frame information + * @param[out] frame_payload on successful decode set to the frame payload + * size and location + * @return #mhd_H2_F_DEC_OK on success, + * the error code otherwise + */ +MHD_INTERNAL enum mhd_H2FrameDecodeResult +mhd_h2_frame_decode (size_t buff_size, + uint8_t *restrict buff, + uint_least32_t max_frame_size, + union mhd_H2FrameUnion *restrict frame_info, + struct mhd_Buffer *restrict frame_payload) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_(2,1) +MHD_FN_PAR_OUT_(4) MHD_FN_PAR_OUT_ (5); + + +/** + * Find an amount of extra bytes required to encode the HTTP/2 frame. + * + * Each frame is encoded to the basic frame header size + * #mhd_H2_FR_HDR_BASE_SIZE plus optional extra bytes (depending on frame + * flags), plus the frame payload (only for relevant frame types), plus + * optional padding at the end. + * + * This function calculates the total extra bytes for known frame types based + * on the frame header type and flags. This extra size is counted as a part + * of the frame "length". + * @param frame_info the information about the frame; only type and flags are + * checked + * @return the number of extra bytes needed to write the frame header, which is + * a value in range 0 .. #mhd_H2_FR_HDR_EXTRA_SIZE_MAX; + * does not include the basic frame header + * size #mhd_H2_FR_HDR_BASE_SIZE + */ +MHD_INTERNAL size_t +mhd_h2_frame_get_extra_hdr_size ( + const union mhd_H2FrameUnion *restrict frame_info) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1); + + +/** + * Get the size of the padding (if any) set in the @a frame_info. + * + * The padding is counted as a part of frame "length". + * @param frame_info the information about frame, only type, flags and + * padding size (if any) are checked + * @return the padding size used at the end of the frame + */ +MHD_INTERNAL size_t +mhd_h2_frame_get_padding_size ( + const union mhd_H2FrameUnion *restrict frame_info) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1); + + +/** + * Calculate full frame size + * @param frame_info the information about frame + * @return the total size of the frame, including frame header and payload + */ +mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) size_t +mhd_h2_frame_get_total_size (const union mhd_H2FrameUnion *restrict frame_info) +{ + mhd_assert ((mhd_h2_frame_get_extra_hdr_size (frame_info) + + mhd_h2_frame_get_padding_size (frame_info)) + <= frame_info->selector.length); + return (size_t) (mhd_H2_FR_HDR_BASE_SIZE + frame_info->selector.length); +} + + +/** + * Set 'length' member in the HTTP/2 frame. + * This function takes into account the frame type, the size of the extra + * frame header (if any), the padding (if allowed by frame type and set in + * @a frame_info) and the payload size. + * + * @param[in,out] frame_info the information about frame: the type, all flags + * and padding size (if any) must be set; the 'length' + * is modified + * @param real_payload_size the size of the real payload of the frame (the + * part of the frame after extra header bytes and + * before the padding (if any) bytes; + * must be zero for frames without payload support; + * must fit 24 bits length together with padding and + * extra header + * @return the total size of the updated frame: the basis header plus the value + * of the frame 'length' field + */ +MHD_INTERNAL size_t +mhd_h2_frame_set_payload_size (union mhd_H2FrameUnion *restrict frame_info, + size_t real_payload_size) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + +/** + * Encode the HTTP/2 frame header into the output buffer. + * + * This function writes the HTTP/2 basic frame header and extra frame header + * (if any, for know frame types only) using data from @a frame_info. + * @param frame_info the frame information to encode + * @param out_buff_size the size of @a out_buff in bytes + * @param[out] out_buff the output buffer to receive the encoded header + * @return the number of bytes written on success + * (always #mhd_H2_FR_HDR_BASE_SIZE or more), + * or 0 if @a out_buff_size is too small (the output buffer may be + * alerted in case of this error) + */ +MHD_INTERNAL size_t +mhd_h2_frame_hdr_encode (const union mhd_H2FrameUnion *restrict frame_info, + size_t out_buff_size, + uint8_t *restrict out_buff) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_SIZE_ (3,2); + +#if defined(_MSC_FULL_VER) +/* Restore warnings */ +#pragma warning(pop) +#endif /* _MSC_FULL_VER */ + +#endif /* ! MHD_H2_FRAME_CODEC_H */ diff --git a/src/mhd2/h2/h2_frame_init.h b/src/mhd2/h2/h2_frame_init.h @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_frame_init.h + * @brief Declarations of HTTP/2 frame decoding and encoding functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_FRAME_INIT_H +#define MHD_H2_FRAME_INIT_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include <string.h> + +#include "h2_frame_types.h" +#include "h2_frame_length.h" + +#if defined(_MSC_FULL_VER) +#pragma warning(push) +/* Disable C4505 "unreferenced local function has been removed" */ +#pragma warning(disable:4505) +#endif /* _MSC_FULL_VER */ + +mhd_static_inline struct mhd_H2FrameDataInfo* +mhd_h2_frame_init_data (union mhd_H2FrameUnion *restrict frame_info, + uint_least32_t stream_id, + bool end_stream) +{ + struct mhd_H2FrameDataInfo *const data = &(frame_info->data); + data->type = mhd_H2_FRAME_DATA_ID; + data->length = 0u; /* To be set later */ + data->stream_id = stream_id; + + data->padded = false; + data->end_stream = end_stream; + + data->pad_length = 0u; + + return data; +} + + +mhd_static_inline struct mhd_H2FrameHeadersInfo* +mhd_h2_frame_init_headers (union mhd_H2FrameUnion *restrict frame_info, + uint_least32_t stream_id, + bool end_headers, + bool end_stream) +{ + struct mhd_H2FrameHeadersInfo *const headers = &(frame_info->headers); + headers->type = mhd_H2_FRAME_HEADERS_ID; + headers->length = 0u; /* To be set later */ + headers->stream_id = stream_id; + + headers->priority = false; + headers->padded = false; + headers->end_headers = end_headers; + headers->end_stream = end_stream; + + headers->pad_length = 0u; + headers->exclusive = false; + headers->stream_dependency = 0u; + headers->weight = 0u; + + return headers; +} + + +mhd_static_inline struct mhd_H2FrameRstStreamInfo* +mhd_h2_frame_init_rst_stream (union mhd_H2FrameUnion *restrict frame_info, + uint_least32_t stream_id, + enum mhd_H2ErrorCode error_code) +{ + struct mhd_H2FrameRstStreamInfo *const rst_stream = &(frame_info->rst_stream); + rst_stream->type = mhd_H2_FRAME_RST_STREAM_ID; + rst_stream->length = mhd_H2_FR_FIXED_LEN_RST_STREAM; /* Fixed size */ + rst_stream->stream_id = stream_id; + + rst_stream->error_code = error_code; + + return rst_stream; +} + + +mhd_static_inline struct mhd_H2FrameSettingsInfo* +mhd_h2_frame_init_settings (union mhd_H2FrameUnion *restrict frame_info, + bool ack) +{ + struct mhd_H2FrameSettingsInfo *const settings = &(frame_info->settings); + settings->type = mhd_H2_FRAME_SETTINGS_ID; + settings->length = 0u; /* Could be increased later */ + settings->stream_id = 0u; + + settings->ack = ack; + + return settings; +} + + +mhd_static_inline struct mhd_H2FramePingInfo* +mhd_h2_frame_init_ping (union mhd_H2FrameUnion *restrict frame_info, + bool ack, + const uint8_t opaque_data[MHD_FN_PAR_FIX_ARR_SIZE_ (8)]) +{ + struct mhd_H2FramePingInfo *const ping = &(frame_info->ping); + ping->type = mhd_H2_FRAME_PING_ID; + ping->length = mhd_H2_FR_FIXED_LEN_PING; /* Fixed size */ + ping->stream_id = 0u; + + ping->ack = ack; + + memcpy (ping->opaque_data, + opaque_data, + 8u); + + return ping; +} + + +mhd_static_inline struct mhd_H2FrameGoawayInfo* +mhd_h2_frame_init_goaway (union mhd_H2FrameUnion *restrict frame_info, + uint_least32_t last_stream_id, + enum mhd_H2ErrorCode error_code) +{ + struct mhd_H2FrameGoawayInfo *const goaway = &(frame_info->goaway); + goaway->type = mhd_H2_FRAME_GOAWAY_ID; + goaway->length = 8u; /* Could be increased later */ + goaway->stream_id = 0u; + + goaway->last_stream_id = last_stream_id; + goaway->error_code = error_code; + + return goaway; +} + + +mhd_static_inline struct mhd_H2FrameWindowUpdateInfo* +mhd_h2_frame_init_window_update (union mhd_H2FrameUnion *restrict frame_info, + uint_least32_t stream_id, + uint_least32_t window_size_increment) +{ + struct mhd_H2FrameWindowUpdateInfo *const window_update = + &(frame_info->window_update); + window_update->type = mhd_H2_FRAME_WINDOW_UPDATE_ID; + window_update->length = mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE; /* Fixed size */ + window_update->stream_id = stream_id; + + window_update->window_size_increment = window_size_increment; + + return window_update; +} + + +mhd_static_inline struct mhd_H2FrameContinuationInfo* +mhd_h2_frame_init_continuation (union mhd_H2FrameUnion *restrict frame_info, + uint_least32_t stream_id, + bool end_headers) +{ + struct mhd_H2FrameContinuationInfo *const continuation = + &(frame_info->continuation); + continuation->type = mhd_H2_FRAME_CONTINUATION_ID; + continuation->length = 0u; /* To be set later */ + continuation->stream_id = stream_id; + + continuation->end_headers = end_headers; + + return continuation; +} + + +#if defined(_MSC_FULL_VER) +/* Restore warnings */ +#pragma warning(pop) +#endif /* _MSC_FULL_VER */ + +#endif /* ! MHD_H2_FRAME_INIT_H */ diff --git a/src/mhd2/h2/h2_frame_length.h b/src/mhd2/h2/h2_frame_length.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_frame_length.h + * @brief HTTP2 frames length for fixed-size frames + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_FRAME_LENGTH_H +#define MHD_H2_FRAME_LENGTH_H 1 + +#include "mhd_sys_options.h" + +#define mhd_H2_FR_FIXED_LEN_PRIORITY (5u) + +#define mhd_H2_FR_FIXED_LEN_RST_STREAM (4u) + +#define mhd_H2_FR_FIXED_LEN_PING (8u) + +#define mhd_H2_FR_FIXED_LEN_WINDOW_UPDATE (4u) + +#endif /* ! MHD_H2_FRAME_LENGTH_H */ diff --git a/src/mhd2/h2/h2_frame_types.h b/src/mhd2/h2/h2_frame_types.h @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_frame_types.h + * @brief Definitions of HTTP/2 frame types + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_FRAME_TYPES_H +#define MHD_H2_FRAME_TYPES_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "h2_err_codes.h" + + +enum MHD_FIXED_ENUM_ mhd_H2FrameDataID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_DATA_ID = 0x00u +}; + +struct mhd_H2FrameDataInfo +{ + uint_least32_t length; + enum mhd_H2FrameDataID type; + uint_least32_t stream_id; + + bool padded; /* Changes extra header size */ + bool end_stream; + + uint_least8_t pad_length; +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FrameHeadersID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_HEADERS_ID = 0x01u +}; + +struct mhd_H2FrameHeadersInfo +{ + uint_least32_t length; + enum mhd_H2FrameHeadersID type; + uint_least32_t stream_id; + + bool priority; /* Changes extra header size */ + bool padded; /* Changes extra header size */ + bool end_headers; + bool end_stream; + + uint_least8_t pad_length; + bool exclusive; + uint_least32_t stream_dependency; + uint_least8_t weight; /* "on-wire" format, 0..255 */ +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FramePriorityID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_PRIORITY_ID = 0x02u +}; + +struct mhd_H2FramePriorityInfo +{ + uint_least32_t length; + enum mhd_H2FramePriorityID type; + uint_least32_t stream_id; + + bool exclusive; + uint_least32_t stream_dependency; + uint_least8_t weight; /* "on-wire" format, 0..255 */ +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FrameRstStreamID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_RST_STREAM_ID = 0x03u +}; + +struct mhd_H2FrameRstStreamInfo +{ + uint_least32_t length; + enum mhd_H2FrameRstStreamID type; + uint_least32_t stream_id; + + enum mhd_H2ErrorCode error_code; // TODO: support 4 > sizeof(int) +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FrameSettingsID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_SETTINGS_ID = 0x04u +}; + +struct mhd_H2FrameSettingsInfo +{ + uint_least32_t length; + enum mhd_H2FrameSettingsID type; + uint_least32_t stream_id; + + bool ack; +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FramePushPromiseID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_PUSH_PROMISE_ID = 0x05u +}; + +struct mhd_H2FramePushPromiseInfo +{ + uint_least32_t length; + enum mhd_H2FramePushPromiseID type; + uint_least32_t stream_id; + + bool padded; /* Changes extra header size */ + bool end_headers; + + uint_least8_t pad_length; + uint_least32_t promised_stream_id; +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FramePingID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_PING_ID = 0x06u +}; + +struct mhd_H2FramePingInfo +{ + uint_least32_t length; + enum mhd_H2FramePingID type; + uint_least32_t stream_id; + + bool ack; + + uint8_t opaque_data[8]; +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FrameGoawayID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_GOAWAY_ID = 0x07u +}; + +struct mhd_H2FrameGoawayInfo +{ + uint_least32_t length; + enum mhd_H2FrameGoawayID type; + uint_least32_t stream_id; + + uint_least32_t last_stream_id; + enum mhd_H2ErrorCode error_code; // TODO: support 4 > sizeof(int) +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FrameWindowUpdateID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_WINDOW_UPDATE_ID = 0x08u +}; + +struct mhd_H2FrameWindowUpdateInfo +{ + uint_least32_t length; + enum mhd_H2FrameWindowUpdateID type; + uint_least32_t stream_id; + + uint_least32_t window_size_increment; +}; + + +enum MHD_FIXED_ENUM_ mhd_H2FrameContinuationID +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_CONTINUATION_ID = 0x09u +}; + +struct mhd_H2FrameContinuationInfo +{ + uint_least32_t length; + enum mhd_H2FrameContinuationID type; + uint_least32_t stream_id; + + bool end_headers; +}; + + +enum mhd_H2FrameIDs +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_FRAME_IDS_DATA_ID = mhd_H2_FRAME_DATA_ID, + mhd_H2_FRAME_IDS_HEADERS_ID = mhd_H2_FRAME_HEADERS_ID, + mhd_H2_FRAME_IDS_PRIORITY_ID = mhd_H2_FRAME_PRIORITY_ID, + mhd_H2_FRAME_IDS_RST_STREAM_ID = mhd_H2_FRAME_RST_STREAM_ID, + mhd_H2_FRAME_IDS_SETTINGS_ID = mhd_H2_FRAME_SETTINGS_ID, + mhd_H2_FRAME_IDS_PUSH_PROMISE_ID = mhd_H2_FRAME_PUSH_PROMISE_ID, + mhd_H2_FRAME_IDS_PING_ID = mhd_H2_FRAME_PING_ID, + mhd_H2_FRAME_IDS_GOAWAY_ID = mhd_H2_FRAME_GOAWAY_ID, + mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID = mhd_H2_FRAME_WINDOW_UPDATE_ID, + mhd_H2_FRAME_IDS_CONTINUATION_ID = mhd_H2_FRAME_CONTINUATION_ID +}; + +struct mhd_H2FrameSelector +{ + uint_least32_t length; + enum mhd_H2FrameIDs type; + uint_least32_t stream_id; +}; + + +union mhd_H2FrameUnion +{ + struct mhd_H2FrameDataInfo data; + struct mhd_H2FrameHeadersInfo headers; + struct mhd_H2FramePriorityInfo priority; + struct mhd_H2FrameRstStreamInfo rst_stream; + struct mhd_H2FrameSettingsInfo settings; + struct mhd_H2FramePushPromiseInfo push_promise; + struct mhd_H2FramePingInfo ping; + struct mhd_H2FrameGoawayInfo goaway; + struct mhd_H2FrameWindowUpdateInfo window_update; + struct mhd_H2FrameContinuationInfo continuation; + + struct mhd_H2FrameSelector selector; +}; + +#endif /* ! MHD_H2_FRAME_TYPES_H */ diff --git a/src/mhd2/h2/h2_proc_conn.c b/src/mhd2/h2/h2_proc_conn.c @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_conn.c + * @brief Implementation of HTTP/2 connection processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" +#include "mhd_unreachable.h" + +#include "mhd_connection.h" + +#include "h2_frame_types.h" + +#include "h2_frame_init.h" +#include "h2_frame_codec.h" + +#include "h2_proc_settings.h" +#include "h2_conn_streams.h" +#include "h2_proc_out.h" + +#include "h2_proc_conn.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_process_first_fr (struct MHD_Connection *restrict c) +{ + union mhd_H2FrameUnion first_fr; + struct mhd_Buffer payload; + enum mhd_H2FrameDecodeResult dec_res; + bool ret; + + mhd_assert (mhd_C_IS_HTTP2 (c)); + mhd_assert (! c->h2.state.init.got_setns); + + dec_res = mhd_h2_frame_decode (c->read_buffer_offset - c->h2.buff.r_cur_frame, + (uint8_t *) c->read_buffer + + c->h2.buff.r_cur_frame, + mhd_H2_STNG_DEF_MAX_FRAME_SIZE, /* Settings are not yet ACKed */ + &first_fr, + &payload); + if (mhd_H2_FRAME_DEC_ERR_IS_HARD (dec_res)) + { + mhd_h2_conn_finish (c, + mhd_H2_F_DEC_CONN_ERR_F_SIZE == dec_res ? + mhd_H2_ERR_FRAME_SIZE_ERROR : mhd_H2_ERR_PROTOCOL_ERROR, + true); + return false; + } + + if (mhd_H2_F_DEC_OK != dec_res) + return false; /* Not yet complete */ + + if ((mhd_H2_FRAME_IDS_SETTINGS_ID != first_fr.selector.type) || + (first_fr.settings.ack)) + { + mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + true); + return false; + } + + ret = mhd_h2_proc_first_settings (c, + &payload); + + c->h2.buff.r_cur_frame += mhd_h2_frame_get_total_size (&first_fr); + + return ret; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_process_in_goaway (struct MHD_Connection *restrict c, + uint_least32_t last_stream_id, + enum mhd_H2ErrorCode err) +{ + c->h2.state.recvd_goaway.occurred = true; + c->h2.state.recvd_goaway.code = err; + c->h2.peer.stream_id_limit = last_stream_id; + + // TODO: close all streams with higher IDs + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_update_stream_init_window (struct MHD_Connection *restrict c, + uint_least32_t init_wind_size) +{ + mhd_assert (0x7FFFFFFFu >= init_wind_size); + (void) c; // TODO: change window in all streams + + c->h2.peer.stream_init_win_sz = init_wind_size; + return true; +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +conn_win_update (struct MHD_Connection *restrict c) +{ + mhd_assert (0 <= c->h2.state.recv_window); + /* Dumb algorithm: if receive windows is less than three quarters of the full + * window size, then bump to the full size. */ + if ((c->h2.rcv_cfg.conn_full_win_sz - c->h2.rcv_cfg.conn_full_win_sz / 4) >= + (uint_least32_t) c->h2.state.recv_window) + { + const uint_least32_t incr = + (uint_least32_t) + (c->h2.rcv_cfg.conn_full_win_sz + - (uint_least32_t) c->h2.state.recv_window); + mhd_assert (0x7FFFFFFFu >= incr); + if (! mhd_h2_q_window_update (c, + 0u, + incr)) + return false; + c->h2.state.recv_window = (int_least32_t) c->h2.rcv_cfg.conn_full_win_sz; + } + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_process_changes (struct MHD_Connection *restrict c) +{ + if (! conn_win_update (c)) + return false; + + if (! mhd_h2_conn_maintain_streams_all (c)) + return false; + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_finish (struct MHD_Connection *restrict c, + enum mhd_H2ErrorCode err, + bool forced) +{ + if (! mhd_h2_q_goaway (c, + err)) + { + if (! forced) + return false; + + c->h2.state.sent_goaway.occurred = true; + c->h2.state.sent_goaway.code = (uint_least32_t) err; + + c->h_layer.state = mhd_HTTP_LAYER_BROKEN; + return true; + } + + c->h2.state.sent_goaway.occurred = true; + c->h2.state.sent_goaway.code = (uint_least32_t) err; + + c->h_layer.state = mhd_HTTP_LAYER_CLOSING; + return true; +} diff --git a/src/mhd2/h2/h2_proc_conn.h b/src/mhd2/h2/h2_proc_conn.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_conn.h + * @brief Declarations of HTTP/2 connection processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_PROC_CONN_H +#define MHD_H2_PROC_CONN_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "h2_err_codes.h" + +struct MHD_Connection; /* forward declaration */ +struct mhd_Buffer; /* forward declaration */ +union mhd_H2FrameUnion; /* forward declaration */ + +/** + * Process the first incoming frame + * @param c the connection to process + * @return 'true' if successfully processed, HTTP/2 connection is fully ready; + * 'false' if HTTP/2 connection cannot be used (not ready or broken) + */ +MHD_INTERNAL bool +mhd_h2_conn_process_first_fr (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Process all changes in the connection. + * Create outgoing control frames as necessary, handle errors. + * @param c the connection to process + * @return 'true' if successfully processed, HTTP/2 processing may continue + * 'false' if not enough output buffer to send the control frames or + * if connection is broken, HTTP/2 procession may not continue + */ +MHD_INTERNAL bool +mhd_h2_conn_process_changes (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +MHD_INTERNAL bool +mhd_h2_conn_process_in_goaway (struct MHD_Connection *restrict c, + uint_least32_t last_stream_id, + enum mhd_H2ErrorCode err) +MHD_FN_PAR_NONNULL_ALL_; + + +MHD_INTERNAL bool +mhd_h2_conn_update_stream_init_window (struct MHD_Connection *restrict c, + uint_least32_t init_wind_size) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Transition connection to closing state, queue relevant frame for peer. + * @param c the connection to process + * @param err the error code to use + * @param forced if 'true' connection will start closing even if there is + * no space to form peer notification frame + * @return 'true' if succeed, + * 'false' if output buffer has no space + */ +MHD_INTERNAL bool +mhd_h2_conn_finish (struct MHD_Connection *restrict c, + enum mhd_H2ErrorCode err, + bool forced) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_PROC_CONN_H */ diff --git a/src/mhd2/h2/h2_proc_in.c b/src/mhd2/h2/h2_proc_in.c @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_in.c + * @brief Implementation of HTTP/2 connection incoming data processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" +#include "mhd_unreachable.h" + +#include "mhd_buffer.h" +#include "mhd_connection.h" + +#include <string.h> + +#include "h2_frame_types.h" +#include "h2_frame_codec.h" + +#include "h2_conn_streams.h" +#include "h2_proc_settings.h" +#include "h2_proc_out.h" +#include "h2_proc_conn.h" + +#include "h2_proc_in.h" + + +enum mhd_H2ProcInFrameResult +{ + mhd_H2_PROC_IN_FRAME_CONTINUE = mhd_H2_STNGS_PROC_OK, + mhd_H2_PROC_IN_FRAME_BLOCKED_BY_OUT = mhd_H2_STNGS_PROC_NO_OUT_BUFF, + mhd_H2_PROC_IN_FRAME_CONN_BROKEN = mhd_H2_STNGS_PROC_STNGS_ERR +}; + +/** + * Process incoming frame + * @param c the connection to use + * @param h2frame the frame information + * @param payload the frame payload (excluding known extra headers) + * @return 'true' if frame was successfully and completely processed, + * 'false' if frame should be processed again later or if connection + * if in closing or broken state (and no incoming frames should be + * processed anymore) + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +process_inc_frame (struct MHD_Connection *restrict c, + const union mhd_H2FrameUnion *h2frame, + struct mhd_Buffer *payload) +{ + if (0u != c->h2.state.continuation_stream_id) + { + if ((mhd_H2_FRAME_IDS_CONTINUATION_ID != h2frame->selector.type) + || (c->h2.state.continuation_stream_id != + h2frame->continuation.stream_id)) + return ! mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + false); + } + else + mhd_assert (0u == c->h2.buff.unproc_hdrs_size); + + switch (h2frame->selector.type) + { + case mhd_H2_FRAME_IDS_DATA_ID: + mhd_assert (0); + return mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + false); + case mhd_H2_FRAME_IDS_HEADERS_ID: + return mhd_h2_conn_streamid_in_headers (c, + h2frame->headers.stream_id, + h2frame->headers.end_stream, + h2frame->headers.end_headers, + payload); + case mhd_H2_FRAME_IDS_RST_STREAM_ID: + return mhd_h2_conn_streamid_in_rst_stream (c, + h2frame->rst_stream.stream_id, + h2frame->rst_stream.error_code); + return mhd_H2_PROC_IN_FRAME_CONTINUE; + case mhd_H2_FRAME_IDS_SETTINGS_ID: + return (mhd_H2_STNGS_PROC_OK == mhd_h2_proc_new_settings (c, + payload)); + case mhd_H2_FRAME_IDS_PUSH_PROMISE_ID: + return ! mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + false); + case mhd_H2_FRAME_IDS_PING_ID: + return mhd_h2_q_ping (c, + true, + h2frame->ping.opaque_data); + case mhd_H2_FRAME_IDS_GOAWAY_ID: + return mhd_h2_conn_process_in_goaway (c, + h2frame->goaway.last_stream_id, + h2frame->goaway.error_code); + case mhd_H2_FRAME_IDS_WINDOW_UPDATE_ID: + if (0u != h2frame->window_update.stream_id) + { + return + mhd_h2_conn_streamid_window_incr ( + c, + h2frame->window_update.stream_id, + h2frame->window_update.window_size_increment); + } + if ((h2frame->window_update.window_size_increment + + (uint_least32_t) c->h2.state.send_window) > 0x7FFFFFFFu) + return ! mhd_h2_conn_finish (c, + mhd_H2_ERR_FLOW_CONTROL_ERROR, + false); + c->h2.state.send_window += + (int_least32_t) h2frame->window_update.window_size_increment; + return true; + case mhd_H2_FRAME_IDS_CONTINUATION_ID: + return + mhd_h2_conn_streamid_in_continuation (c, + h2frame->continuation.stream_id, + h2frame->continuation.end_headers, + payload); + case mhd_H2_FRAME_IDS_PRIORITY_ID: + default: + break; /* Ignored */ + } + /* Ignored types of frame */ + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_conn_process_in_data (struct MHD_Connection *restrict c) +{ + mhd_assert (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state); + + mhd_assert (c->read_buffer_size >= c->read_buffer_offset); + mhd_assert (c->read_buffer_offset >= c->h2.buff.r_cur_frame); + + if (c->h2.buff.r_cur_frame == c->read_buffer_offset) + return true; /* Shortcut, nothing to process */ + + do + { + union mhd_H2FrameUnion h2frame; + enum mhd_H2FrameDecodeResult dec_res; + struct mhd_Buffer payload; + bool proc_next; + uint8_t *const frame_buff = + (uint8_t *) c->read_buffer + c->h2.buff.r_cur_frame; + const size_t buff_left = c->read_buffer_offset - c->h2.buff.r_cur_frame; + + proc_next = true; + dec_res = mhd_h2_frame_decode (buff_left, + frame_buff, + c->h2.rcv_cfg.max_frame_size, + &h2frame, + &payload); + switch (dec_res) + { + case mhd_H2_F_DEC_OK: + proc_next = process_inc_frame (c, + &h2frame, + &payload); + proc_next = (proc_next && (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state)); + if (c->h2.state.top_seen_stream_id < h2frame.selector.stream_id) + c->h2.state.top_seen_stream_id = h2frame.selector.stream_id; + break; + case mhd_H2_F_DEC_F_PAYLOAD_INCOMPLETE: + case mhd_H2_F_DEC_F_HEADER_INCOMPLETE: + proc_next = false; + break; + case mhd_H2_F_DEC_STREAM_ERR_F_SIZE: + case mhd_H2_F_DEC_STREAM_ERR_PROT: + if (mhd_h2_frame_get_total_size (&h2frame) > buff_left) + { + /* The frame is not yet complete */ + proc_next = false; + break; + } + proc_next = + mhd_h2_conn_streamid_abort ( + c, + h2frame.selector.stream_id, + (mhd_H2_F_DEC_STREAM_ERR_F_SIZE == dec_res) ? + mhd_H2_ERR_FRAME_SIZE_ERROR : mhd_H2_ERR_PROTOCOL_ERROR); + if (c->h2.state.top_seen_stream_id < h2frame.selector.stream_id) + c->h2.state.top_seen_stream_id = h2frame.selector.stream_id; + break; + case mhd_H2_F_DEC_CONN_ERR_F_SIZE: + mhd_h2_conn_finish (c, + mhd_H2_ERR_FRAME_SIZE_ERROR, + false); + return false; + case mhd_H2_F_DEC_CONN_ERR_PROT: + mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + false); + return false; + break; + default: + break; /* ignore unknown types */ + } + + if (! proc_next) + break; + + mhd_assert (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state); + + mhd_assert ((c->read_buffer_offset - c->h2.buff.r_cur_frame) >= + mhd_h2_frame_get_total_size (&h2frame)); + + c->h2.buff.r_cur_frame += mhd_h2_frame_get_total_size (&h2frame); + } while (c->h2.buff.r_cur_frame < c->read_buffer_offset); + + mhd_assert (c->read_buffer_offset >= c->h2.buff.r_cur_frame); + if (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state) + { + const size_t data_left = c->read_buffer_offset - c->h2.buff.r_cur_frame; + + mhd_assert (data_left <= c->read_buffer_offset); + + memmove (c->read_buffer, + c->read_buffer + c->h2.buff.unproc_hdrs_pos, + c->h2.buff.unproc_hdrs_size); + c->h2.buff.unproc_hdrs_pos = 0u; + + memmove (c->read_buffer + c->h2.buff.unproc_hdrs_size, + c->read_buffer + c->h2.buff.r_cur_frame, + data_left); + + c->h2.buff.r_cur_frame = c->h2.buff.unproc_hdrs_size; + c->read_buffer_offset = c->h2.buff.r_cur_frame + data_left; + } + + return (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state); +} diff --git a/src/mhd2/h2/h2_proc_in.h b/src/mhd2/h2/h2_proc_in.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_in.h + * @brief Declarations of HTTP/2 connection incoming data processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_PROC_IN_H +#define MHD_H2_PROC_IN_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +MHD_INTERNAL bool +mhd_h2_conn_process_in_data (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_PROC_IN_H */ diff --git a/src/mhd2/h2/h2_proc_out.c b/src/mhd2/h2/h2_proc_out.c @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_out.c + * @brief Implementation of HTTP/2 connection outgoing data processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_connection.h" + +#include "h2_err_codes.h" +#include "h2_frame_types.h" +#include "h2_frame_init.h" +#include "h2_frame_codec.h" + +#include "h2_proc_in.h" + +#include "h2_proc_out.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_out_buff_has_space_sz (struct MHD_Connection *restrict c, + size_t space_needed) +{ + size_t have_buff_space; + mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset); + + mhd_assert (! c->h2.dbg.w_buff_updating); + mhd_H2_W_BUFF_UPDATING_SET (&(c->h2)); + + have_buff_space = c->write_buffer_size - c->write_buffer_append_offset; + + mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2)); + + return (space_needed <= have_buff_space); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_out_buff_has_space_fr (struct MHD_Connection *restrict c, + union mhd_H2FrameUnion *restrict h2frame) +{ + return mhd_h2_out_buff_has_space_sz (c, + mhd_h2_frame_get_total_size (h2frame)); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_out_buff_acquire_fr_w_payload_l ( + struct MHD_Connection *restrict c, + const union mhd_H2FrameUnion *restrict h2frame, + uint_least32_t full_payload_limit, + struct mhd_Buffer *restrict buff, + size_t *restrict payload_offset) +{ + const size_t extra_hdr_size = mhd_h2_frame_get_extra_hdr_size (h2frame); + const size_t padding_size = mhd_h2_frame_get_padding_size (h2frame); + size_t w_buff_space; + + mhd_assert (! c->h2.dbg.w_buff_updating); + mhd_H2_W_BUFF_UPDATING_SET (&(c->h2)); + mhd_assert (c->write_buffer_size >= c->write_buffer_append_offset); + + if (full_payload_limit > c->h2.peer.max_frame_size) + full_payload_limit = (uint_least32_t) c->h2.peer.max_frame_size; + + w_buff_space = c->write_buffer_size - c->write_buffer_append_offset; + if (((mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_size + padding_size) + >= w_buff_space) || + ((extra_hdr_size + padding_size) >= full_payload_limit)) + { + mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2)); + return false; + } + + mhd_assert (c->h2.peer.max_frame_size > mhd_H2_FR_HDR_BASE_SIZE); + + if (full_payload_limit < (w_buff_space - mhd_H2_FR_HDR_BASE_SIZE)) + w_buff_space = full_payload_limit + mhd_H2_FR_HDR_BASE_SIZE; + + buff->data = c->write_buffer + c->write_buffer_append_offset; + buff->size = w_buff_space - padding_size; + *payload_offset = mhd_H2_FR_HDR_BASE_SIZE + extra_hdr_size; + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_out_buff_acquire_fr_w_payload ( + struct MHD_Connection *restrict c, + const union mhd_H2FrameUnion *restrict h2frame, + struct mhd_Buffer *restrict buff, + size_t *restrict payload_offset) +{ + return + mhd_h2_out_buff_acquire_fr_w_payload_l (c, + h2frame, + 0xFFFFFFFFu, + buff, + payload_offset); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_out_buff_unlock (struct MHD_Connection *restrict c, + size_t size_used) +{ + mhd_assert (c->h2.dbg.w_buff_updating); + mhd_assert (c->write_buffer_size >= size_used); + mhd_assert ((c->write_buffer_size - size_used) + >= c->write_buffer_append_offset); + + c->write_buffer_append_offset += size_used; + mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2)); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_q_frame_no_payload (struct MHD_Connection *restrict c, + union mhd_H2FrameUnion *restrict h2frame) +{ + size_t fr_hdr_size; + size_t w_buff_space; + bool succeed; + + mhd_assert (mhd_h2_frame_get_extra_hdr_size (h2frame) == + h2frame->selector.length); + + fr_hdr_size = mhd_H2_FR_HDR_BASE_SIZE + + mhd_h2_frame_get_extra_hdr_size (h2frame); + + mhd_assert (! c->h2.dbg.w_buff_updating); + mhd_H2_W_BUFF_UPDATING_SET (&(c->h2)); + + succeed = false; + w_buff_space = c->write_buffer_size - c->write_buffer_append_offset; + if (fr_hdr_size <= w_buff_space) + { + uint8_t *w_buff; + size_t written; + w_buff = (uint8_t *) c->write_buffer + c->write_buffer_append_offset; + + written = mhd_h2_frame_hdr_encode (h2frame, + w_buff_space, + w_buff); + mhd_assert (fr_hdr_size == written); + c->write_buffer_append_offset += written; + + succeed = true; + } + + mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2)); + return succeed; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_q_rst_stream (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + enum mhd_H2ErrorCode err) +{ + union mhd_H2FrameUnion h2frame; + + mhd_assert (0u != stream_id); + if (c->h2.state.top_rst_stream_id < stream_id) + c->h2.state.top_rst_stream_id = stream_id; + + mhd_h2_frame_init_rst_stream (&h2frame, + stream_id, + err); + + return mhd_h2_q_frame_no_payload (c, + &h2frame); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_q_ping (struct MHD_Connection *restrict c, + bool ack, + const uint8_t opaque_data[MHD_FN_PAR_FIX_ARR_SIZE_ (8)]) +{ + union mhd_H2FrameUnion h2frame; + + mhd_h2_frame_init_ping (&h2frame, + ack, + opaque_data); + + return mhd_h2_q_frame_no_payload (c, + &h2frame); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_q_goaway (struct MHD_Connection *restrict c, + enum mhd_H2ErrorCode err) +{ + union mhd_H2FrameUnion h2frame; + + mhd_h2_frame_init_goaway (&h2frame, + c->h2.state.top_proc_stream_id, + err); + + return mhd_h2_q_frame_no_payload (c, + &h2frame); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_q_window_update (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + uint_least32_t win_size_incr) +{ + union mhd_H2FrameUnion h2frame; + + mhd_assert (0u != win_size_incr); + + mhd_h2_frame_init_window_update (&h2frame, + stream_id, + win_size_incr); + + return mhd_h2_q_frame_no_payload (c, + &h2frame); +} diff --git a/src/mhd2/h2/h2_proc_out.h b/src/mhd2/h2/h2_proc_out.h @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_out.h + * @brief Declarations of HTTP/2 connection outgoing data processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_PROC_OUT_H +#define MHD_H2_PROC_OUT_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "h2_err_codes.h" + +struct MHD_Connection; /* forward declaration */ +struct mhd_Buffer; /* forward declaration */ +union mhd_H2FrameUnion; /* forward declaration */ + +/** + * Check whether the output buffer has at least specified free space. + * @param c the connection to check + * @param space_needed the amount of free space needed + * @return 'true' if output buffer has enough space, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_h2_out_buff_has_space_sz (struct MHD_Connection *restrict c, + size_t space_needed) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Check whether the output buffer has enough space to add the frame (together + * with optional payload). + * @param c the connection to check + * @param h2frame the frame to use, the 'type', all flags and the 'length' + * must be set + * @return 'true' if output buffer has enough space, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_h2_out_buff_has_space_fr (struct MHD_Connection *restrict c, + union mhd_H2FrameUnion *restrict h2frame) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Acquire the output buffer for specified frame with undefined payload size. + * + * The @a h2frame must have all information set except the length of the frame. + * The provided @a buff is not larger than maximum frame size allowed for + * connection. If @a h2frame has padding, then the size for the padding is + * reserved at the end of the @a buff (but not included to the @a buff size). + * + * In the debug builds this function "locks" the output buffer if succeed. + * The caller must always call #mhd_h2_out_buff_unlock() after finishing + * working with the buffer even if nothing is written to the buffer. + * + * @param c the connection to use + * @param h2frame the frame to use, the 'type' and all flags must be set, + * the 'length' is ignored + * @param[out] buff set to the acquired space in the buffer, the size is always + * larger than the basic header and the extra header; + * the space reserved for the padding is not included + * @param[out] payload_offset set to the offset where to put the frame payload + * @return 'true' if output buffer has enough space for the frame header and at + * least one byte for the payload, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_h2_out_buff_acquire_fr_w_payload ( + struct MHD_Connection *restrict c, + const union mhd_H2FrameUnion *restrict h2frame, + struct mhd_Buffer *restrict buff, + size_t *restrict payload_offset) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Acquire the output buffer for specified frame with undefined payload size. + * + * Version of #mhd_h2_out_buff_acquire_fr_w_payload() with predefined limit + * for the frame full payload size. + * + * @param c the connection to use + * @param h2frame the frame to use, the 'type' and all flags must be set, + * the 'length' is ignored + * @param full_payload_limit the maximum size of the frame (excluding base + * frame header) + * @param[out] buff set to the acquired space in the buffer, the size is always + * larger than the basic header and the extra header; + * the space reserved for the padding is not included + * @param[out] payload_offset set to the offset where to put the frame payload + * @return 'true' if output buffer has enough space for the frame header and at + * least one byte for the payload, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_h2_out_buff_acquire_fr_w_payload_l ( + struct MHD_Connection *restrict c, + const union mhd_H2FrameUnion *restrict h2frame, + uint_least32_t full_payload_limit, + struct mhd_Buffer *restrict buff, + size_t *restrict payload_offset) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Finish writing the data to the output buffer. + * The output buffer must be previously acquired by calling + * #mhd_h2_out_buff_acquire_fr_w_payload(). + * @param c the connection to use + * @param size_used the size of the frame written; if frame had padding then + * it could be larger (by the padding size) than the size of + * the buffer provided by #mhd_h2_out_buff_acquire_fr_w_payload() + */ +MHD_INTERNAL void +mhd_h2_out_buff_unlock (struct MHD_Connection *restrict c, + size_t size_used) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Queue to the sending buffer a frame without payload + * @param c the connection to use + * @param h2frame the frame to queue + * @return 'true' if the frame is successfully added, + * 'false' if sending buffer has not enough space + */ +MHD_INTERNAL bool +mhd_h2_q_frame_no_payload (struct MHD_Connection *restrict c, + union mhd_H2FrameUnion *restrict h2frame) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Queue to the sending buffer a RST_STREAM frame + * @param c the connection to use + * @param stream_id the stream ID for the frame + * @param err the HTTP/2 error code + * @return 'true' if the frame is successfully added, + * 'false' if sending buffer has not enough space + */ +MHD_INTERNAL bool +mhd_h2_q_rst_stream (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + enum mhd_H2ErrorCode err) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Queue to the sending buffer a PING frame + * @param c the connection to use + * @param ack the value of the ACK flag in the frame + * @param opaque the PING opaque data + * @return 'true' if the frame is successfully added, + * 'false' if sending buffer has not enough space + */ +MHD_INTERNAL bool +mhd_h2_q_ping (struct MHD_Connection *restrict c, + bool ack, + const uint8_t opaque[MHD_FN_PAR_FIX_ARR_SIZE_ (8)]) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Queue to the sending buffer a GOAWAY frame without additional debug info + * @param c the connection to use + * @param err the HTTP/2 error code + * @return 'true' if the frame is successfully added, + * 'false' if sending buffer has not enough space + */ +MHD_INTERNAL bool +mhd_h2_q_goaway (struct MHD_Connection *restrict c, + enum mhd_H2ErrorCode err) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Queue to the sending buffer a WINDOW_UPDATE frame + * @param c the connection to use + * @param stream_id the stream ID for the frame + * @param win_size_incr the Window Size Increment value; + * must not be zero, must fit 31 bits + * @return 'true' if the frame is successfully added, + * 'false' if sending buffer has not enough space + */ +MHD_INTERNAL bool +mhd_h2_q_window_update (struct MHD_Connection *restrict c, + uint_least32_t stream_id, + uint_least32_t win_size_incr) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_PROC_OUT_H */ diff --git a/src/mhd2/h2/h2_proc_settings.c b/src/mhd2/h2/h2_proc_settings.c @@ -0,0 +1,323 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_settings.c + * @brief Implementation of HTTP/2 connection settings processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_connection.h" + +#include "h2_frame_types.h" +#include "h2_frame_init.h" +#include "h2_settings.h" + +#include "h2_frame_codec.h" +#include "hpack/mhd_hpack_codec.h" +#include "h2_proc_conn.h" +#include "h2_proc_out.h" + +#include "h2_proc_settings.h" + + +static bool +h2_has_space_for_ack (struct MHD_Connection *restrict c) +{ +#ifndef NDEBUG + if (1) + { + union mhd_H2FrameUnion h2frame; + + mhd_h2_frame_init_settings (&h2frame, + true); + mhd_assert (0u == mhd_h2_frame_get_extra_hdr_size (&h2frame)); + } +#endif /* NDEBUG */ + return mhd_h2_out_buff_has_space_sz (c, + mhd_H2_FR_HDR_BASE_SIZE); +} + + +static bool +h2_q_settings_ack (struct MHD_Connection *restrict c) +{ + union mhd_H2FrameUnion h2frame; + + mhd_h2_frame_init_settings (&h2frame, + true); + /* The actual size should match free space check */ + mhd_assert (0u == mhd_h2_frame_get_extra_hdr_size (&h2frame)); + + return mhd_h2_q_frame_no_payload (c, + &h2frame); +} + + +MHD_INTERNAL bool +mhd_h2_proc_first_settings (struct MHD_Connection *restrict c, + const struct mhd_Buffer *restrict stngs_payload) +{ + bool ack_succeed; + + /* The payload size must be checked by frame decoder */ + mhd_assert (0u == (stngs_payload->size % 6u)); + + mhd_assert (h2_has_space_for_ack (c)); + + if (stngs_payload->size > 6u) + { + size_t pos; + + for (pos = 0u; pos < (stngs_payload->size - 5u); pos += 6u) + { + struct mhd_H2Setting stng; + mhd_h2_setting_decode ((uint8_t *) stngs_payload->data + pos, + &stng); + + switch (stng.identifier) + { + case mhd_H2_STNGS_HEADER_TABLE_SIZE: + if (mhd_DTBL_MAX_SIZE >= stng.value) + { + if (4096u >= stng.value) // TODO: take the limit from the daemon + mhd_hpack_enc_set_dyn_size (&(c->h2.hk_enc), + (size_t) stng.value); + } + break; + case mhd_H2_STNGS_ENABLE_PUSH: + /* Ignored */ + break; + case mhd_H2_STNGS_CONCURRENT_STREAMS: + c->h2.peer.max_concur_streams = stng.value; + break; + case mhd_H2_STNGS_INITIAL_WINDOW_SIZE: + if (0x7FFFFFFFu < stng.value) + { + mhd_h2_conn_finish (c, + mhd_H2_ERR_FLOW_CONTROL_ERROR, + true); + return false; /* Failure exit point */ + } + + /* Set the initial size. No streams should be modified as no + streams have been started yet. */ + c->h2.peer.stream_init_win_sz = stng.value; + break; + case mhd_H2_STNGS_MAX_FRAME_SIZE: + if ((mhd_H2_STNG_MIN_MAX_FRAME_SIZE > stng.value) || + (mhd_H2_STNG_MAX_MAX_FRAME_SIZE < stng.value)) + { + mhd_h2_conn_finish (c, + mhd_H2_ERR_PROTOCOL_ERROR, + true); + return false; /* Failure exit point */ + } +#if 0 // TODO: use limit from the daemon settings + if (mhd_H2_STNG_DEF_MAX_FRAME_SIZE >= stng.value) + c->h2.peer.max_frame_size = stng.value; +#endif + break; + case mhd_H2_STNGS_MAX_HEADER_LIST_SIZE: + c->h2.peer.max_header_list = stng.value; + break; + case mhd_H2_STNGS_ENABLE_CONNECT_PROTOCOL: + case mhd_H2_STNGS_NO_RFC7540_PRIORITIES: + /* Ignored */ + break; +#ifndef mhd_USE_ENUM_BASE_T + case mhd_H2_STNGS_SENTINEL: +#endif /*mhd_USE_ENUM_BASE_T */ + default: + /* Unknown setting ignored */ + break; + } + } + } + c->h2.state.init.got_setns = true; + + ack_succeed = h2_q_settings_ack (c); + mhd_assert (ack_succeed); + (void) ack_succeed; + + return true; /* Success exit point */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_H2SettingsProcessResult +mhd_h2_proc_new_settings (struct MHD_Connection *restrict c, + const struct mhd_Buffer *restrict stngs_payload) +{ + (void) c; (void) stngs_payload; // TODO: implement + return mhd_H2_STNGS_PROC_STNGS_ERR; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_q_settings_first_fr (struct MHD_Connection *restrict c) +{ + size_t fr_hdr_size; + size_t final_fr_hdr_size; + size_t payload_space; + uint8_t *payload; + size_t payload_pos; + bool set_all_settings; + struct mhd_Buffer buff; + union mhd_H2FrameUnion h2frame; + + mhd_h2_frame_init_settings (&h2frame, + false); + + /* This is the first data sent by the server */ + mhd_assert (0u == c->write_buffer_append_offset); + + if (! mhd_h2_out_buff_acquire_fr_w_payload (c, + &h2frame, + &buff, + &fr_hdr_size)) + { + mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2)); + mhd_h2_conn_finish (c, + mhd_H2_ERR_INTERNAL_ERROR, + true); + return false; + } + + payload = (uint8_t *) buff.data + fr_hdr_size; + payload_space = buff.size - fr_hdr_size; + + if (payload_space != (payload_space & 0xFFFFFFFFu)) + payload_space = 0xFFFFFFFFu; + + payload_pos = 0u; + + // TODO: use configurable values for settings + if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE) + { + mhd_h2_setting_encode3 (mhd_H2_STNGS_HEADER_TABLE_SIZE, + (uint_least32_t) c->h2.hk_dec.max_allowed_dyn_size, + payload + payload_pos); + payload_pos += mhd_H2_SETTING_SIZE; + } + + if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE) + { + mhd_h2_setting_encode3 (mhd_H2_STNGS_ENABLE_PUSH, + 0u, + payload + payload_pos); + payload_pos += mhd_H2_SETTING_SIZE; + } + + if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE) + { + mhd_assert (0x7FFFFFFFu >= c->h2.rcv_cfg.stream_init_win_sz); + mhd_h2_setting_encode3 (mhd_H2_STNGS_INITIAL_WINDOW_SIZE, + c->h2.rcv_cfg.stream_init_win_sz, + payload + payload_pos); + payload_pos += mhd_H2_SETTING_SIZE; + } + + if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE) + { + mhd_assert (0x7FFFFFFFu >= c->h2.rcv_cfg.max_frame_size); + mhd_h2_setting_encode3 (mhd_H2_STNGS_MAX_FRAME_SIZE, + c->h2.rcv_cfg.max_frame_size, + payload + payload_pos); + payload_pos += mhd_H2_SETTING_SIZE; + } + + if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE) + { + mhd_h2_setting_encode3 (mhd_H2_STNGS_MAX_HEADER_LIST_SIZE, + c->h2.rcv_cfg.max_header_list, + payload + payload_pos); + payload_pos += mhd_H2_SETTING_SIZE; + } + + if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE) + { + mhd_h2_setting_encode3 (mhd_H2_STNGS_ENABLE_CONNECT_PROTOCOL, + 0u, + payload + payload_pos); + payload_pos += mhd_H2_SETTING_SIZE; + } + + if (payload_space >= payload_pos + mhd_H2_SETTING_SIZE) + { + mhd_h2_setting_encode3 (mhd_H2_STNGS_NO_RFC7540_PRIORITIES, + 1u, + payload + payload_pos); + payload_pos += mhd_H2_SETTING_SIZE; + set_all_settings = true; + } + else + set_all_settings = false; + + if (! set_all_settings) + { + mhd_H2_W_BUFF_UPDATING_CLEAR (&(c->h2)); + mhd_h2_conn_finish (c, + mhd_H2_ERR_INTERNAL_ERROR, + true); + return false; + } + + mhd_assert (0u == payload_pos % mhd_H2_SETTING_SIZE); + + mhd_h2_frame_set_payload_size (&h2frame, + payload_pos); + + final_fr_hdr_size = mhd_h2_frame_hdr_encode (&h2frame, + buff.size, + (uint8_t *) buff.data); + mhd_assert (fr_hdr_size == final_fr_hdr_size); + mhd_h2_out_buff_unlock (c, + final_fr_hdr_size + payload_pos); + + c->h2.state.init.sent_setns = true; + ++c->h2.state.sent_setns_noakc; + + return true; +} diff --git a/src/mhd2/h2/h2_proc_settings.h b/src/mhd2/h2/h2_proc_settings.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_proc_settings.h + * @brief Declarations of HTTP/2 connection settings processing functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_PROC_SETTINGS_H +#define MHD_H2_PROC_SETTINGS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +/** + * Default value of Initial Window Size + */ +#define mhd_H2_STNG_DEF_INIT_WIN_SIZE (65535u) + +/** + * Minimal allowed value of Maximum Frame Size + */ +#define mhd_H2_STNG_MIN_MAX_FRAME_SIZE (16384u) + +/** + * Maximum allowed value of Maximum Frame Size + */ +#define mhd_H2_STNG_MAX_MAX_FRAME_SIZE (16777215u) + +/** + * Default value of Maximum Frame Size + */ +#define mhd_H2_STNG_DEF_MAX_FRAME_SIZE mhd_H2_STNG_MIN_MAX_FRAME_SIZE + +struct MHD_Connection; /* forward declaration */ +struct mhd_Buffer; /* forward declaration */ + +enum mhd_H2SettingsProcessResult +{ + /** + * Settings processed. + * Settings ACK queued. + */ + mhd_H2_STNGS_PROC_OK = 0 + , + /** + * No output buffer space to queue settings ACK. + * The frame should be processed later again + */ + mhd_H2_STNGS_PROC_NO_OUT_BUFF + , + /** + * Settings error. + * GOAWAY frame queued, connection marked as closing + */ + mhd_H2_STNGS_PROC_STNGS_ERR +}; + +/** + * Process first settings frame sent by peer + * @param c the connection to use + * @param stngs_payload the payload of the settings frame + * @return 'true' if setting successfully processed; + * 'false' on failure, GOAWAY queued if possible and connection + * is marked as "closing" or "broken". + */ +MHD_INTERNAL bool +mhd_h2_proc_first_settings (struct MHD_Connection *restrict c, + const struct mhd_Buffer *restrict stngs_payload) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL enum mhd_H2SettingsProcessResult +mhd_h2_proc_new_settings (struct MHD_Connection *restrict c, + const struct mhd_Buffer *restrict stngs_payload) +MHD_FN_PAR_NONNULL_ALL_; + + +MHD_INTERNAL bool +mhd_h2_q_settings_first_fr (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_PROC_SETTINGS_H */ diff --git a/src/mhd2/h2/h2_reply_funcs.c b/src/mhd2/h2/h2_reply_funcs.c @@ -0,0 +1,599 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_reply_funcs.c + * @brief Definitions of HTTP/2 reply sending functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_str_macros.h" + +#include "mhd_constexpr.h" + +#include "mhd_assert.h" +#include "mhd_unreachable.h" + +#include "mhd_buffer.h" +#include "mhd_response.h" +#include "mhd_connection.h" +#include "mhd_daemon.h" + +#include "mhd_str.h" +#include "mhd_read_file.h" + +#include "stream_process_reply.h" + +#include "h2_conn_data.h" +#include "h2_stream_data.h" + +#include "h2_frame_init.h" +#include "h2_proc_conn.h" +#include "h2_proc_out.h" + +#include "h2_frame_codec.h" + +#include "hpack/mhd_hpack_codec.h" + + +#include "h2_reply_funcs.h" + +struct mhd_H2Stream; /* Forward declaration */ + +/* local wrapper */ +mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_IN_ (2) MHD_FN_PAR_IN_ (3) +MHD_FN_PAR_OUT_SIZE_ (6,5) MHD_FN_PAR_OUT_ (7) bool +enc_field (struct mhd_HpackEncContext *restrict hk_enc, + const struct mhd_BufferConst *restrict name, + const struct mhd_BufferConst *restrict value, + enum mhd_HpackEncPolicy enc_pol, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + enum mhd_HpackEncResult enc_res; + + enc_res = mhd_hpack_enc_field (hk_enc, + name, + value, + enc_pol, + out_buff_size, + out_buff, + bytes_encoded); + + mhd_assert (mhd_HPACK_ENC_RES_ALLOC_ERR != enc_res); + + return (mhd_HPACK_ENC_RES_OK == enc_res); +} + + +static MHD_FN_PAR_NONNULL_ALL_ size_t +stream_headers_encode (struct mhd_H2Stream *restrict s, + struct mhd_Buffer *restrict pl, + bool *restrict fields_complete) +{ + struct mhd_HpackEncContext *const hk_enc = &(s->c->h2.hk_enc); + struct MHD_Response *const r = s->rpl.response; + uint8_t *restrict buff = (uint8_t *) pl->data; + size_t pos; + size_t pos_incr; + size_t fld_num; + enum mhd_HpackEncResult enc_res; + struct mhd_ResponseHeader *hdr; + + *fields_complete = false; /* Could be updated at the end */ + pos = 0u; + fld_num = 0u; + + /* Pseudo-header */ + if (fld_num >= s->rpl.fields.num_sent) + { + enc_res = mhd_hpack_enc_ph_status (hk_enc, + (uint_fast16_t) s->rpl.response->sc, + mhd_HPACK_ENC_PFS_POL_NORMAL, + pl->size - pos, + buff + pos, + &pos_incr); + mhd_assert (mhd_HPACK_ENC_RES_ALLOC_ERR != enc_res); + if (mhd_HPACK_ENC_RES_OK != enc_res) + return pos; + + pos += pos_incr; + ++(s->rpl.fields.num_sent); + } + ++fld_num; + + /* "date" header */ + + if ( (! r->cfg.has_hdr_date) && + (! s->c->daemon->req_cfg.suppress_date) ) + { + if (fld_num >= s->rpl.fields.num_sent) + { + char val_buff[30]; + if (mhd_build_date_str (val_buff)) + { + static const struct mhd_BufferConst hdr_name = + mhd_MSTR_INIT ("date"); + struct mhd_BufferConst hdr_val; + + hdr_val.data = val_buff; + hdr_val.size = 29u; + + if (! enc_field (hk_enc, + &hdr_name, + &hdr_val, + 1 >= s->c->h2.streams.num_streams ? + mhd_HPACK_ENC_POL_LOW_PRIO : mhd_HPACK_ENC_POL_NEUTRAL, + pl->size - pos, + buff + pos, + &pos_incr)) + return pos; + + pos += pos_incr; + ++(s->rpl.fields.num_sent); + } + } + ++fld_num; + } + + /* "content-length" header */ + if (s->rpl.fields.auto_cntn_len) + { + if (fld_num >= s->rpl.fields.num_sent) + { + static const struct mhd_BufferConst hdr_name = + mhd_MSTR_INIT ("content-length"); + char val_buff[21]; /* Maximum supported value is 18446744073709551615 */ + struct mhd_BufferConst hdr_val; + + mhd_assert (MHD_SIZE_UNKNOWN > r->cntn_size); + hdr_val.data = val_buff; + hdr_val.size = mhd_uint64_to_str (r->cntn_size, + val_buff, + sizeof(val_buff)); + mhd_assert (0u != hdr_val.size); + + if (! enc_field (hk_enc, + &hdr_name, + &hdr_val, + r->reuse.reusable ? + mhd_HPACK_ENC_POL_NEUTRAL : mhd_HPACK_ENC_POL_LOW_PRIO, + pl->size - pos, + buff + pos, + &pos_incr)) + return pos; + + pos += pos_incr; + ++(s->rpl.fields.num_sent); + } + ++fld_num; + } + + /* User headers */ + + for (hdr = mhd_DLINKEDL_GET_FIRST (r, headers); + NULL != hdr; + hdr = mhd_DLINKEDL_GET_NEXT (hdr, headers)) + { + if (NULL == hdr->h2.name.data) + continue; /* The header is HTTP/1.x only */ + + if (fld_num >= s->rpl.fields.num_sent) + { + if (! enc_field (hk_enc, + &(hdr->h2.name), + &(hdr->h2.value), + mhd_HPACK_ENC_POL_NEUTRAL, + pl->size - pos, + buff + pos, + &pos_incr)) + return pos; + pos += pos_incr; + ++(s->rpl.fields.num_sent); + } + ++fld_num; + } + + *fields_complete = true; + return pos; +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +stream_headers_send (struct mhd_H2Stream *s) +{ + union mhd_H2FrameUnion h2frame; + struct mhd_H2FrameHeadersInfo *hdrs; + struct mhd_H2FrameContinuationInfo *cont; + struct mhd_Buffer buff; + struct mhd_Buffer payload; + size_t payload_offset; + bool *complete_header; + size_t payload_used; + + mhd_assert (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage); + + if (0u == s->rpl.fields.num_sent) + { + hdrs = mhd_h2_frame_init_headers (&h2frame, + s->stream_id, + false, /* could be updated below */ + ! s->rpl.send_content); + cont = NULL; + complete_header = &(hdrs->end_headers); + } + else + { + hdrs = NULL; + cont = mhd_h2_frame_init_continuation (&h2frame, + s->stream_id, + false); /* could be updated below */ + complete_header = &(cont->end_headers); + } + + if (! mhd_h2_out_buff_acquire_fr_w_payload (s->c, + &h2frame, + &buff, + &payload_offset)) + return false; + + payload.data = buff.data + payload_offset; + payload.size = buff.size - payload_offset; + + payload_used = stream_headers_encode (s, + &payload, + complete_header); + + if (0u != payload_used) + { + const size_t full_fr_size = mhd_h2_frame_set_payload_size (&h2frame, + payload_used); + const size_t final_fr_hdr_size = + mhd_h2_frame_hdr_encode (&h2frame, + payload_offset, + (uint8_t*) buff.data); + mhd_assert (payload_offset == final_fr_hdr_size); + (void) final_fr_hdr_size; + + mhd_h2_out_buff_unlock (s->c, + full_fr_size); + if (*complete_header) + { + s->rpl.stage = s->rpl.send_content ? + mhd_H2_RPL_STAGE_HEADERS_COMPLETE : + mhd_H2_RPL_STAGE_END_STREAM; + } + return true; /* Success exit point */ + } + + mhd_h2_out_buff_unlock (s->c, + 0u); + + if (((s->c->write_buffer_size - s->c->write_buffer_append_offset) >= + mhd_H2_FR_HDR_BASE_SIZE + s->c->h2.peer.max_frame_size) || + (0u == s->c->write_buffer_append_offset)) + { + /* The output buffer may contain the maximum size frame, but no single + header has been added. It makes no sense to wait more as the + response header is too large to be used in this connection. */ + s->state.mhd_err = mhd_H2_ERR_INTERNAL_ERROR; + s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN; + return false; + } + + return false; +} + + +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (4,3) +MHD_FN_PAR_OUT_ (5) bool +content_read_iovec (struct MHD_Response *restrict r, + uint_fast64_t offset, + size_t buff_size, + uint8_t *restrict buff, + size_t *restrict written) +{ + size_t i; + uint_fast64_t skipped; + const mhd_iovec *const restrict iov = r->cntn.iovec.iov; + + mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype); + + skipped = 0u; + + for (i = 0u; r->cntn.iovec.cnt > i; ++i) + { + if (skipped + iov[i].iov_len > offset) + break; + skipped += iov[i].iov_len; + mhd_assert (skipped >= iov[i].iov_len); + } + + if (r->cntn.iovec.cnt == i) + return false; + + if (1) + { + size_t elmnt_copy; + const size_t elmnt_off = (size_t) (offset - skipped); + + if (elmnt_off != (offset - skipped)) + return false; + + mhd_assert (0u != iov[i].iov_len); + + elmnt_copy = (size_t) (iov[i].iov_len - elmnt_off); + if (buff_size < elmnt_copy) + elmnt_copy = buff_size; + + memcpy (buff, + ((const uint8_t *) iov[i].iov_base) + elmnt_off, + elmnt_copy); + *written = elmnt_copy; + + if (elmnt_copy == buff_size) + return true; + + ++i; + } + + for ((void) i; r->cntn.iovec.cnt > i; ++i) + { + mhd_assert (0u != iov[i].iov_len); + if ((buff_size - *written) <= iov[i].iov_len) + { + memcpy (buff + *written, + iov[i].iov_base, + buff_size - *written); + *written = buff_size; + return true; + } + memcpy (buff + *written, + iov[i].iov_base, + (size_t) iov[i].iov_len); + *written += (size_t) iov[i].iov_len; + mhd_assert (*written > iov[i].iov_len); + } + return true; +} + + +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (4,3) +MHD_FN_PAR_OUT_ (5) bool +content_read_file (struct MHD_Response *restrict r, + uint_fast64_t offset, + size_t buff_size, + uint8_t *restrict buff, + size_t *restrict written) +{ + uint_fast64_t file_off; + + mhd_assert (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype); + // TODO: support pipe reading without position + mhd_assert (! r->cntn.file.is_pipe); + + file_off = offset + r->cntn.file.offset; + if (file_off < offset) + return false; /* Offset too large */ + + return (mhd_FILE_READ_OK == + mhd_read_file (r->cntn.file.fd, + file_off, + buff_size, + (char*) buff, + written)); +} + + +mhd_constexpr uint_least32_t min_size_for_data = 128u; + +static MHD_FN_PAR_NONNULL_ALL_ bool +stream_content_send (struct mhd_H2Stream *s) +{ + struct MHD_Response *const r = s->rpl.response; + union mhd_H2FrameUnion h2frame; + struct mhd_H2FrameDataInfo *dat; + struct mhd_Buffer buff; + uint8_t *pld_buff; + size_t pld_buff_size; + size_t cntnt_left; + size_t payload_offset; + size_t payload_used; + int_least32_t wndw_limit; + uint_least32_t full_payload_limit; + + mhd_assert (mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage); + mhd_assert (s->rpl.send_content); + mhd_assert (0u != r->cntn_size); + mhd_assert (! r->cfg.head_only); + + if (s->c->h2.state.send_window < s->state.send_window) + wndw_limit = s->c->h2.state.send_window; + else + wndw_limit = s->state.send_window; + + if (0 >= wndw_limit) + return false; /* The peer should increment window(s) first */ + + full_payload_limit = (uint_least32_t) wndw_limit; + if (MHD_SIZE_UNKNOWN != r->cntn_size) + { + cntnt_left = r->cntn_size - s->rpl.cntn_read_pos; + if (cntnt_left < full_payload_limit) + full_payload_limit = (uint_least32_t) cntnt_left; + } + else + cntnt_left = MHD_SIZE_UNKNOWN; + + if ((min_size_for_data > full_payload_limit) + && (cntnt_left != full_payload_limit)) + return false; + + dat = mhd_h2_frame_init_data (&h2frame, + s->stream_id, + false); /* could be updated below */ + + if (! mhd_h2_out_buff_acquire_fr_w_payload_l (s->c, + &h2frame, + full_payload_limit, + &buff, + &payload_offset)) + return false; + + pld_buff = (uint8_t*) buff.data + payload_offset; + pld_buff_size = buff.size - payload_offset; + mhd_assert (mhd_H2_FR_HDR_BASE_SIZE < pld_buff_size); + + mhd_assert (r->cntn_size > s->rpl.cntn_read_pos); + + payload_used = 0u; + switch (r->cntn_dtype) + { + case mhd_RESPONSE_CONTENT_DATA_BUFFER: + payload_used = (size_t) full_payload_limit; + memcpy (pld_buff, + r->cntn.buf + s->rpl.cntn_read_pos, + payload_used); + break; + case mhd_RESPONSE_CONTENT_DATA_IOVEC: + if (! content_read_iovec (r, + s->rpl.cntn_read_pos, + pld_buff_size, + pld_buff, + &payload_used)) + payload_used = 0u; + break; + case mhd_RESPONSE_CONTENT_DATA_FILE: + if (! content_read_file (r, + s->rpl.cntn_read_pos, + pld_buff_size, + pld_buff, + &payload_used)) + payload_used = 0u; + break; + case mhd_RESPONSE_CONTENT_DATA_CALLBACK: + s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN; + break; + case mhd_RESPONSE_CONTENT_DATA_INVALID: + default: + mhd_UNREACHABLE (); + s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN; + break; + } + + dat->end_stream = (payload_used + s->rpl.cntn_read_pos == r->cntn_size); + + if (0u != payload_used) + { + const size_t full_fr_size = mhd_h2_frame_set_payload_size (&h2frame, + payload_used); + const size_t final_fr_hdr_size = + mhd_h2_frame_hdr_encode (&h2frame, + payload_offset, + (uint8_t*) buff.data); + mhd_assert (payload_offset == final_fr_hdr_size); + (void) final_fr_hdr_size; + + mhd_h2_out_buff_unlock (s->c, + full_fr_size); + s->c->h2.state.send_window -= + (int_least32_t) (full_fr_size - mhd_H2_FR_HDR_BASE_SIZE); + mhd_assert (0 <= s->c->h2.state.send_window); + s->state.send_window -= + (int_least32_t) (full_fr_size - mhd_H2_FR_HDR_BASE_SIZE); + mhd_assert (0 <= s->state.send_window); + + return true; /* Success exit point */ + } + + mhd_h2_out_buff_unlock (s->c, + 0u); + + s->state.mhd_err = mhd_H2_ERR_INTERNAL_ERROR; + s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN; + + return false; + +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_stream_reply_send (struct mhd_H2Stream *s) +{ + mhd_assert (s->is_h2); + mhd_assert (mhd_H2_RPL_STAGE_END_STREAM != s->rpl.stage); + mhd_assert (mhd_H2_RPL_STAGE_BROKEN != s->rpl.stage); + + if (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage) + { + if (! mhd_hpack_enc_dyn_resize (&(s->c->h2.hk_enc))) + { + /* Ignore failure of the next function as the connection and stream + will be retried next round if connection is not aborted. */ + mhd_h2_conn_finish (s->c, + mhd_H2_ERR_INTERNAL_ERROR, + false); + return false; + } + + if (! stream_headers_send (s)) + return false; + + if ((mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage) && + (mhd_RESPONSE_CONTENT_DATA_FILE <= s->rpl.response->cntn_dtype)) + return true; /* Do not combine with content sending as the data is not ready yet */ + } + + if (mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage) + { + if (! stream_content_send (s)) + return false; + } + + return true; + +} diff --git a/src/mhd2/h2/h2_reply_funcs.h b/src/mhd2/h2/h2_reply_funcs.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_reply_funcs.h + * @brief Declarations of HTTP/2 reply sending functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_REPLY_FUNCS_H +#define MHD_H2_REPLY_FUNCS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct mhd_H2Stream; /* Forward declaration */ + +MHD_INTERNAL bool +mhd_h2_stream_reply_send (struct mhd_H2Stream *s) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_H2_REPLY_FUNCS_H */ diff --git a/src/mhd2/h2/h2_req_data.h b/src/mhd2/h2/h2_req_data.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_data.h + * @brief Definition of HTTP/2 request data structure + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_REQ_DATA_H +#define MHD_H2_REQ_DATA_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "http_method.h" + +struct MHD_Connection; /* Forward declaration */ + + +enum MHD_FIXED_ENUM_ mhd_H2ReqStage +{ + /** + * Headers are not completely received. + * Processing the opening HEADERS frame or receiving and processing + * CONTINUATION frames. + */ + mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE + , + mhd_H2_REQ_STAGE_HEADERS_DECODING + , + mhd_H2_REQ_STAGE_HEADERS_PROCESSING + , + /** + * Headers are completely received. + * DATA frames or second HEADERS frame (containing trailers) can be received. + */ + mhd_H2_REQ_STAGE_HEADERS_COMPLETE + , + /** + * Trailers are not completely received. + * Processing the second HEADERS frame (which started trailers) or receiving + * and processing CONTINUATION frames. + */ + mhd_H2_REQ_STAGE_TRAILERS_INCOMPLETE + , + mhd_H2_REQ_STAGE_TRAILERS_DECODING + , + mhd_H2_REQ_STAGE_TRAILERS_PROCESSING + , + /** + * The client must not send any HEADERS or DATA frames. + */ + mhd_H2_REQ_STAGE_END_STREAM + , + /** + * Any frames ignored with RST_STREAM. + */ + mhd_H2_REQ_STAGE_BROKEN + +}; + +struct mhd_H2RequestData +{ + /** + * Always 'true' + */ + bool is_http2; + + enum mhd_H2ReqStage stage; + + /** + * 'true' when 'end stream' flag was received. The stage could be still + * #mhd_H2_REQ_STAGE_HEADERS_INCOMPLETE or + * #mhd_H2_REQ_STAGE_TRAILERS_INCOMPLETE as CONTINUATION frames are being + * processed + */ + bool got_end_stream; + + enum mhd_HTTP_Method method; + + uint_fast64_t cntn_size; + + /** + * Position of ":method" pseudo-header in request items block. + * Set to #mhd_H2_REQ_ITEM_POS_INVALID if not available. + */ + size_t pos_method; + /** + * Position of ":path" pseudo-header in request items block. + * Set to #mhd_H2_REQ_ITEM_POS_INVALID if not available. + */ + size_t pos_path; + /** + * Position of ":authority" pseudo-header or "Host" header in request items + * block. + * Set to #mhd_H2_REQ_ITEM_POS_INVALID if not available. + */ + size_t pos_authority; + + struct mhd_ApplicationAction app_act; + + /** + * Set to 'true' when application gets any information about this + * request or stream. + */ + bool app_seen; + + void *app_context; +}; + +#endif /* ! MHD_H2_REQ_DATA_H */ diff --git a/src/mhd2/h2/h2_req_fields.c b/src/mhd2/h2/h2_req_fields.c @@ -0,0 +1,523 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_dec_fields.c + * @brief Implementation of HTTP/2 request fields functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include <string.h> + +#include "mhd_assert.h" +#include "mhd_unreachable.h" +#include "mhd_assume.h" + +#include "mhd_constexpr.h" +#include "mhd_buffer.h" +#include "mhd_str_types.h" +#include "mhd_str_macros.h" + +#include "mhd_connection.h" +#include "h2_conn_data.h" + +#include "h2_stream_data.h" + +#include "mhd_str.h" + +#include "stream_process_request.h" + +#include "h2_req_item_kinds.h" +#include "h2_req_item_struct.h" +#include "h2_req_items_funcs.h" + +#include "hpack/mhd_hpack_codec.h" + +#include "h2_proc_conn.h" +#include "h2_conn_streams.h" + +#include "h2_req_fields.h" + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_IN_ (2) +MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) enum mhd_H2DecFieldsResult +mhd_h2_req_fields_decode (struct mhd_HpackDecContext *restrict hk_dec, + const struct mhd_Buffer *restrict enc_data, + bool are_trailers, + struct mhd_H2ReqItemsBlock *restrict ib, + size_t *restrict left_unprocessed) +{ + mhd_constexpr unsigned int max_no_fields = 8u; + const enum mhd_H2RequestItemKind field_kind = + are_trailers ? mhd_H2_RIK_TRAILER : mhd_H2_RIK_HEADER; + size_t pos; + unsigned int no_fields; + enum mhd_H2DecFieldsResult ret; + + pos = 0u; + no_fields = 0u; + ret = mhd_H2_DEC_FIELDS_OK; + + while (pos < enc_data->size) + { + size_t name_len; + size_t val_len; + size_t pos_incr; + enum mhd_HpackDecResult res; + struct mhd_Buffer buff; + + mhd_assert (mhd_H2_DEC_FIELDS_OK == ret); + + if (! mhd_h2_items_get_buff_new_item (ib, + &buff)) + return mhd_H2_DEC_FIELDS_NO_SPACE; + + res = mhd_hpack_dec_data (hk_dec, + enc_data->size - pos, + (const uint8_t *) enc_data->data + pos, + buff.size, + buff.data, + &name_len, + &val_len, + &pos_incr); + + if (! mhd_HPACK_DEC_RES_IS_ERR (res)) + { + mhd_assert (0u != pos_incr); + pos += pos_incr; + } + + switch (res) + { + case mhd_HPACK_DEC_RES_NO_NEW_FIELD: + if (max_no_fields < ++no_fields) + { + ret = mhd_H2_DEC_FIELDS_PROT_ABUSE; + break; + } + mhd_h2_items_cancel_new_item_buff (ib); + continue; + case mhd_HPACK_DEC_RES_NEW_FIELD: + mhd_assert (buff.size >= (name_len + val_len + 2u)); + mhd_assert (0 == buff.data[name_len]); + mhd_assert (0 == buff.data[name_len + 1 + val_len]); + mhd_h2_items_add_new_item_buff (ib, + name_len, + val_len, + (':' == buff.data[0]) ? + mhd_H2_RIK_PSEUDOHEADER : field_kind); + continue; + case mhd_HPACK_DEC_RES_INCOMPLETE: + mhd_assert (mhd_H2_DEC_FIELDS_OK == ret); + break; + case mhd_HPACK_DEC_RES_ALLOC_ERR: + ret = mhd_H2_DEC_FIELDS_INT_ERR; + break; + case mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL: // TODO: support "minimal" decoding for decoder state updates + case mhd_HPACK_DEC_RES_STRING_TOO_LONG: + ret = mhd_H2_DEC_FIELDS_NO_SPACE; + break; + case mhd_HPACK_DEC_RES_NUMBER_TOO_LONG: + case mhd_HPACK_DEC_RES_DYN_SIZE_UPD_TOO_LARGE: + ret = mhd_H2_DEC_FIELDS_PROT_ABUSE; + break; + case mhd_HPACK_DEC_RES_DYN_SIZE_UPD_MISSING: + case mhd_HPACK_DEC_RES_HUFFMAN_ERR: + case mhd_HPACK_DEC_RES_HPACK_BAD_IDX: + case mhd_HPACK_DEC_RES_HPACK_ERR: + ret = mhd_H2_DEC_FIELDS_BROKEN_DATA; + break; + case mhd_HPACK_DEC_RES_INTERNAL_ERR: + default: + mhd_UNREACHABLE_D ("Impossible value"); + ret = mhd_H2_DEC_FIELDS_INT_ERR; + break; + } + mhd_h2_items_cancel_new_item_buff (ib); + break; /* Break the loop */ + } + + mhd_assert (pos <= enc_data->size); + *left_unprocessed = enc_data->size - pos; + + return ret; +} + + +static inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) bool +req_validate_fields_chars (struct mhd_H2Stream *restrict s) +{ + // TODO: implement checking all field chars for validity, RFC 9113 Section 8.2.1 + (void) s; + return true; +} + + +static inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_OUT_ (2) bool +req_pseudoheaders_preprocess (struct mhd_H2Stream *restrict s, + size_t *pos) +{ + static const struct MHD_String ph_method = mhd_MSTR_INIT (":method"); + static const struct MHD_String ph_scheme = mhd_MSTR_INIT (":scheme"); + static const struct MHD_String ph_authority = mhd_MSTR_INIT (":authority"); + static const struct MHD_String ph_path = mhd_MSTR_INIT (":path"); + struct mhd_H2ReqItemsBlock *const ib = s->c->h2.mem.req_ib; + const char *buff = mhd_h2_items_get_strings_buffc (ib); + bool have_method = false; + bool have_scheme = false; + bool have_authority = false; + bool have_path = false; + + *pos = 0u; + while (1) + { + const struct mhd_H2ReqItem *item; + item = mhd_h2_items_get_item_nc (ib, + *pos); + + if (NULL == item) + break; + else if (mhd_H2_RIK_PSEUDOHEADER != item->kind) + break; + else if ((item->name_len == ph_method.len) && + (0 == memcmp (buff + item->offset, + ph_method.cstr, + ph_method.len))) + { + if (have_method) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP); + have_method = true; + s->req.pos_method = *pos; + s->req.method = + mhd_parse_http_method (item->val_len, + buff + item->offset + ph_method.len + 1u); + } + else if ((item->name_len == ph_path.len) && + (0 == memcmp (buff + item->offset, + ph_path.cstr, + ph_path.len))) + { + if (have_path) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP); + have_path = true; + s->req.pos_path = *pos; + } + else if ((item->name_len == ph_authority.len) && + (0 == memcmp (buff + item->offset, + ph_authority.cstr, + ph_authority.len))) + { + if (have_authority) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP); + have_authority = true; + s->req.pos_authority = *pos; + } + else if ((item->name_len == ph_scheme.len) && + (0 == memcmp (buff + item->offset, + ph_scheme.cstr, + ph_scheme.len))) + { + if (have_scheme) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP); + have_scheme = true; + } + mhd_assert (':' == buff[item->offset]); + ++(*pos); + } + + if (! have_method) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING); + + if (mhd_HTTP_METHOD_CONNECT != s->req.method) + { + if (! have_path || ! have_scheme) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_EXTRA); + } + else + { + if (have_path || have_scheme) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING); + } + + return true; +} + + +static inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_OUT_ (2) bool +req_headers_preprocess (struct mhd_H2Stream *restrict s, + size_t *pos) +{ + static const struct MHD_String h_host = mhd_MSTR_INIT ("host"); + static const struct MHD_String h_cntn_len = mhd_MSTR_INIT ("content-length"); + struct mhd_H2ReqItemsBlock *const ib = s->c->h2.mem.req_ib; + const char *buff = mhd_h2_items_get_strings_buffc (ib); + + while (1) + { + const struct mhd_H2ReqItem *item; + item = mhd_h2_items_get_item_nc (ib, + *pos); + + if (NULL == item) + break; + else if (mhd_H2_RIK_PSEUDOHEADER == item->kind) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_PSEUDOHDR_AFTER_HDR); + else if (mhd_H2_RIK_HEADER != item->kind) + (void) 0; /* skip */ + else if ((item->name_len == h_cntn_len.len) && + (0 == memcmp (buff + item->offset, + h_cntn_len.cstr, + h_cntn_len.len))) + { + uint_fast64_t cntnt_len; + + if ((0u == item->val_len) || + (item->val_len != + mhd_str_to_uint64 (buff + item->offset + h_cntn_len.len + 1u, + &cntnt_len))) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG); + + if ((MHD_SIZE_UNKNOWN != s->req.cntn_size) && + (s->req.cntn_size != cntnt_len)) + return mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG); + else + s->req.cntn_size = cntnt_len; + } + else if ((item->name_len == h_host.len) && + (0 == memcmp (buff + item->offset, + h_host.cstr, + h_host.len))) + { + if (mhd_H2_REQ_ITEM_POS_INVALID == s->req.pos_authority) + s->req.pos_authority = *pos; + else + { + const struct mhd_H2ReqItem *item_auth = + mhd_h2_items_get_item_nc (ib, + s->req.pos_authority); + mhd_assert (NULL != item_auth); + if ((item_auth->val_len != item->val_len) || + (! mhd_str_equal_caseless_bin_n (buff + item->offset + + item->name_len + 1u, + buff + item_auth->offset + + item_auth->name_len + 1u, + item->val_len))) + return + mhd_h2_stream_req_problem (s, + mhd_H2_REQ_PRBLM_HOST_HDR_WRONG_EXTRA); + } + } + + ++(*pos); + mhd_assert ((mhd_H2_RIK_HEADER != item->kind) || + (':' != buff[item->offset])); + } + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) bool +mhd_h2_req_headers_preprocess (struct mhd_H2Stream *restrict s) +{ + size_t pos; + + mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib) + == s->stream_id); + + if (! req_validate_fields_chars (s)) + return false; + + if (! req_pseudoheaders_preprocess (s, + &pos)) + return false; + + mhd_assert (0u != pos); + mhd_assert (mhd_HTTP_METHOD_NO_METHOD != s->req.method); + + if (! req_headers_preprocess (s, + &pos)) + return false; + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) bool +mhd_h2_req_uri_parse (struct mhd_H2Stream *restrict s) +{ + struct mhd_H2ReqItemsBlock *const restrict ib = s->c->h2.mem.req_ib; + char *const restrict buff = mhd_h2_items_get_strings_buff (ib); + struct mhd_H2ReqItem *restrict item = + mhd_h2_items_get_item_n (ib, + s->req.pos_path); + const size_t path_start = (size_t) (item->offset + item->name_len + 1u); + char *questn_mark; + + questn_mark = (char*) memchr (buff + path_start, + '?', + (size_t) item->val_len); + if (NULL == questn_mark) + { + item->val_len = + (uint_least32_t) + mhd_str_dec_norm_uri_path ((size_t) item->val_len, + buff + path_start); + } + else + { + const size_t path_len = (size_t) (questn_mark - (buff + path_start)); + const size_t uri_end = (size_t) (path_start + item->val_len); + size_t i = path_start + path_len + 1u; + + mhd_assert (path_len < item->val_len); + + buff[path_start + path_len] = '\0'; /* Zero-terminate the path */ + item->val_len = + (uint_least32_t) + mhd_str_dec_norm_uri_path (path_len, + buff + path_start); + + do + { + size_t name_start; + size_t name_len; + size_t value_start; + size_t value_len; + + value_start = 0u; + for (name_start = i; i < uri_end; ++i) /* Processing parameter */ + { + if ('+' == buff[i]) + buff[i] = ' '; + else if ('=' == buff[i]) + { + /* Found start of the value */ + for (value_start = ++i; i < uri_end; ++i) /* Processing parameter value */ + { + if ('+' == buff[i]) + buff[i] = ' '; + else if ('&' == buff[i]) /* delimiter for the next parameter */ + break; /* Next parameter */ + } + break; /* End of the current parameter */ + } + else if ('&' == buff[i]) + break; /* End of the name of the parameter without a value */ + } + + /* PCT-decode, zero-terminate and store the found parameter */ + + if (0u != value_start) /* Value cannot start at zero position */ + { /* Name with value */ + mhd_assert (name_start + 1u <= value_start); + name_len = value_start - name_start - 1u; + value_len = i - value_start; + } + else + { /* Name without value */ + name_len = i - name_start; + value_len = 0u; + } + name_len = mhd_str_pct_decode_lenient_n (buff + name_start, + name_len, + buff + name_start, + name_len, + NULL); // TODO: add support for broken encoding detection + buff[name_start + name_len] = 0; + + if (0u != value_start) + { + value_len = + mhd_str_pct_decode_lenient_n (buff + name_start + name_len + 1u, + value_len, + buff + value_start, + value_len, + NULL); // TODO: add support for broken encoding detection + buff[value_start + value_len] = 0; + } + + if (! mhd_h2_items_reserve_new_item (ib)) + break; // TODO: support reporting no space errors + + mhd_h2_items_add_new_item_reserved (ib, + name_start, + name_len, + value_len, + (0u != value_start) + ? mhd_H2_RIK_URI_PARAM : + mhd_H2_RIK_URI_PARAM_NV); + + } while (uri_end > ++i); + + } + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) bool +mhd_h2_req_cookie_parse (struct mhd_H2Stream *restrict s) +{ + // TODO: handle cookie combining + // TODO: Implement cookie parsing + return true; +} diff --git a/src/mhd2/h2/h2_req_fields.h b/src/mhd2/h2/h2_req_fields.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_fields.h + * @brief Declaration of HTTP/2 request fields functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_REQ_FIELDS_H +#define MHD_H2_REQ_FIELDS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_sizet_type.h" + +struct mhd_Buffer; /* Forward declaration */ +struct mhd_HpackDecContext; /* Forward declaration */ +struct mhd_H2ReqItemsBlock; /* Forward declaration */ +struct mhd_H2Stream; /* Forward declaration */ + +enum MHD_FIXED_ENUM_ mhd_H2DecFieldsResult +{ + /** + * The data was successfully decoded. + * Some incomplete data at the end of the block may be left unprocessed. + */ + mhd_H2_DEC_FIELDS_OK + , + /** + * Not enough space to add the decoded field + */ + mhd_H2_DEC_FIELDS_NO_SPACE + , + /** + * Internal error while decoding the data. + * It could be memory allocation error when dynamic table is increasing + * within the allowed limits. + */ + mhd_H2_DEC_FIELDS_INT_ERR + , + /** + * The encoded data is incorrectly encoded or broken + */ + mhd_H2_DEC_FIELDS_BROKEN_DATA + , + /** + * The data encoded in a way that excessively use resources or bandwidth + */ + mhd_H2_DEC_FIELDS_PROT_ABUSE +}; + +MHD_INTERNAL enum mhd_H2DecFieldsResult +mhd_h2_req_fields_decode (struct mhd_HpackDecContext *restrict hk_dec, + const struct mhd_Buffer *restrict enc_data, + bool are_trailers, + struct mhd_H2ReqItemsBlock *restrict ib, + size_t *restrict left_unprocessed) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_(1) MHD_FN_PAR_IN_(2) +MHD_FN_PAR_INOUT_(4) MHD_FN_PAR_OUT_ (5); + +MHD_INTERNAL bool +mhd_h2_req_headers_preprocess (struct mhd_H2Stream *restrict s) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + +MHD_INTERNAL bool +mhd_h2_req_uri_parse (struct mhd_H2Stream *restrict s) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + +MHD_INTERNAL bool +mhd_h2_req_cookie_parse (struct mhd_H2Stream *restrict s) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + + +#endif /* ! MHD_H2_REQ_FIELDS_H */ diff --git a/src/mhd2/h2/h2_req_get_items.c b/src/mhd2/h2/h2_req_get_items.c @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_get_items.c + * @brief Implementation of HTTP/2 request items public getters + * @author Karlson2k (Evgeny Grin) + */ + + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_cntnr_ptr.h" + +#include "mhd_connection.h" +#include "h2_conn_data.h" +#include "h2_stream_data.h" + +#include "h2_req_item_struct.h" +#include "h2_req_items_funcs.h" + +#include "mhd_str.h" + +#include "h2_req_get_items.h" + + +mhd_static_inline unsigned int +req_items_kind_mask (enum MHD_ValueKind kind) +{ + unsigned int m; + m = 0u; + if (0u != (MHD_VK_HEADER & (unsigned int) kind)) + m |= (unsigned int) mhd_H2_RIK_HEADER; + if (0u != (MHD_VK_COOKIE & (unsigned int) kind)) + m |= (unsigned int) mhd_H2_RIK_COOKIE; + if (0u != (MHD_VK_URI_QUERY_PARAM & (unsigned int) kind)) + { + m |= (unsigned int) mhd_H2_RIK_URI_PARAM; + m |= (unsigned int) mhd_H2_RIK_URI_PARAM_NV; + } + if (0u != (MHD_VK_TRAILER & (unsigned int) kind)) + m |= (unsigned int) mhd_H2_RIK_TRAILER; + + return m; +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (4,3) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_OUT_ (5) bool +mhd_h2_request_get_value_n (struct MHD_Request *restrict r, + enum MHD_ValueKind kind, + size_t key_len, + const char *restrict key, + struct MHD_StringNullable *restrict value_out) +{ + struct mhd_H2Stream *const s = + mhd_CNTNR_PTR ((struct mhd_H2RequestData *) (void*) r, + struct mhd_H2Stream, req); + struct MHD_Connection *const c = s->c; + size_t pos; + unsigned int type_mask; + const char *buff; + + mhd_assert (r->is_http2); + mhd_assert (s->is_h2); + + if ((mhd_H2_REQ_STAGE_HEADERS_DECODING != s->req.stage) + && (mhd_H2_REQ_STAGE_HEADERS_PROCESSING != s->req.stage) + && (mhd_H2_REQ_STAGE_TRAILERS_DECODING != s->req.stage) + && (mhd_H2_REQ_STAGE_TRAILERS_PROCESSING != s->req.stage)) + return false; + + mhd_assert (mhd_h2_items_debug_get_streamid (c->h2.mem.req_ib) + == s->stream_id); + + type_mask = req_items_kind_mask (kind); + buff = mhd_h2_items_get_strings_buffc (c->h2.mem.req_ib); + + pos = 0u; + while (true) + { + const struct mhd_H2ReqItem *itm; + + itm = mhd_h2_items_get_item_nc (c->h2.mem.req_ib, + pos++); + + if (NULL == itm) + break; + + if (key_len != itm->name_len) + continue; + + if (0u == (type_mask & (unsigned int) itm->kind)) + continue; + + if (! mhd_str_equal_lowercase_bin_n (key, + buff + itm->offset, + key_len)) + continue; + + if (mhd_H2_RIK_URI_PARAM_NV != itm->kind) + { + value_out->cstr = buff + itm->offset + itm->name_len + 1u; + value_out->len = (size_t) itm->val_len; + } + else + { + value_out->cstr = NULL; + value_out->len = 0u; + } + + return true; + } + + return false; +} + + +mhd_static_inline enum MHD_ValueKind +req_item_kind_h2_to_h1 (enum mhd_H2RequestItemKind h2kind) +{ + switch (h2kind) + { + case mhd_H2_RIK_HEADER: + case mhd_H2_RIK_PSEUDOHEADER: + return MHD_VK_HEADER; + case mhd_H2_RIK_COOKIE: + return MHD_VK_COOKIE; + case mhd_H2_RIK_URI_PARAM: + case mhd_H2_RIK_URI_PARAM_NV: + return MHD_VK_URI_QUERY_PARAM; + case mhd_H2_RIK_TRAILER: + return MHD_VK_TRAILER; + case mhd_H2_RIK_PLACEHOLDER: + case mhd_H2_RIK_ELIMINATED: + default: + break; + } + return MHD_VK_HEADER; +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) size_t +mhd_h2_request_get_values_cb (struct MHD_Request *r, + enum MHD_ValueKind kind, + MHD_NameValueIterator iterator, + void *iterator_cls) +{ + struct mhd_H2Stream *const s = + mhd_CNTNR_PTR ((struct mhd_H2RequestData *) (void*) r, + struct mhd_H2Stream, req); + struct MHD_Connection *const c = s->c; + size_t pos; + unsigned int type_mask; + const char *buff; + size_t count; + + + mhd_assert (r->is_http2); + mhd_assert (s->is_h2); + + if ((mhd_H2_REQ_STAGE_HEADERS_DECODING != s->req.stage) + && (mhd_H2_REQ_STAGE_HEADERS_PROCESSING != s->req.stage) + && (mhd_H2_REQ_STAGE_TRAILERS_DECODING != s->req.stage) + && (mhd_H2_REQ_STAGE_TRAILERS_PROCESSING != s->req.stage)) + return false; + + mhd_assert (mhd_h2_items_debug_get_streamid (c->h2.mem.req_ib) + == s->stream_id); + + type_mask = req_items_kind_mask (kind); + buff = mhd_h2_items_get_strings_buffc (c->h2.mem.req_ib); + + pos = 0u; + count = 0u; + while (true) + { + const struct mhd_H2ReqItem *itm; + + itm = mhd_h2_items_get_item_nc (c->h2.mem.req_ib, + pos++); + + if (NULL == itm) + break; + + if (((unsigned int) itm->kind) != (type_mask & (unsigned int) itm->kind)) + continue; + + ++count; + if (NULL != iterator) + { + struct MHD_NameAndValue nv; + + nv.name.cstr = buff + itm->offset; + nv.name.len = (size_t) itm->val_len; + if (mhd_H2_RIK_URI_PARAM_NV != itm->kind) + { + nv.value.cstr = buff + itm->offset + itm->name_len + 1u; + nv.value.len = (size_t) itm->val_len; + } + else + { + nv.value.cstr = NULL; + nv.value.len = 0u; + } + + if (MHD_NO == + iterator (iterator_cls, + req_item_kind_h2_to_h1 (itm->kind), + &nv)) + return count; + } + } + + return count; +} diff --git a/src/mhd2/h2/h2_req_get_items.h b/src/mhd2/h2/h2_req_get_items.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_get_items.h + * @brief Declarations of HTTP/2 request items public getters + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_REQ_GET_ITEMS_H +#define MHD_H2_REQ_GET_ITEMS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_sizet_type.h" + +#include "h2_req_item_kinds.h" + +#include "mhd_public_api.h" + + +MHD_INTERNAL bool +mhd_h2_request_get_value_n (struct MHD_Request *restrict r, + enum MHD_ValueKind kind, + size_t key_len, + const char *restrict key, + struct MHD_StringNullable *restrict value_out) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (4,3) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_OUT_ (5); + +MHD_INTERNAL size_t +mhd_h2_request_get_values_cb (struct MHD_Request *r, + enum MHD_ValueKind kind, + MHD_NameValueIterator iterator, + void *iterator_cls) +MHD_FN_PAR_NONNULL_ (1); + +#endif /* ! MHD_H2_REQ_GET_ITEMS_H */ diff --git a/src/mhd2/h2/h2_req_item_kinds.h b/src/mhd2/h2/h2_req_item_kinds.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_item_kinds.h + * @brief Definition of the kinds of the HTTP/2 request items + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_REQ_ITEM_KINDS_H +#define MHD_H2_REQ_ITEM_KINDS_H 1 + +#include "mhd_sys_options.h" + +#ifdef mhd_USE_ENUM_BASE_T +# include "sys_base_types.h" +#endif + + +/** + * Request item kind + */ +enum MHD_FIXED_FLAGS_ENUM_ mhd_H2RequestItemKind +mhd_ENUM_BASE_T (uint_least8_t) +{ + mhd_H2_RIK_HEADER = (1u << 0u), + mhd_H2_RIK_PSEUDOHEADER = (1u << 1u), + mhd_H2_RIK_COOKIE = (1u << 2u), + mhd_H2_RIK_URI_PARAM = (1u << 3u), + mhd_H2_RIK_URI_PARAM_NV = (1u << 3u) + (1u << 7u), + mhd_H2_RIK_TRAILER = (1u << 4u), + mhd_H2_RIK_PLACEHOLDER = (1u << 5u), + mhd_H2_RIK_ELIMINATED = (1u << 6u) +}; + +#endif /* ! MHD_H2_REQ_ITEM_KINDS_H */ diff --git a/src/mhd2/h2/h2_req_item_struct.h b/src/mhd2/h2/h2_req_item_struct.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_item_struct.h + * @brief Definition of the structure for request items (headers, URI params) + * @author Karlson2k (Evgeny Grin) + * + * The sizes of all strings are intentionally limited to 32 bits (4GiB). + */ + +#ifndef MHD_H2_REQ_ITEM_STRUCT_H +#define MHD_H2_REQ_ITEM_STRUCT_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#include "mhd_str_types.h" + +#include "h2_req_item_kinds.h" + +/** + * HTTP/2 request item + */ +struct mhd_H2ReqItem +{ + /** + * The kind of the item + */ + enum mhd_H2RequestItemKind kind; + /** + * The offset of the name of the header in the buffer + */ + uint_least32_t offset; + /** + * The length of the name of the header (not including mandatory + * zero-termination). + */ + uint_least32_t name_len; + /** + * The length of the name of the header (not including mandatory + * zero-termination). + * The value is located of @a offset + @a name_len + 1 position in the buffer. + */ + uint_least32_t val_len; +}; + +#endif /* ! MHD_H2_REQ_ITEM_STRUCT_H */ diff --git a/src/mhd2/h2/h2_req_items_funcs.c b/src/mhd2/h2/h2_req_items_funcs.c @@ -0,0 +1,498 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_items_funcs.c + * @brief Function for the request items (headers, URI params) + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#include "mhd_align.h" +#include "mhd_predict.h" +#include "mhd_constexpr.h" + +#include "mhd_assert.h" + +#include "sys_malloc.h" + +#include "mhd_buffer.h" +#include "mhd_str_types.h" + +#include "h2_req_item_struct.h" + +#include "h2_req_items_funcs.h" + + +struct mhd_H2ReqItemsBlock +{ + /** + * Number of items in the items block + */ + size_t num_items; + + /** + * The size of the items buffer, in bytes + */ + size_t buf_size; + + /** + * The starting offset of the free buffer space + */ + uint_least32_t start_free; + +#ifndef NDEBUG + uint_least32_t stream_id; + bool buff_locked; +#endif /* ! NDEBUG */ +}; + +mhd_constexpr size_t mhd_rii_size = sizeof (struct mhd_H2ReqItem); + +mhd_static_inline char * +h2_ib_get_buff (struct mhd_H2ReqItemsBlock *ib) +{ + mhd_assert (ib->start_free <= ib->buf_size); + return (char *) (ib + 1u); +} + + +mhd_static_inline const char * +h2_ib_get_buffc (const struct mhd_H2ReqItemsBlock *ib) +{ + mhd_assert (ib->start_free <= ib->buf_size); + return (const char *) (ib + 1u); +} + + +mhd_static_inline struct mhd_H2ReqItem * +h2_ib_get_zero_item (struct mhd_H2ReqItemsBlock *ib) +{ + return ((struct mhd_H2ReqItem *) + (void *) (h2_ib_get_buff (ib) + ib->buf_size)) + - 1u; +} + + +mhd_static_inline const struct mhd_H2ReqItem * +h2_ib_get_zero_itemc (const struct mhd_H2ReqItemsBlock *ib) +{ + return ((const struct mhd_H2ReqItem *) + (const void *) (h2_ib_get_buffc (ib) + ib->buf_size)) + - 1u; +} + + +/* 'pos' is zero-based */ +mhd_static_inline struct mhd_H2ReqItem * +h2_ib_get_n_item (struct mhd_H2ReqItemsBlock *ib, + size_t pos) +{ + struct mhd_H2ReqItem *const ret = h2_ib_get_zero_item (ib) - pos; + mhd_assert (ib->buf_size >= (ib->start_free + + ib->num_items * mhd_rii_size)); + return ret; +} + + +/* 'pos' is zero-based */ +mhd_static_inline const struct mhd_H2ReqItem * +h2_ib_get_n_itemc (const struct mhd_H2ReqItemsBlock *ib, + size_t pos) +{ + const struct mhd_H2ReqItem *const ret = h2_ib_get_zero_itemc (ib) - pos; + mhd_assert (ib->buf_size >= (ib->start_free + + ib->num_items * mhd_rii_size)); + return ret; +} + + +mhd_static_inline size_t +h2_ib_get_buff_free_size (const struct mhd_H2ReqItemsBlock *ib) +{ + mhd_assert (ib->buf_size >= (ib->start_free + + ib->num_items * sizeof(struct mhd_H2ReqItem))); + return ib->buf_size - ib->start_free - (ib->num_items * mhd_rii_size); +} + + +mhd_static_inline char * +h2_ib_get_buff_free_ptr (struct mhd_H2ReqItemsBlock *ib) +{ + return h2_ib_get_buff (ib) + ib->start_free; +} + + +MHD_INTERNAL mhd_FN_RET_UNALIASED +mhd_FN_OBJ_CONSTRUCTOR (mhd_h2_items_block_destroy) +struct mhd_H2ReqItemsBlock * +mhd_h2_items_block_create (size_t buffer_size) +{ + struct mhd_H2ReqItemsBlock *ret; + uint_fast32_t buf_alloc_size; + + buf_alloc_size = (buffer_size & 0xFFFFFFFFu); + if (mhd_COND_HARDLY_EVER ((0xFFFFFFFFu + - 2u * mhd_ALIGNOF (struct mhd_H2ReqItem)) + > buffer_size)) + buf_alloc_size = + (uint_fast32_t) (0xFFFFFFFFu - 2 * mhd_ALIGNOF (struct mhd_H2ReqItem)); + + /* Round up to alignment */ + buf_alloc_size += + (uint_fast32_t) + ((mhd_ALIGNOF (struct mhd_H2ReqItem) + - (buf_alloc_size % mhd_ALIGNOF (struct mhd_H2ReqItem))) + % mhd_ALIGNOF (struct mhd_H2ReqItem)); + + /* Adjust the allocation size in case if alignment of mhd_H2ReqItem is + stricter than alignment of mhd_H2ReqItemsBlock */ + buf_alloc_size += + (uint_fast32_t) + ((mhd_ALIGNOF (struct mhd_H2ReqItem) + - (sizeof(*ret) % mhd_ALIGNOF (struct mhd_H2ReqItem))) + % mhd_ALIGNOF (struct mhd_H2ReqItem)); + + mhd_assert ((buffer_size <= buf_alloc_size) || \ + (0xFFFFFFFFu - 2 * mhd_ALIGNOF (struct mhd_H2ReqItem) \ + <= buf_alloc_size)); + + ret = (struct mhd_H2ReqItemsBlock *) malloc (sizeof (*ret) + buf_alloc_size); + + if (NULL == ret) + return NULL; /* Failure exit point */ + + ret->buf_size = (size_t) buf_alloc_size; +#ifndef NDEBUG + ret->buff_locked = false; +#endif /* ! NDEBUG */ + mhd_h2_items_block_reset (ret); + + return ret; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_items_block_destroy (struct mhd_H2ReqItemsBlock *ib) +{ + free (ib); +} + + +MHD_INTERNAL +MHD_FN_PAR_INOUT_ (1) void +mhd_h2_items_block_reset (struct mhd_H2ReqItemsBlock *restrict ib) +{ + mhd_assert (ib->start_free <= ib->buf_size); + mhd_assert (! ib->buff_locked); + + ib->num_items = 0u; + ib->start_free = 0u; + +#ifndef NDEBUG + ib->stream_id = 0u; +#endif /* ! NDEBUG */ +} + + +MHD_INTERNAL +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_OUT_ (2) +MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_items_get_buff_new_item (struct mhd_H2ReqItemsBlock *restrict ib, + struct mhd_Buffer *restrict buff) +{ + const size_t free_space = h2_ib_get_buff_free_size (ib); + + mhd_assert (! ib->buff_locked); + + if (mhd_rii_size + 2u > free_space) /* 2 for two zero-terminations */ + return false; + +#ifndef NDEBUG + ib->buff_locked = true; +#endif /* ! NDEBUG */ + + buff->data = h2_ib_get_buff_free_ptr (ib); + buff->size = free_space - mhd_rii_size; + + return true; +} + + +MHD_INTERNAL +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ bool +mhd_h2_items_reserve_new_item (struct mhd_H2ReqItemsBlock *restrict ib) +{ + const size_t free_space = h2_ib_get_buff_free_size (ib); + + mhd_assert (! ib->buff_locked); + + if (mhd_rii_size + 2u > free_space) /* 2 for two zero-terminations */ + return false; + +#ifndef NDEBUG + ib->buff_locked = true; +#endif /* ! NDEBUG */ + + return true; +} + + +MHD_INTERNAL +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_items_add_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib, + size_t name_len, + size_t val_len, + enum mhd_H2RequestItemKind kind) +{ + struct mhd_H2ReqItem *const itm = h2_ib_get_n_item (ib, ib->num_items); + + mhd_assert (ib->buff_locked); + mhd_assert (h2_ib_get_buff_free_size (ib) >= \ + name_len + val_len + 2u + mhd_rii_size); + mhd_assert (0 == h2_ib_get_buff_free_ptr (ib)[name_len]); + mhd_assert (0 == h2_ib_get_buff_free_ptr (ib)[name_len + 1 + val_len]); + + itm->kind = kind; + itm->offset = ib->start_free; + itm->name_len = (uint_least32_t) name_len; + itm->val_len = (uint_least32_t) val_len; + + ib->start_free += (uint_least32_t) (name_len + val_len + 2u); + ++ib->num_items; + +#ifndef NDEBUG + ib->buff_locked = false; +#endif /* ! NDEBUG */ + + mhd_assert (ib->buf_size >= (ib->start_free + + ib->num_items * sizeof(struct mhd_H2ReqItem))); +} + + +MHD_INTERNAL +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_items_add_new_item_reserved (struct mhd_H2ReqItemsBlock *restrict ib, + size_t name_start, + size_t name_len, + size_t val_len, + enum mhd_H2RequestItemKind kind) +{ + struct mhd_H2ReqItem *const itm = h2_ib_get_n_item (ib, ib->num_items); + + mhd_assert (ib->buff_locked); + mhd_assert (h2_ib_get_buff_free_size (ib) >= mhd_rii_size); + mhd_assert (0 == h2_ib_get_buffc (ib)[name_start + name_len]); + mhd_assert ((mhd_H2_RIK_URI_PARAM_NV == kind) || + (0 == h2_ib_get_buffc (ib)[name_start + name_len + 1 + val_len])); + mhd_assert (name_start < ib->start_free); + mhd_assert (name_len + val_len + 2u <= ib->start_free); + + itm->kind = kind; + itm->offset = (uint_least32_t) name_start; + itm->name_len = (uint_least32_t) name_len; + itm->val_len = (uint_least32_t) val_len; + + ++ib->num_items; + +#ifndef NDEBUG + ib->buff_locked = false; +#endif /* ! NDEBUG */ + + mhd_assert (ib->buf_size >= (ib->start_free + + ib->num_items * sizeof(struct mhd_H2ReqItem))); +} + + +#ifndef NDEBUG +MHD_INTERNAL +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_items_cancel_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib) +{ + mhd_assert (ib->buff_locked); + ib->buff_locked = false; +} + + +#endif /* ! NDEBUG */ + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_ +MHD_FN_PURE_ char * +mhd_h2_items_get_strings_buff (struct mhd_H2ReqItemsBlock *restrict ib) +{ + return h2_ib_get_buff (ib); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_ +MHD_FN_PURE_ const char * +mhd_h2_items_get_strings_buffc (const struct mhd_H2ReqItemsBlock *restrict ib) +{ + return h2_ib_get_buffc (ib); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_ struct mhd_H2ReqItem * +mhd_h2_items_get_item_n (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos) +{ + if (ib->num_items <= pos) + return NULL; + return h2_ib_get_n_item (ib, + pos); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_ const struct mhd_H2ReqItem * +mhd_h2_items_get_item_nc (const struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos) +{ + if (ib->num_items <= pos) + return NULL; + return h2_ib_get_n_itemc (ib, + pos); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_ bool +mhd_h2_items_get_item_name (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + struct MHD_String *restrict name) +{ + const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib, + pos); + if (NULL == itm) + return false; + + name->cstr = h2_ib_get_buffc (ib) + itm->offset; + name->len = itm->name_len; + mhd_assert (0 == name->cstr[name->len]); + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_ bool +mhd_h2_items_get_item_value (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + struct MHD_String *restrict value) +{ + const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib, + pos); + if (NULL == itm) + return false; + + value->cstr = h2_ib_get_buffc (ib) + itm->offset + itm->name_len + 1u; + value->len = itm->val_len; + mhd_assert (0 == value->cstr[value->len]); + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_ bool +mhd_h2_items_get_item_kind (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + enum mhd_H2RequestItemKind *restrict kind) +{ + const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib, + pos); + if (NULL == itm) + return false; + + *kind = itm->kind; + mhd_assert (0u != (unsigned int) *kind); + + return true; + +} + + +MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (3) MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_OUT_ (5) bool +mhd_h2_items_get_item_full (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + struct MHD_String *restrict name, + struct MHD_String *restrict value, + enum mhd_H2RequestItemKind *restrict kind) +{ + const struct mhd_H2ReqItem *const itm = h2_ib_get_n_itemc (ib, + pos); + const char *const buff = h2_ib_get_buffc (ib); + + if (NULL == itm) + return false; + + name->cstr = buff + itm->offset; + name->len = itm->name_len; + value->cstr = buff + itm->offset + itm->name_len + 1u; + value->len = itm->val_len; + *kind = itm->kind; + + mhd_assert (0 == name->cstr[name->len]); + mhd_assert (0 == value->cstr[value->len]); + mhd_assert (0u != (unsigned int) *kind); + + return true; +} + + +#ifndef NDEBUG +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_h2_items_debug_set_streamid (struct mhd_H2ReqItemsBlock *restrict ib, + uint_least32_t stream_id) +{ + ib->stream_id = stream_id; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ uint_least32_t +mhd_h2_items_debug_get_streamid (struct mhd_H2ReqItemsBlock *restrict ib) +{ + return ib->stream_id; +} + + +#endif /* ! NDEBUG */ diff --git a/src/mhd2/h2/h2_req_items_funcs.h b/src/mhd2/h2/h2_req_items_funcs.h @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_req_items_funcs.h + * @brief Declarations of the request items (headers, URI params) functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_REQ_ITEMS_FUNCS_H +#define MHD_H2_REQ_ITEMS_FUNCS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "sys_bool_type.h" + +#include "h2_req_item_kinds.h" + +struct mhd_H2ReqItem; /* Forward declaration */ +struct mhd_H2ReqItemsBlock; /* Forward declaration */ +struct mhd_Buffer; /* Forward declaration */ +struct MHD_String; /* Forward declaration */ + + +MHD_INTERNAL void +mhd_h2_items_block_destroy (struct mhd_H2ReqItemsBlock *ib) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Create request items block + * @param buffer_size the size of the items block, must be less than UINT32_MAX + * @return the pointer to the new request items block if succeed, + * NULL if failed (out of memory) + */ +MHD_INTERNAL struct mhd_H2ReqItemsBlock * +mhd_h2_items_block_create (size_t buffer_size) +mhd_FN_RET_UNALIASED mhd_FN_OBJ_CONSTRUCTOR (mhd_h2_items_block_destroy); + + +/** + * Reset request items block + * @param ib the pointer to the previously initialised items block to reset + */ +MHD_INTERNAL void +mhd_h2_items_block_reset (struct mhd_H2ReqItemsBlock *restrict ib) +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_; + +/** + * Allocates the buffer space for a new item. + * + * This function gives all available buffer space, excluding space for the + * new item header. + * + * It must be finally followed by a single call of one of the + * #mhd_h2_items_add_new_item_buff() or #mhd_h2_items_cancel_new_item_buff(). + * @param ib the pointer to items block data + * @param[out] buff set to the available space in the buffer + * @return 'true' if succeed, + * 'false' if no space for a new item is available + */ +MHD_INTERNAL bool +mhd_h2_items_get_buff_new_item (struct mhd_H2ReqItemsBlock *restrict ib, + struct mhd_Buffer *restrict buff) +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_NONNULL_ALL_; + +/** + * Allocates the buffer space for a new item header, assuming that strings + * will be placed over other allocated and then reduced item. + * + * It must be finally followed by a single call of one of the + * #mhd_h2_mhd_h2_items_add_new_item_reserved() or + * #mhd_h2_items_cancel_new_item_buff(). + * @param ib the pointer to items block data + * @param[out] buff set to the available space in the buffer + * @return 'true' if succeed, + * 'false' if no space for a new item is available + */ +MHD_INTERNAL bool +mhd_h2_items_reserve_new_item (struct mhd_H2ReqItemsBlock *restrict ib) +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_; + +/** + * Add a new item to the items block based on previously allocated space + * + * The new item must be located at the start of the buffer which must be + * previously allocated by calling #mhd_h2_items_get_new_item_buff() function. + * + * The name string must be at zero position and must be zero-terminated. + * The value string must start immediately after zero-termination of the name + * string and must be zero-terminated too (the form is "name\0value\0"). + * + * The strings must fit the buffer. + * @param ib the pointer to items block data + * @param name_len the length of the name string, not including mandatory + * zero termination + * @param val_len the length of the value string, not including mandatory + * zero termination + */ +MHD_INTERNAL void +mhd_h2_items_add_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib, + size_t name_len, + size_t val_len, + enum mhd_H2RequestItemKind kind) +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL void +mhd_h2_items_add_new_item_reserved (struct mhd_H2ReqItemsBlock *restrict ib, + size_t name_start, + size_t name_len, + size_t val_len, + enum mhd_H2RequestItemKind kind) +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_; + + +#ifndef NDEBUG +MHD_INTERNAL void +mhd_h2_items_cancel_new_item_buff (struct mhd_H2ReqItemsBlock *restrict ib) +MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_NONNULL_ALL_; + +#else /* NDEBUG */ +# define mhd_h2_items_cancel_new_item_buff(ib) ((void) 0) /* do nothing */ +#endif /* NDEBUG */ + +MHD_INTERNAL char * +mhd_h2_items_get_strings_buff (struct mhd_H2ReqItemsBlock *restrict ib) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_ MHD_FN_PURE_; + +MHD_INTERNAL const char * +mhd_h2_items_get_strings_buffc (const struct mhd_H2ReqItemsBlock *restrict ib) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_ MHD_FN_PURE_; + +/* return NULL if 'pos' does not exist */ +MHD_INTERNAL struct mhd_H2ReqItem * +mhd_h2_items_get_item_n (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_; + +/* return NULL if 'pos' does not exist */ +MHD_INTERNAL const struct mhd_H2ReqItem * +mhd_h2_items_get_item_nc (const struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PURE_; + +MHD_INTERNAL bool +mhd_h2_items_get_item_name (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + struct MHD_String *restrict name) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_; + +MHD_INTERNAL bool +mhd_h2_items_get_item_value (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + struct MHD_String *restrict value) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_; + +MHD_INTERNAL bool +mhd_h2_items_get_item_kind (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + enum mhd_H2RequestItemKind *restrict kind) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (3) MHD_FN_PURE_; + +MHD_INTERNAL bool +mhd_h2_items_get_item_full (struct mhd_H2ReqItemsBlock *restrict ib, + size_t pos, + struct MHD_String *restrict name, + struct MHD_String *restrict value, + enum mhd_H2RequestItemKind *restrict kind) +MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ + MHD_FN_PAR_OUT_ (3) MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_OUT_ (5); + +#ifndef NDEBUG +MHD_INTERNAL void +mhd_h2_items_debug_set_streamid (struct mhd_H2ReqItemsBlock *restrict ib, + uint_least32_t stream_id) +MHD_FN_PAR_NONNULL_ALL_; + +MHD_INTERNAL uint_least32_t +mhd_h2_items_debug_get_streamid (struct mhd_H2ReqItemsBlock *restrict ib) +MHD_FN_PAR_NONNULL_ALL_; + +#else /* NDEBUG */ +# define mhd_h2_items_debug_set_streamid(ib,stream_id) ((void) 0) +# define mhd_h2_items_debug_get_streamid(ib) ((void) 0) +#endif + +#endif /* ! MHD_H2_REQ_ITEMS_FUNCS_H */ diff --git a/src/mhd2/h2/h2_resp_data.h b/src/mhd2/h2/h2_resp_data.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_resp_data.h + * @brief Definition of response data specific for HTTP/2 + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_RESP_DATA_H +#define MHD_H2_RESP_DATA_H 1 + +#include "mhd_sys_options.h" + +#include "mhd_buffer.h" + +/** + * Response header / field for HTTP/2 + */ +struct mhd_H2ResponseHeader +{ + /** + * The name of the header / field for HTTP/2 + * If @a data member is NULL, then the header must not be used for HTTP/2 + */ + struct mhd_BufferConst name; + + /** + * The value of the header / field for HTTP/2 + */ + struct mhd_BufferConst value; +}; + +#endif /* ! MHD_H2_RESP_DATA_H */ diff --git a/src/mhd2/h2/h2_settings.h b/src/mhd2/h2/h2_settings.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_settings.h + * @brief HTTP2 SETTINGS identifiers, coding and decoding + * @author Karlson2k (Evgeny Grin) + * + * SETTINGS parameters are defined in RFC 9113. Some additional identifiers + * are taken from other RFCs (see comments in the code). + */ + +#ifndef MHD_H2_SETTINGS_H +#define MHD_H2_SETTINGS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#include "mhd_assert.h" + +#include "mhd_bithelpers.h" + +#if defined(_MSC_FULL_VER) +#pragma warning(push) +/* Disable C4505 "unreferenced local function has been removed" */ +#pragma warning(disable:4505) +#endif /* _MSC_FULL_VER */ + +/** + * HTTP/2 SETTINGS identifiers + * + * Extracted from RFC 9113, Section 6.5.2; RFC 8441, Section 9.1; + * RFC 9218, Section 16. + */ +enum mhd_H2SettingsID +mhd_ENUM_BASE_T (uint_least16_t) +{ + mhd_H2_STNGS_HEADER_TABLE_SIZE = 0x01u + , + mhd_H2_STNGS_ENABLE_PUSH = 0x02u + , + mhd_H2_STNGS_CONCURRENT_STREAMS = 0x03u + , + mhd_H2_STNGS_INITIAL_WINDOW_SIZE = 0x04u + , + mhd_H2_STNGS_MAX_FRAME_SIZE = 0x05u + , + mhd_H2_STNGS_MAX_HEADER_LIST_SIZE = 0x06u + , + mhd_H2_STNGS_ENABLE_CONNECT_PROTOCOL = 0x08u + , + mhd_H2_STNGS_NO_RFC7540_PRIORITIES = 0x09u +#ifndef mhd_USE_ENUM_BASE_T + , + /** + * Not a real identifier, no not use + */ + mhd_H2_STNGS_SENTINEL = 0xFFFFu +#endif /* ! mhd_USE_ENUM_BASE_T */ +}; + + +/** + * HTTP/2 setting + */ +struct mhd_H2Setting +{ + /** + * Setting's identifier + */ + enum mhd_H2SettingsID identifier; + /** + * Setting's value + */ + uint_least32_t value; +}; + +/** + * The size of single HTTP/2 setting + */ +#define mhd_H2_SETTING_SIZE (6u) + +/** + * Decode a SETTINGS parameter from "on-wire" 6-byte form. + * + * @param encoded the 6-byte array with the encoded parameter + * @param[out] p_identifier receives the 16-bit setting identifier + * @param[out] p_value receives the 32-bit setting value + */ +static inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_ (3) void +mhd_h2_setting_decode3 (const uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)], + uint_least16_t *restrict p_identifier, + uint_least32_t *restrict p_value) +{ + *p_identifier = mhd_GET_16BIT_BE_UNALIGN (encoded); + *p_value = mhd_GET_32BIT_BE_UNALIGN (encoded + 2u); +} + + +/** + * Decode a SETTINGS parameter into a @ref mhd_H2Setting structure. + * + * @param encoded the 6-byte array with the encoded parameter + * @param[out] setting receives the decoded identifier and value + */ +static inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) void +mhd_h2_setting_decode (const uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)], + struct mhd_H2Setting *restrict setting) +{ + uint_least16_t identifier; + mhd_h2_setting_decode3 (encoded, + &identifier, + &(setting->value)); + setting->identifier = (enum mhd_H2SettingsID) identifier; +} + + +/** + * Encode a SETTINGS parameter to on-wire 6-byte form. + * + * @param identifier the 16-bit setting identifier + * @param value the 32-bit setting value + * @param[out] encoded the destination 6-byte array + */ +static inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (3) void +mhd_h2_setting_encode3 (uint_least16_t identifier, + uint_least32_t value, + uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)]) +{ + mhd_assert (identifier == (identifier & 0xFFFFu)); + mhd_assert (value == (value & 0xFFFFFFFFu)); + mhd_PUT_16BIT_BE_UNALIGN (encoded, + identifier); + mhd_PUT_32BIT_BE_UNALIGN (encoded + 2u, + value); +} + + +/** + * Encode a SETTINGS parameter from @ref mhd_H2Setting. + * + * @param setting the setting to encode + * @param[out] encoded the destination 6-byte array + */ +static inline MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_ (2) void +mhd_h2_setting_encode (const struct mhd_H2Setting *restrict setting, + uint8_t encoded[MHD_FN_PAR_FIX_ARR_SIZE_ (6)]) +{ + mhd_h2_setting_encode3 ((uint_least16_t) setting->identifier, + setting->value, + encoded); +} + + +#if defined(_MSC_FULL_VER) +/* Restore warnings */ +#pragma warning(pop) +#endif /* _MSC_FULL_VER */ + +#endif /* ! MHD_H2_SETTINGS_H */ diff --git a/src/mhd2/h2/h2_stream_data.h b/src/mhd2/h2/h2_stream_data.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/h2_stream_data.h + * @brief Definition of structure for HTTP/2 stream data + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_H2_STREAM_DATA_H +#define MHD_H2_STREAM_DATA_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_dlinked_list.h" + +#include "h2_err_codes.h" + +#include "h2_req_data.h" + +#define mhd_H2_REQ_ITEM_POS_INVALID ((size_t) (~((size_t) 0u))) + +#ifndef MHD_SIZE_UNKNOWN +# define MHD_SIZE_UNKNOWN ((uint_least64_t) 0xFFFFFFFFFFFFFFFFu) +#endif /* MHD_SIZE_UNKNOWN */ + +struct MHD_Connection; /* forward declaration */ +struct MHD_Response; /* forward declaration */ + +enum mhd_H2ReplyStage +{ + /** + * Headers should be formed and sent + */ + mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE = 0, + mhd_H2_RPL_STAGE_HEADERS_COMPLETE, + mhd_H2_RPL_STAGE_TRAILERS_INCOMPLETE, + mhd_H2_RPL_STAGE_END_STREAM, + mhd_H2_RPL_STAGE_BROKEN +}; + +struct mhd_H2ReplyFieldsData +{ + bool auto_cntn_len; + + size_t num_sent; +}; + +struct mhd_H2ReplyData +{ + /** + * Response to transmit (initially NULL). + */ + struct MHD_Response *response; + + enum mhd_H2ReplyStage stage; + + struct mhd_H2ReplyFieldsData fields; + + uint_fast64_t cntn_read_pos; + + + bool send_content; +}; + +struct mhd_H2StreamState +{ + int_least32_t send_window; + + int_least32_t recv_window; + + bool rcvd_rst_stream; + enum mhd_H2ErrorCode peer_err; + + bool sent_rst_stream; + enum mhd_H2ErrorCode mhd_err; +}; + +struct mhd_H2Stream; /* Forward declaration */ + +mhd_DLINKEDL_LINKS_DEF (mhd_H2Stream); + +struct mhd_H2Stream +{ + /** + * Must be always 'true' + */ + bool is_h2; + + uint_least32_t stream_id; + + /** + * The links in the container + */ + mhd_DLNKDL_LINKS (mhd_H2Stream, streams); + + /** + * The links in the sending list (when sending only) + */ + mhd_DLNKDL_LINKS (mhd_H2Stream, send_q); + + /** + * Pointer to the connection structure which is processing this stream + */ + struct MHD_Connection *c; + + struct mhd_H2RequestData req; + + struct mhd_H2ReplyData rpl; + + struct mhd_H2StreamState state; +}; + +#endif /* ! MHD_H2_STREAM_DATA_H */ diff --git a/src/mhd2/h2/hpack/mhd_hpack_codec.c b/src/mhd2/h2/hpack/mhd_hpack_codec.c @@ -5327,8 +5327,9 @@ mhd_hpack_enc_init (struct mhd_HpackEncContext *hk_enc) mhd_dtbl_get_table_max_size (hk_enc->dyn)); /* Set all sizes to the same initial value */ - hk_enc->new_dyn_size = mhd_hpack_def_dyn_table_size; - hk_enc->smallest_dyn_size = hk_enc->new_dyn_size; + hk_enc->dyn_size_peer = mhd_hpack_def_dyn_table_size; + hk_enc->dyn_size_new = hk_enc->dyn_size_peer; + hk_enc->dyn_size_smallest = hk_enc->dyn_size_peer; return true; /* Success exit point */ } @@ -5352,12 +5353,38 @@ mhd_hpack_enc_set_dyn_size (struct mhd_HpackEncContext *hk_enc, size_t new_dyn_size) { mhd_assert (mhd_DTBL_MAX_SIZE >= new_dyn_size); - if (hk_enc->smallest_dyn_size > new_dyn_size) - hk_enc->smallest_dyn_size = new_dyn_size; + if (hk_enc->dyn_size_smallest > new_dyn_size) + hk_enc->dyn_size_smallest = new_dyn_size; /* Postpone actual table resize to avoid several realloc() calls if multiple table resizes are performed. */ - hk_enc->new_dyn_size = new_dyn_size; + hk_enc->dyn_size_new = new_dyn_size; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) bool +mhd_hpack_enc_dyn_resize (struct mhd_HpackEncContext *hk_enc) +{ + mhd_assert (hk_enc->dyn_size_new >= hk_enc->dyn_size_smallest); + + if (mhd_dtbl_get_table_max_size (hk_enc->dyn) != hk_enc->dyn_size_new) + { +#ifndef MHD_FAVOR_SMALL_CODE + /* This is just an optimisation to simplify eviction later */ + mhd_dtbl_evict_to_size (hk_enc->dyn, + hk_enc->dyn_size_smallest); +#endif /* ! MHD_FAVOR_SMALL_CODE */ + + if (mhd_COND_HARDLY_EVER (! mhd_dtbl_resize (&(hk_enc->dyn), \ + hk_enc->dyn_size_new))) + return false; + + mhd_assert (mhd_dtbl_get_table_max_size (hk_enc->dyn) == \ + hk_enc->dyn_size_new); + } + + return true; } @@ -5959,7 +5986,7 @@ hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc, { const size_t field_size = name->size + value->size + mhd_dtbl_entry_overhead; - const size_t dyn_size = hk_enc->new_dyn_size; + const size_t dyn_size = hk_enc->dyn_size_new; const size_t dyn_used = mhd_dtbl_get_table_used (hk_enc->dyn); const size_t dyn_free = dyn_size - dyn_used; const size_t num_entries = mhd_dtbl_get_num_entries (hk_enc->dyn); @@ -6089,14 +6116,19 @@ hpack_enc_check_dyn_size_update ( size_t pos_incr; struct mhd_HpackDTblContext *restrict const dyn = hk_enc->dyn; - mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->smallest_dyn_size); - mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->new_dyn_size); - mhd_assert (hk_enc->new_dyn_size >= hk_enc->smallest_dyn_size); + mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->dyn_size_smallest); + mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->dyn_size_new); + mhd_assert (hk_enc->dyn_size_peer >= hk_enc->dyn_size_smallest); + mhd_assert (hk_enc->dyn_size_new >= hk_enc->dyn_size_smallest); mhd_assert (mhd_dtbl_get_table_max_size (dyn) \ - >= hk_enc->smallest_dyn_size); + >= hk_enc->dyn_size_smallest); + + if (mhd_dtbl_get_table_max_size (dyn) != hk_enc->dyn_size_smallest) + mhd_dtbl_evict_to_size (dyn, + hk_enc->dyn_size_smallest); - if ((mhd_dtbl_get_table_max_size (dyn) == hk_enc->smallest_dyn_size) && - (hk_enc->new_dyn_size == hk_enc->smallest_dyn_size)) + if ((hk_enc->dyn_size_smallest == hk_enc->dyn_size_peer) && + (hk_enc->dyn_size_new == hk_enc->dyn_size_peer)) { *bytes_encoded = 0u; return true; /* No resize signal needed */ @@ -6108,13 +6140,13 @@ hpack_enc_check_dyn_size_update ( pos = 0u; - if (mhd_dtbl_get_table_max_size (dyn) != hk_enc->smallest_dyn_size) + if (hk_enc->dyn_size_peer != hk_enc->dyn_size_smallest) { /* Signal the minimal size so the peer evicts entries */ pos_incr = hpack_put_number_to_buf (dyn_size_upd_msg_prfx, dyn_size_upd_msg_prfx_bits, - (uint_fast32_t) hk_enc->smallest_dyn_size, + (uint_fast32_t) hk_enc->dyn_size_smallest, out_buff_size, out_buff); @@ -6122,12 +6154,9 @@ hpack_enc_check_dyn_size_update ( return false; /* Not enough space */ pos += pos_incr; - - mhd_dtbl_evict_to_size (dyn, - hk_enc->smallest_dyn_size); } - if (hk_enc->new_dyn_size != hk_enc->smallest_dyn_size) + if (hk_enc->dyn_size_new != hk_enc->dyn_size_smallest) { if (pos == out_buff_size) return false; /* Not enough space for the second resize message */ @@ -6136,7 +6165,7 @@ hpack_enc_check_dyn_size_update ( pos_incr = hpack_put_number_to_buf (dyn_size_upd_msg_prfx, dyn_size_upd_msg_prfx_bits, - (uint_fast32_t) hk_enc->new_dyn_size, + (uint_fast32_t) hk_enc->dyn_size_new, out_buff_size - pos, out_buff + pos); @@ -6165,17 +6194,20 @@ hpack_enc_check_dyn_size_update ( static bool hpack_enc_perform_dyn_size_update (struct mhd_HpackEncContext *restrict hk_enc) { - if (mhd_dtbl_get_table_max_size (hk_enc->dyn) != hk_enc->new_dyn_size) + mhd_assert (mhd_dtbl_get_table_used (hk_enc->dyn) + <= hk_enc->dyn_size_smallest); + if (mhd_dtbl_get_table_max_size (hk_enc->dyn) != hk_enc->dyn_size_new) { if (mhd_COND_HARDLY_EVER (! mhd_dtbl_resize (&(hk_enc->dyn), \ - hk_enc->new_dyn_size))) + hk_enc->dyn_size_new))) return false; mhd_assert (mhd_dtbl_get_table_max_size (hk_enc->dyn) == \ - hk_enc->new_dyn_size); + hk_enc->dyn_size_new); } - hk_enc->smallest_dyn_size = hk_enc->new_dyn_size; + hk_enc->dyn_size_smallest = hk_enc->dyn_size_new; + hk_enc->dyn_size_peer = hk_enc->dyn_size_new; return true; } @@ -6197,11 +6229,13 @@ mhd_hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc, size_t pos_incr; enum mhd_HpackEncResultInternal enc_field_res; - mhd_assert (0u != out_buff_size); mhd_assert ((name->size & 0xFFFFFFFFu) == name->size); mhd_assert ((value->size & 0xFFFFFFFFu) == value->size); mhd_assert ((0u == name->size) || (':' != name->data[0])); + if (0u == out_buff_size) + return mhd_HPACK_ENC_BUFFER_TOO_SMALL; + pos = 0u; /* Add Dynamic Table Size Update message if needed */ @@ -6513,10 +6547,12 @@ mhd_hpack_enc_ph_status (struct mhd_HpackEncContext *restrict hk_enc, size_t pos_incr; enum mhd_HpackEncResultInternal enc_field_res; - mhd_assert (0u != out_buff_size); mhd_assert (100u <= code); mhd_assert (699u >= code); + if (0u == out_buff_size) + return mhd_HPACK_ENC_BUFFER_TOO_SMALL; + pos = 0u; /* Add Dynamic Table Size Update message if needed */ diff --git a/src/mhd2/h2/hpack/mhd_hpack_codec.h b/src/mhd2/h2/hpack/mhd_hpack_codec.h @@ -200,9 +200,9 @@ enum MHD_FIXED_ENUM_ mhd_HpackDecResult * update). * For header fields, writes "name\0value\0" to @a out_buff. * @param hk_dec the decoder context - * @param enc_data_size the size of @a enc_data + * @param enc_data_size the size of @a enc_data, must not be zero * @param enc_data the encoded data - * @param out_buff_size the size of @a out_buff + * @param out_buff_size the size of @a out_buff, must be at least two bytes * @param[out] out_buff the output buffer for the decoded strings * @param[out] name_len to be set to the length of the name, not counting * zero-terminating @@ -256,6 +256,9 @@ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); /** * Set the current maximum dynamic table size for the encoder. * + * To avoid repetitive memory allocations, the real table resize is performed + * later. + * * @param hk_enc the encoder context * @param new_dyn_size the new limit in bytes, * must be <= #mhd_DTBL_MAX_SIZE and must be within @@ -266,6 +269,19 @@ mhd_hpack_enc_set_dyn_size (struct mhd_HpackEncContext *hk_enc, size_t new_dyn_size) MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); +/** + * Perform dynamic table resize if it is pending. + * + * If table is resized before encoding new fields, then encoding functions + * never return #mhd_HPACK_ENC_RES_ALLOC_ERR. + * + * @param hk_enc the encoder context + * @return 'true' on success, + * 'false' on allocation error + */ +MHD_INTERNAL bool +mhd_hpack_enc_dyn_resize (struct mhd_HpackEncContext *hk_enc) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); /** * Preference for HPACK encoding @@ -450,9 +466,11 @@ enum MHD_FIXED_ENUM_ mhd_HpackEncResult * @param out_buff_size the size of @a out_buff in bytes * @param[out] out_buff the output buffer to receive the encoded data * @param[out] bytes_encoded set to the number of bytes written to @a out_buff - * @return #mhd_HPACK_ENC_RES_OK on success, - * #mhd_HPACK_ENC_BUFFER_TOO_SMALL if the output buffer is too small, - * #mhd_HPACK_ENC_RES_ALLOC_ERR on dynamic table allocation error + * @return #mhd_HPACK_ENC_RES_OK on success; + * #mhd_HPACK_ENC_BUFFER_TOO_SMALL if the output buffer is too small; + * #mhd_HPACK_ENC_RES_ALLOC_ERR on dynamic table allocation error, + * never returned if #mhd_hpack_enc_set_dyn_size() was not earlier or + * if #mhd_hpack_enc_dyn_resize() after #mhd_hpack_enc_set_dyn_size() */ MHD_INTERNAL enum mhd_HpackEncResult mhd_hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc, diff --git a/src/mhd2/h2/hpack/mhd_hpack_enc_types.h b/src/mhd2/h2/hpack/mhd_hpack_enc_types.h @@ -64,22 +64,29 @@ struct mhd_HpackEncContext /** * The latest set dynamic table maximum size. * If it is different from the current size set in the @a dyn, then the - * Dynamic Table Size Update message will be added automatically before the - * first encoded header. + * @a dyn is resized before adding new field. * Set initially to the default size of the dynamic table. */ - size_t new_dyn_size; + size_t dyn_size_new; /** * The smallest dynamic table size used after the last Dynamic Table Size * Update message. * If this value is different from the current size set in the @a dyn, then - * two Dynamic Table Size Update messages will be added automatically before - * the first encoded header (first message with the minimal size and the - * second message with the final size). + * the fields from dynamic table are evicted to specified size before adding + * new fields. + * Set initially to the default size of the dynamic table. + */ + size_t dyn_size_smallest; + + /** + * Last reported to peer size of the dynamic table. + * If @a new_dyn_size or @a smallest_dyn_size are different then + * Dynamic Table Size Update messages are added automatically before the + * first encoded header. * Set initially to the default size of the dynamic table. */ - size_t smallest_dyn_size; + size_t dyn_size_peer; }; #endif /* ! MHD_HPACK_ENC_TYPES_H */ diff --git a/src/mhd2/mhd_action.h b/src/mhd2/mhd_action.h @@ -388,4 +388,21 @@ struct MHD_UploadAction union mhd_UploadActionData data; }; + +/** + * 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; +}; + + #endif /* ! MHD_ACTION_H */ diff --git a/src/mhd2/mhd_comm_layer_state.h b/src/mhd2/mhd_comm_layer_state.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/mhd_comm_layer_state.h + * @brief The definition of simplified state of communication layer + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_COMM_LAYER_STATE_H +#define MHD_COMM_LAYER_STATE_H 1 + +#include "mhd_sys_options.h" + +/** + * Simplified state of communication layer + */ +enum MHD_FIXED_ENUM_ mhd_CommLayerState +{ + /** + * This (or lower) layer is processing the data. + * Upper layer communication is not possible. + */ + mhd_COMM_LAYER_PROCESSING = -1 + , + /** + * This layer (and all lower layers) is connected. + * Upper layer communication can be performed. + */ + mhd_COMM_LAYER_OK = 0, + /** + * This (or lower) Layer is broken. + * Connection must be closed. + */ + mhd_COMM_LAYER_BROKEN = 1 +}; + +#endif /* ! MHD_COMM_LAYER_STATE_H */ diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ /* This file is part of GNU libmicrohttpd. - Copyright (C) 2014-2024 Evgeny Grin (Karlson2k) + Copyright (C) 2014-2025 Evgeny Grin (Karlson2k) Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff GNU libmicrohttpd is free software; you can redistribute it and/or @@ -73,6 +73,10 @@ # include "mhd_upgrade.h" #endif /* MHD_SUPPORT_UPGRADE */ +#ifdef MHD_SUPPORT_HTTP2 +# include "mhd_http_layer_state.h" +#endif + #include "mhd_socket_error.h" #include "mhd_public_api.h" @@ -81,6 +85,10 @@ # include "mhd_tls_choice.h" /* For the TLS struct forward declaration */ #endif +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_conn_data.h" +#endif + /** * Minimum reasonable size by which MHD tries to increment read/write buffers. * We usually begin with half the available pool space for the @@ -200,6 +208,46 @@ enum mhd_TlsBufDataIn #endif /* MHD_SUPPORT_HTTPS */ +#ifdef MHD_SUPPORT_HTTP2 +/** + * The HTTP protocol version family + */ +enum MHD_FIXED_ENUM_ mhd_HttpVerFamily +{ + /** + * Not yet detected + */ + mhd_HTTP_VER_FAM_NOT_SET = 0 + , + /** + * Not HTTP/2 (assuming HTTP/1.1 or HTTP/1.0) + */ + mhd_HTTP_VER_FAM_NOT_2 + , + /*** + * HTTP/2 (also called HTTP/2.0) + */ + mhd_HTTP_VER_FAM_2 + , + /** + * Unsupported (but detected) protocol family + */ + mhd_HTTP_VER_FAM_UNSUPPORTED = 99 + , + /** + * Invalid protocol family + */ + mhd_HTTP_VER_FAM_INVALID = 100 +}; + + +struct mhd_HttpCommLayer +{ + enum mhd_HttpLayerState state; + enum mhd_HttpVerFamily fam; +}; +#endif /* MHD_SUPPORT_HTTP2 */ + /** * What is this connection waiting for? */ @@ -570,6 +618,18 @@ struct MHD_Connection enum mhd_TlsBufDataIn tls_has_data_in; #endif /* MHD_SUPPORT_HTTPS */ +#ifdef MHD_SUPPORT_HTTP2 + /** + * HTTP communication layer + */ + struct mhd_HttpCommLayer h_layer; + + /** + * HTTP/2 data + * Used only if @ h_layer.fam is #mhd_HTTP_VER_FAM_2 + */ + struct mhd_H2ConnData h2; +#endif /** * 'true' if connection is in 'process ready' list, * 'false' otherwise @@ -800,4 +860,11 @@ struct MHD_Connection # define mhd_C_HAS_TLS_DATA_IN(c) (0) #endif /* ! MHD_SUPPORT_HTTPS */ +#ifdef MHD_SUPPORT_HTTP2 +# define mhd_C_IS_HTTP2(c) \ + (mhd_HTTP_VER_FAM_2 == c->h_layer.fam) +#else /* ! MHD_SUPPORT_HTTP2 */ +# define mhd_C_IS_HTTP2(c) (! ! 0) +#endif /* ! MHD_SUPPORT_HTTP2 */ + #endif /* ! MHD_CONNECTION_H */ diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h @@ -1071,6 +1071,27 @@ struct mhd_DaemonLargeBuffer #endif }; +#ifdef MHD_SUPPORT_HTTP2 + +/** + * Generic settings for HTTP layer communication handling. + * + * These settings do not include specific settings for requests processing. + */ +struct mhd_DaemonHttpSettings +{ + /** + * 'true' if HTTP/1.x communication is allowed + */ + bool http1x; + /** + * 'true' if HTTP/2 communication is allowed + */ + bool http2; +}; + +#endif /* MHD_SUPPORT_HTTP2 */ + /** * Settings for requests processing */ @@ -1212,6 +1233,18 @@ struct MHD_Daemon */ struct mhd_DaemonConnections conns; + + /* HTTP communication layer */ +#ifdef MHD_SUPPORT_HTTP2 + /** + * Generic settings for HTTP layer communication handling. + * + * These settings do not include specific settings for requests processing. + */ + struct mhd_DaemonHttpSettings http_cfg; +#endif /* MHD_SUPPORT_HTTP2 */ + + /* Request processing data */ /** @@ -1319,4 +1352,13 @@ struct MHD_Daemon # define mhd_D_HAS_AUTH_DIGEST(d) (! ! 0) #endif +#ifdef MHD_SUPPORT_HTTP2 +# define mhd_D_IS_HTTP1_ENABLED(d) (d->http_cfg.http1x) +# define mhd_D_IS_HTTP2_ENABLED(d) (d->http_cfg.http2) +#else /* ! MHD_SUPPORT_HTTP2 */ +# define mhd_D_IS_HTTP1_ENABLED(d) (! 0) +# define mhd_D_IS_HTTP2_ENABLED(d) (! ! 0) +#endif /* ! MHD_SUPPORT_HTTP2 */ + + #endif /* ! MHD_DAEMON_H */ diff --git a/src/mhd2/mhd_http_layer_state.h b/src/mhd2/mhd_http_layer_state.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/mhd_http_layer_state.h + * @brief The definition of the HTTP layer connection state + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_HTTP_LAYER_STATE_H +#define MHD_HTTP_LAYER_STATE_H 1 + +#include "mhd_sys_options.h" + +/** + * HTTP layer communication state + */ +enum MHD_FIXED_ENUM_ mhd_HttpLayerState +{ + /** + * Performing handshake + */ + mhd_HTTP_LAYER_PREFACE + , + /** + * The HTTP layer is fully connected, normal HTTP communication + */ + mhd_HTTP_LAYER_CONNECTED + , + /** + * Performing graceful closing of the HTTP communication + */ + mhd_HTTP_LAYER_CLOSING + , + /** + * HTTP communication closed + */ + mhd_HTTP_LAYER_CLOSED + , + /** + * HTTP communication broken + */ + mhd_HTTP_LAYER_BROKEN +}; + +#endif /* ! MHD_HTTP_LAYER_STATE_H */ diff --git a/src/mhd2/mhd_lib_init.c b/src/mhd2/mhd_lib_init.c @@ -67,6 +67,9 @@ #ifdef MHD_SUPPORT_HTTPS # include "mhd_tls_funcs.h" #endif +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/hpack/h2_huffman_codec.h" +#endif #include "mhd_lib_init.h" #include "mhd_lib_init_auto.h" @@ -377,6 +380,9 @@ mhd_lib_global_full_init_once (void) #ifdef MHD_SUPPORT_HTTPS mhd_tls_global_init_once (); #endif /* MHD_SUPPORT_HTTPS */ +#ifdef MHD_SUPPORT_HTTP2 + mhd_h2_huffman_init (); +#endif /* MHD_SUPPORT_HTTP2 */ mhd_lib_fully_inited_once = true; diff --git a/src/mhd2/mhd_request.h b/src/mhd2/mhd_request.h @@ -64,21 +64,9 @@ # include "mhd_post_parser.h" #endif - -/** - * 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; -}; +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_stream_data.h" +#endif /** * The request line processing data @@ -398,6 +386,12 @@ struct mhd_ReqAuthData */ struct MHD_Request { +#ifdef MHD_SUPPORT_HTTP2 + /** + * Always 'false' in HTTP/1.x requests + */ + bool is_http2; +#endif /* MHD_SUPPORT_HTTP2 */ /** * Linked list of parsed headers. */ @@ -556,5 +550,23 @@ struct MHD_Request union MHD_HeadersProcessing hdrs; }; +#ifdef MHD_SUPPORT_HTTP2 +# define mhd_REQ_IS_HTTP2(req) ((req)->is_http2) +#else /* ! MHD_SUPPORT_HTTP2 */ +# define mhd_REQ_IS_HTTP2(req) (! ! 0) +#endif /* ! MHD_SUPPORT_HTTP2 */ + +#ifdef MHD_SUPPORT_HTTP2 +# define mhd_REQ_GET_ACT_UNION(req) \ + (mhd_REQ_IS_HTTP2 ((req)) ? \ + &(((struct mhd_H2RequestData*) (req))->app_act) : \ + &((req)->app_act)) +#else /* ! MHD_SUPPORT_HTTP2 */ +# define mhd_REQ_GET_ACT_UNION(req) (&((req)->app_act)) +#endif /* ! MHD_SUPPORT_HTTP2 */ + +#define mhd_REQ_GET_ACT_HEAD(req) (&(mhd_REQ_GET_ACT_UNION (req)->head_act)) + +#define mhd_REQ_GET_ACT_UPLD(req) (&(mhd_REQ_GET_ACT_UNION (req)->upl_act)) #endif /* ! MHD_REQUEST_H */ diff --git a/src/mhd2/mhd_response.h b/src/mhd2/mhd_response.h @@ -66,6 +66,10 @@ #include "mhd_atomic_counter.h" +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_resp_data.h" +#endif + struct ResponseOptions; /* forward declaration */ @@ -88,6 +92,13 @@ struct mhd_ResponseHeader */ struct MHD_String value; +#ifdef MHD_SUPPORT_HTTP2 + /** + * HTTP/2-specific header / field data + */ + struct mhd_H2ResponseHeader h2; +#endif /* MHD_SUPPORT_HTTP2 */ + /** * The links to other headers */ diff --git a/src/mhd2/mhd_str.c b/src/mhd2/mhd_str.c @@ -44,12 +44,15 @@ #include "mhd_sys_options.h" -#include "mhd_str.h" +#include "mhd_assert.h" +#include "mhd_limits.h" + +#include "mhd_constexpr.h" +#include "mhd_assume.h" #include <string.h> -#include "mhd_assert.h" -#include "mhd_limits.h" +#include "mhd_str.h" #ifdef MHD_FAVOR_SMALL_CODE # ifdef mhd_static_inline @@ -97,7 +100,9 @@ isasciilower (char c) mhd_static_inline MHD_FN_CONST_ bool isasciiupper (char c) { - return (c <= 'Z') && (c >= 'A'); + const unsigned int uc = (unsigned int) (unsigned char) c; + const unsigned int t = uc - (unsigned int) (unsigned char) 'A'; + return (((unsigned int) ('Z' - 'A')) >= t); } @@ -162,8 +167,6 @@ isasciialnum (char 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 @@ -176,10 +179,12 @@ isasciialnum (char c) mhd_static_inline MHD_FN_CONST_ char toasciilower (char c) { - return isasciiupper (c) ? (char) (0x20u | (unsigned char) c) : c; + return (char) (((unsigned char) c) | ((isasciiupper (c) ? 1u : 0u) << 5u)); } +#if 0 /* Disable unused functions. */ + /** * Convert US-ASCII character to upper case. * If character is lower case letter in US-ASCII than it's converted to upper @@ -839,6 +844,24 @@ charsequalcaseless (char c1, char c2) } +/** + * Compare mixed case and lower case characters. + * + * @param mc the mixed case char to compare + * @param lc the lower case char to compare + * @return boolean 'true' if chars are caseless equal, false otherwise + */ +mhd_static_inline MHD_FN_CONST_ bool +charsequallowercase (char mc, char lc) +{ + char uc; + if (mc == lc) + return true; + uc = ((char) (~0x20u & (unsigned char) lc)); + return (mc == uc) && isasciiupper (mc); +} + + #else /* !HAVE_INLINE_FUNCS */ @@ -913,8 +936,8 @@ charsequalcaseless (char c1, char c2) * @param c character to convert * @return converted to lower case character */ -# define toasciilower(c) ((isasciiupper (c)) ? (((char) (c)) - 'A' + 'a') : \ - ((char) (c))) +# define toasciilower(c) \ + ((isasciiupper (c)) ? (((char) (c)) - 'A' + 'a') : ((char) (c))) /** @@ -988,6 +1011,17 @@ static const char map_value_to_xdigit[16] = (((0x20u | (unsigned char) (c1)) == (0x20u | (unsigned char) (c2))) \ && isasciilower (((char) (0x20u | (unsigned char) (c2))))) ) +/** + * Compare mixed case and lower case characters. + * + * @param mc the mixed case char to compare + * @param lc the lower case char to compare + * @return boolean 'true' if chars are caseless equal, false otherwise + */ +#define charsequallowercase(mc,lc) \ + ( ((mc) == (lc)) || \ + (((0x20u | (unsigned char) (mc)) == ((unsigned char) (lc))) && \ + isasciilower (lc)) ) #endif /* !HAVE_INLINE_FUNCS */ @@ -1061,6 +1095,56 @@ mhd_str_equal_caseless_bin_n (const char *const str1, MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_IN_ (2) bool +mhd_str_equal_lowercase_bin_n (const char *const mixstr, + const char *const lowstr, + size_t len) +{ + size_t i; + + for (i = 0; i < len; ++i) + { + const char mc = mixstr[i]; + const char lc = lowstr[i]; + mhd_assert (! isasciiupper (lc)); + if (! charsequallowercase (mc, lc)) + return false; + } + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PAR_OUT_SIZE_ (3,1) void +mhd_str_to_lowercase_bin_n (size_t size, + const char *restrict inbuff, + char *restrict outbuff) +{ + size_t i; + + for (i = 0; i < size; ++i) + outbuff[i] = toasciilower (inbuff[i]); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) bool +mhd_str_is_lowercase_bin_n (size_t len, + const char *restrict str) +{ + size_t i; + + for (i = 0; i < len; ++i) + if (isasciiupper (str[i])) + return false; + + return true; +} + + +MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_CSTR_ (1) MHD_FN_PAR_IN_ (1) MHD_FN_PAR_IN_ (2) bool mhd_str_has_token_caseless (const char *restrict str, @@ -2249,6 +2333,272 @@ mhd_str_pct_decode_in_place_lenient (char *restrict str, } +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_INOUT_SIZE_ (2,1) size_t +mhd_str_dec_norm_uri_path (size_t str_len, + char *restrict str) +{ + size_t r; + size_t w; + + mhd_assert ((str_len + 3u) > str_len); /* Algo does not work with str_len close to max */ + + for (r = 0u, w = 0u; str_len > r; ++r) + { + /* Process all segments not started with "/" if any */ + char c; + mhd_ASSUME (w <= r); + c = str[r]; + if ('/' == c) + break; /* Processed after this loop */ + if ('%' == c && str_len > (r + 2u)) + { + const int h = xdigittovalue (str[r + 1u]); + const int l = xdigittovalue (str[r + 2u]); + if ((0 <= h) && (0 <= l)) + { + char dec; + dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u)); + if ('/' != dec) + { + c = dec; + r += 2u; + } + } + } + if ('.' == c) + { + char c2; + if (str_len == r + 1u) + continue; /* Skip "."; actually stop */ + mhd_ASSUME (w <= r); + c2 = str[r + 1u]; + if ('/' == c2) + { + ++r; + continue; /* Skip "./" */ + } + if ('%' == c2 && str_len > (r + 3u)) + { + const int h = xdigittovalue (str[r + 2u]); + const int l = xdigittovalue (str[r + 3u]); + if ((0 <= h) && (0 <= l)) + { + char dec; + dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u)); + if ('/' != dec) + { + c2 = dec; + r += 2u; + } + } + } + if ('.' == c2) + { + char c3; + if (str_len == r + 2u) + { + ++r; + continue; /* Skip ".."; actually stop */ + } + mhd_ASSUME (w <= r); + c3 = str[r + 2u]; + if ('/' == c3) + { + r += 2u; + continue; /* Skip "../" */ + } + str[w++] = c; + str[w++] = c2; + /* c3 has not been percent-decoded */ + r += 2u; + } + else + { + str[w++] = c; + str[w++] = c2; + r += 2u; + } + } + else + { + str[w++] = c; + r += 1u; + } + break; + } + mhd_ASSUME (w <= r); + /* Found first segment with is not "../" and is not "./" */ + for ((void) r; str_len > r && '/' != str[r]; ++r) + { + char c; + mhd_ASSUME (w <= r); + c = str[r]; + if ('%' == c && str_len > (r + 2u)) + { + const int h = xdigittovalue (str[r + 1u]); + const int l = xdigittovalue (str[r + 2u]); + if ((0 <= h) && (0 <= l)) + { + char dec; + dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u)); + if ('/' != dec) + { + c = dec; + r += 2u; + } + } + } + mhd_ASSUME (w <= r); + str[w++] = c; + } + if (str_len > r) + { + /* Found first segment started with '/' */ + mhd_ASSUME ('/' == str[r]); + while (str_len > r) + { + char slash_chr = str[r]; + size_t seg_start = w; + mhd_ASSUME ('/' == slash_chr); + str[w++] = slash_chr; + ++r; + if (str_len > r) + { + char c; + mhd_ASSUME (w <= r); + c = str[r]; + if ('/' == c) + continue; + if ('%' == c && str_len > (r + 2u)) + { + const int h = xdigittovalue (str[r + 1u]); + const int l = xdigittovalue (str[r + 2u]); + if ((0 <= h) && (0 <= l)) + { + char dec; + dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u)); + if ('/' != dec) + { + c = dec; + r += 2u; + } + } + } + if ('.' == c) + { + char c2; + if (str_len == r + 1u) + { + ++r; + break; /* Skip ".", leave bare '/' */ + } + mhd_ASSUME (w <= r); + c2 = str[r + 1u]; + if ('/' == c2) + { + w = seg_start; + ++r; + continue; /* Skip "."; go to the next "/", which will be written again */ + } + if ('%' == c2 && str_len > (r + 3u)) + { + const int h = xdigittovalue (str[r + 2u]); + const int l = xdigittovalue (str[r + 3u]); + if ((0 <= h) && (0 <= l)) + { + char dec; + dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u)); + if ('/' != dec) + { + c2 = dec; + r += 2u; + } + } + } + if ('.' == c2) + { + char c3; + if (str_len == r + 2u) + { + w = seg_start; + if (0 < w) + do + { + --w; + } while (0 < w && '/' != str[w]); + str[w++] = '/'; + r += 2u; + break; /* Skip ".."; replace prev segment with '/' */ + } + mhd_ASSUME (w <= r); + c3 = str[r + 2u]; + if ('/' == c3) + { + w = seg_start; + if (0 < w) + do + { + --w; + } while (0 < w && '/' != str[w]); + r += 2u; + continue; /* Skip ".."; put next '/' to the start of prev segment */ + } + str[w++] = c; + str[w++] = c2; + /* c3 has not been percent-decoded */ + r += 2u; + } + else + { + str[w++] = c; + str[w++] = c2; + r += 2u; + } + } + else + { + str[w++] = c; + r += 1u; + } + mhd_assert (seg_start < w); + } + for ((void) r; str_len > r && '/' != str[r]; ++r) + { + /* Process the end of the segment */ + char c; + mhd_ASSUME (w <= r); + c = str[r]; + if ('%' == c && str_len > (r + 2u)) + { + const int h = xdigittovalue (str[r + 1u]); + const int l = xdigittovalue (str[r + 2u]); + if ((0 <= h) && (0 <= l)) + { + char dec; + dec = (char) ((unsigned char) l | (((unsigned char) h) << 4u)); + if ('/' != dec) + { + c = dec; + r += 2u; + } + } + } + mhd_ASSUME (w <= r); + str[w++] = c; + } + } + mhd_assert (0u != w); + mhd_assert ((r == str_len) || ('/' == str[w - 1u])); + } + + if (str_len > w) + str[w] = '\0'; + + return w; +} + + #ifdef MHD_SUPPORT_AUTH_DIGEST MHD_INTERNAL MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ diff --git a/src/mhd2/mhd_str.h b/src/mhd2/mhd_str.h @@ -137,6 +137,49 @@ MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1) MHD_FN_PAR_IN_ (2); && mhd_str_equal_caseless_bin_n (arr,str,l)) /** + * Check two string for equality, converting first string to US-ASCII lower case + * before comparing 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 mixstr mixed case string to compare + * @param lowstr lower case 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_lowercase_bin_n (const char *const mixstr, + const char *const lowstr, + size_t len) +MHD_FN_PURE_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_ (1) MHD_FN_PAR_IN_ (2); + +/** + * Convert sequence of bytes to lower case US-ASCII letters. + * @param size the size of the data in the @a inbuff + * @param inbuff the input data, does not need to be zero-terminated; + * if it is zero-terminated and zero-termination of @a outbuff + * is needed, make sure that @a size includes zero-termination + * @param[out] outbuff the output buffer; should have at least @a size bytes + * available + */ +MHD_INTERNAL void +mhd_str_to_lowercase_bin_n (size_t size, + const char *restrict inbuff, + char *restrict outbuff) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2,1) MHD_FN_PAR_OUT_SIZE_ (3,1); + +/** + * Check whether the string does not contain US-ASCII upper case letters. + * @param len the length of the @a str + * @param str the string to check, does not need to be zero terminated + * @return 'true' if not US-ASCII letter found in the string, + * 'false' otherwise. + */ +MHD_INTERNAL bool +mhd_str_is_lowercase_bin_n (size_t len, + const char *restrict str) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2,1); + +/** * 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 @@ -649,6 +692,12 @@ mhd_str_pct_decode_in_place_lenient (char *restrict str, bool *restrict broken_encoding) MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (1); +MHD_INTERNAL size_t +mhd_str_dec_norm_uri_path (size_t str_len, + char *restrict str) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_INOUT_SIZE_ (2,1); + + #ifdef MHD_SUPPORT_AUTH_DIGEST /** * Check two strings for equality, "unquoting" the first string from quoted diff --git a/src/mhd2/mhd_stream.h b/src/mhd2/mhd_stream.h @@ -47,6 +47,8 @@ #include "mhd_sys_options.h" +#include "sys_bool_type.h" + /** * The HTTP stream data. * For HTTP/1.x this information held in MHD_Connection structure @@ -54,10 +56,15 @@ struct MHD_Stream { /** - * The dummy member. - * Do not use + * Always 'false' for HTTP/1.x streams */ - int place_holder; + bool is_http2; }; +#ifdef MHD_SUPPORT_HTTP2 +# define mhd_STRM_IS_HTTP2(stream) ((stream)->is_http2) +#else /* ! MHD_SUPPORT_HTTP2 */ +# define mhd_STRM_IS_HTTP2(stream) (! ! 0) +#endif /* ! MHD_SUPPORT_HTTP2 */ + #endif /* ! MHD_STREAM_H */ diff --git a/src/mhd2/mhd_tls_enums.h b/src/mhd2/mhd_tls_enums.h @@ -87,4 +87,24 @@ enum MHD_FIXED_ENUM_ mhd_TlsProcedureResult mhd_TLS_PROCED_FAILED }; +/** + * Protocol selected by ALPN + */ +enum MHD_FIXED_ENUM_ mhd_TlsAlpnProt +{ + /** + * The protocol was not selected by ALPN + * ALPN is not used by the client or TLS backend does not support ALPN + */ + mhd_TLS_ALPN_PROT_NOT_SELECTED + , + mhd_TLS_ALPN_PROT_HTTP1_0 + , + mhd_TLS_ALPN_PROT_HTTP1_1 + , + mhd_TLS_ALPN_PROT_HTTP2 + , + mhd_TLS_ALPN_PROT_ERROR +}; + #endif /* ! MHD_TLS_ENUMS_H */ diff --git a/src/mhd2/mhd_tls_funcs.h b/src/mhd2/mhd_tls_funcs.h @@ -224,6 +224,13 @@ #define mhd_tls_conn_get_tls_ver(c_tls,tls_ver_out) \ mhd_TLS_FUNC (_conn_get_tls_ver)((c_tls),(tls_ver_out)) +/** + * Get a protocol selected by ALPN + * @param c_tls the connection TLS handle + * @return the selected protocol code + */ +#define mhd_tls_conn_get_alpn_prot(c_tls) \ + mhd_TLS_FUNC (_conn_get_alpn_prot)((c_tls)) /* ** General information function ** */ diff --git a/src/mhd2/request_get_value.c b/src/mhd2/request_get_value.c @@ -56,6 +56,10 @@ #include "mhd_assert.h" #include "mhd_str.h" +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_req_get_items.h" +#endif /* MHD_SUPPORT_HTTP2 */ + #include "mhd_public_api.h" @@ -75,6 +79,15 @@ mhd_request_get_value_n (struct MHD_Request *restrict request, mhd_assert (strlen (key) == key_len); +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_REQ_IS_HTTP2 (request)) + return mhd_h2_request_get_value_n (request, + kind, + key_len, + key, + value_out); +#endif /* MHD_SUPPORT_HTTP2 */ + if (MHD_VK_POSTDATA != kind) { struct mhd_RequestField *f; @@ -185,6 +198,14 @@ MHD_request_get_values_cb (struct MHD_Request *request, { size_t count; + #ifdef MHD_SUPPORT_HTTP2 + if (mhd_REQ_IS_HTTP2 (request)) + return mhd_h2_request_get_values_cb (request, + kind, + iterator, + iterator_cls); +#endif /* MHD_SUPPORT_HTTP2 */ + count = 0; if (MHD_VK_POSTDATA != kind) { diff --git a/src/mhd2/response_add_header.c b/src/mhd2/response_add_header.c @@ -45,6 +45,9 @@ #include "mhd_sys_options.h" +#include "mhd_str_macros.h" +#include "mhd_arr_num_elems.h" + #include "response_add_header.h" #include "mhd_response.h" #include "mhd_locks.h" @@ -52,39 +55,168 @@ #include <string.h> #include "sys_malloc.h" +#include "mhd_str.h" + #include "mhd_public_api.h" +#ifdef MHD_SUPPORT_HTTP2 +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_CSTR_ (2) MHD_FN_PAR_IN_SIZE_ (2,1) bool +is_name_h2_allowed (size_t name_len, + const char name[MHD_FN_PAR_DYN_ARR_SIZE_ (name_len)]) +{ + static const struct MHD_String h2_forbidden[] = { + mhd_MSTR_INIT ("Connection"), + mhd_MSTR_INIT ("Transfer-Encoding"), + mhd_MSTR_INIT ("Upgrade"), + mhd_MSTR_INIT ("Keep-Alive"), + mhd_MSTR_INIT ("Proxy-Connection") + }; + size_t i; + + for (i = 0u; i < mhd_ARR_NUM_ELEMS (h2_forbidden); ++i) + { + const struct MHD_String *const frbdn = h2_forbidden + i; + + if (frbdn->len != name_len) + continue; + if (mhd_str_equal_caseless_bin_n (frbdn->cstr, + name, + name_len)) + return false; + } + return true; +} + + +#endif /* MHD_SUPPORT_HTTP2 */ 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 +MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_CSTR_ (3) +MHD_FN_PAR_NONNULL_ (5) MHD_FN_PAR_CSTR_ (5) bool response_add_header_no_check ( - struct MHD_Response *response, + struct MHD_Response *restrict response, size_t name_len, - const char name[MHD_FN_PAR_DYN_ARR_SIZE_ (name_len)], + const char *restrict name, size_t value_len, - const char value[MHD_FN_PAR_DYN_ARR_SIZE_ (value_len)]) + const char *restrict value) { char *buf; + size_t pos; struct mhd_ResponseHeader *new_hdr; + size_t strings_size = 0u; +#ifdef MHD_SUPPORT_HTTP2 + bool h2_allowed; + bool name_is_lower = false; + size_t val_empty_prf = 0u; + size_t val_empty_suf = 0u; + + mhd_assert (0 == name[name_len]); + mhd_assert (0 == value[value_len]); + + h2_allowed = is_name_h2_allowed (name_len, + name); + if (h2_allowed) + { + name_is_lower = mhd_str_is_lowercase_bin_n (name_len, + name); + strings_size += (name_is_lower ? 0u : (name_len + 1u)); + + while ((' ' == value[val_empty_prf]) || ('\t' == value[val_empty_prf])) + ++val_empty_prf; + if (val_empty_prf != value_len) + { + while ((' ' == value[value_len - 1u - val_empty_suf]) + || ('\t' == value[value_len - 1u - val_empty_suf])) + ++val_empty_suf; + } + + mhd_assert (val_empty_prf <= value_len); + mhd_assert ((val_empty_suf < value_len) || (0u == value_len)); + mhd_assert (val_empty_prf + val_empty_suf <= value_len); + + if ((0u != val_empty_prf) || (0u != val_empty_suf)) + strings_size += value_len + 1u - val_empty_prf - val_empty_suf; + } +#endif /* MHD_SUPPORT_HTTP2 */ + + mhd_assert (0 == name[name_len]); + mhd_assert (0 == value[value_len]); + + strings_size += name_len + 1u; + strings_size += value_len + 1u; new_hdr = (struct mhd_ResponseHeader *) - malloc (sizeof(struct mhd_ResponseHeader) + name_len - + value_len + 2); + malloc (sizeof(struct mhd_ResponseHeader) + strings_size); 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; + pos = 0u; + memcpy (buf + pos, name, name_len + 1u); + new_hdr->name.cstr = buf + pos; new_hdr->name.len = name_len; - buf += name_len + 1; - memcpy (buf, value, value_len); - buf[value_len] = 0; - new_hdr->value.cstr = buf; + pos += name_len + 1u; + memcpy (buf + pos, value, value_len + 1u); + new_hdr->value.cstr = buf + pos; new_hdr->value.len = value_len; + pos += value_len + 1u; + +#ifdef MHD_SUPPORT_HTTP2 + if (h2_allowed) + { + if (! name_is_lower) + { + mhd_str_to_lowercase_bin_n (name_len + 1u, + name, + buf + pos); + new_hdr->h2.name.data = buf + pos; + new_hdr->h2.name.size = name_len; + pos += name_len + 1u; + } + else + { + new_hdr->h2.name.data = new_hdr->name.cstr; + new_hdr->h2.name.size = new_hdr->name.len; + } + + if ((0u != val_empty_prf) || (0u != val_empty_suf)) + { + memcpy (buf + pos, + value + val_empty_prf, + value_len - val_empty_prf - val_empty_suf); + buf[pos + value_len - val_empty_prf - val_empty_suf] = 0; + new_hdr->h2.value.data = buf + pos; + new_hdr->h2.value.size = value_len - val_empty_prf - val_empty_suf; + pos += value_len - val_empty_prf - val_empty_suf + 1u; + } + else + { + new_hdr->h2.value.data = new_hdr->value.cstr; + new_hdr->h2.value.size = new_hdr->value.len; + } + // TODO: implement checking name for "never-index" patterns and other indexing preferences patterns + } + else + { + new_hdr->h2.name.data = NULL; + new_hdr->h2.name.size = 0u; + new_hdr->h2.value.data = NULL; + new_hdr->h2.value.size = 0u; + } + mhd_assert ((NULL != new_hdr->h2.name.data) + || (0u == new_hdr->h2.name.size)); + mhd_assert ((NULL != new_hdr->h2.value.data) + || (0u == new_hdr->h2.value.size)); + mhd_assert ((NULL != new_hdr->h2.name.data) || \ + (NULL == new_hdr->h2.value.data)); + mhd_assert ((NULL != new_hdr->h2.value.data) || \ + (NULL == new_hdr->h2.name.data)); +#endif /* MHD_SUPPORT_HTTP2 */ + + mhd_assert (strings_size == pos); + (void) pos; /* Mute compiler warning in non-debug builds */ mhd_DLINKEDL_INIT_LINKS (new_hdr, headers); mhd_DLINKEDL_INS_LAST (response, new_hdr, headers); @@ -108,9 +240,9 @@ mhd_response_remove_all_headers (struct MHD_Response *restrict r) static enum MHD_StatusCode -response_add_header_int (struct MHD_Response *response, - const char *name, - const char *value) +response_add_header_int (struct MHD_Response *restrict response, + const char *restrict name, + const char *restrict value) { const size_t name_len = strlen (name); const size_t value_len = strlen (value); diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c @@ -755,6 +755,77 @@ mhd_conn_remove_from_timeout_lists (struct MHD_Connection *restrict c) } +/* return 'true' is lingering needed, 'false' is lingering is not needed */ +static MHD_FN_PAR_NONNULL_ALL_ bool +conn_start_socket_closing (struct MHD_Connection *restrict c, + bool close_hard) +{ + bool need_lingering; + /* 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->sk.fd); + c->stage = mhd_HTTP_STAGE_PRE_CLOSING; + c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + + return false; + } + + mhd_assert (c->sk.state.rmt_shut_wr || \ + ! mhd_SOCKET_ERR_IS_HARD (c->sk.state.discnt_err)); + + need_lingering = ! c->sk.state.rmt_shut_wr; + if (need_lingering) + { +#ifdef MHD_SUPPORT_HTTPS + if (mhd_C_HAS_TLS (c)) + { + if ((0 != (((unsigned int) c->sk.ready) + & mhd_SOCKET_NET_STATE_SEND_READY)) + || c->sk.props.is_nonblck) + need_lingering = + (mhd_TLS_PROCED_FAILED != mhd_tls_conn_shutdown (c->tls)); + } + else +#endif /* MHD_SUPPORT_HTTPS */ + if (1) + { + need_lingering = mhd_socket_shut_wr (c->sk.fd); + if (need_lingering) + need_lingering = (! c->sk.state.rmt_shut_wr); /* Skip as already closed */ + } + } + + return need_lingering; +} + + +#ifdef MHD_SUPPORT_HTTP2 + +static MHD_FN_PAR_NONNULL_ALL_ void +conn_h2_start_closing (struct MHD_Connection *restrict c, + bool close_hard) +{ + mhd_assert (mhd_C_IS_HTTP2 (c)); + mhd_assert (c->h2.dbg.h2_deinited); + mhd_assert (! c->rq.app_aware); + + conn_start_socket_closing (c, + close_hard); + + mhd_conn_remove_from_timeout_lists (c); + +#ifndef NDEBUG + c->dbg.closing_started = true; +#endif +} + + +#endif /* MHD_SUPPORT_HTTP2 */ + + MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3) void mhd_conn_start_closing (struct MHD_Connection *restrict c, @@ -776,6 +847,19 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, log_msg ? "\"" : ""); #endif /* mhd_DEBUG_CONN_ADD_CLOSE */ +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_C_IS_HTTP2 (c)) + { + mhd_assert ((mhd_CONN_CLOSE_DAEMON_SHUTDOWN == reason) || + (mhd_CONN_CLOSE_H2_CLOSE_SOFT == reason) || + (mhd_CONN_CLOSE_H2_CLOSE_HARD == reason)); + mhd_assert (NULL == log_msg); + conn_h2_start_closing (c, + reason != mhd_CONN_CLOSE_H2_CLOSE_SOFT); + return; + } +#endif /* MHD_SUPPORT_HTTP2 */ + reply_sending_aborted = ((mhd_HTTP_STAGE_HEADERS_SENDING <= c->stage) && (mhd_HTTP_STAGE_FULL_REPLY_SENT > c->stage)); @@ -799,6 +883,11 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, sc = MHD_SC_CLIENT_SHUTDOWN_EARLY; mhd_assert (! reply_sending_aborted); break; + case mhd_CONN_CLOSE_H2_PREFACE_MISSING: + close_hard = true; + end_code = MHD_REQUEST_ENDED_HTTP_PROTOCOL_ERROR; + sc = MHD_SC_ALPN_H2_NO_PREFACE; + break; case mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY: close_hard = true; end_code = (! c->stop_with_error || c->rq.too_large) ? @@ -976,6 +1065,10 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, end_code = MHD_REQUEST_ENDED_COMPLETED_OK; break; +#ifdef MHD_SUPPORT_HTTP2 + case mhd_CONN_CLOSE_H2_CLOSE_SOFT: + case mhd_CONN_CLOSE_H2_CLOSE_HARD: +#endif /* MHD_SUPPORT_HTTP2 */ default: mhd_assert (0 && "Unreachable code"); mhd_UNREACHABLE (); @@ -994,44 +1087,10 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, } else #endif /* MHD_SUPPORT_UPGRADE */ - /* 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->sk.fd); - c->stage = mhd_HTTP_STAGE_PRE_CLOSING; - c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; - } - else + if (1) { - bool use_graceful_closing; - - mhd_assert (c->sk.state.rmt_shut_wr || \ - ! mhd_SOCKET_ERR_IS_HARD (c->sk.state.discnt_err)); - - use_graceful_closing = ! c->sk.state.rmt_shut_wr; - if (use_graceful_closing) - { -#ifdef MHD_SUPPORT_HTTPS - if (mhd_C_HAS_TLS (c)) - { - if ((0 != (((unsigned int) c->sk.ready) - & mhd_SOCKET_NET_STATE_SEND_READY)) - || c->sk.props.is_nonblck) - use_graceful_closing = - (mhd_TLS_PROCED_FAILED != mhd_tls_conn_shutdown (c->tls)); - } - else -#endif /* MHD_SUPPORT_HTTPS */ - if (1) - { - use_graceful_closing = mhd_socket_shut_wr (c->sk.fd); - if (use_graceful_closing) - use_graceful_closing = (! c->sk.state.rmt_shut_wr); /* Skip as already closed */ - } - } - if (use_graceful_closing) + if (conn_start_socket_closing (c, + close_hard)) { (void) 0; // TODO: start local lingering phase c->stage = mhd_HTTP_STAGE_PRE_CLOSING; // TODO: start local lingering phase diff --git a/src/mhd2/stream_funcs.h b/src/mhd2/stream_funcs.h @@ -211,6 +211,11 @@ enum mhd_ConnCloseReason */ mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY , + /** + * The client shut down send before complete request sent + */ + mhd_CONN_CLOSE_H2_PREFACE_MISSING + , /* Hard problem while sending */ @@ -319,6 +324,19 @@ enum mhd_ConnCloseReason */ mhd_CONN_CLOSE_HTTP_COMPLETED +#ifdef MHD_SUPPORT_HTTP2 + , + /** + * Graceful closing after finishing HTTP/2 communication. + * The HTTP/2 itself could be closed by error. + */ + mhd_CONN_CLOSE_H2_CLOSE_SOFT + , + /** + * Hard closing after finishing HTTP/2 communication. + */ + mhd_CONN_CLOSE_H2_CLOSE_HARD +#endif /* MHD_SUPPORT_HTTP2 */ }; @@ -435,6 +453,13 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); mhd_conn_start_closing ((c), mhd_CONN_CLOSE_UPGRADE, NULL) #endif /* MHD_SUPPORT_UPGRADE */ +#ifdef MHD_SUPPORT_HTTP2 +# define mhd_conn_start_closing_h2_soft(c) \ + mhd_conn_start_closing ((c), mhd_CONN_CLOSE_H2_CLOSE_SOFT, NULL) +# define mhd_conn_start_closing_h2_hard(c) \ + mhd_conn_start_closing ((c), mhd_CONN_CLOSE_H2_CLOSE_HARD, NULL) +#endif /* MHD_SUPPORT_HTTP2 */ + /** * Perform first part of the initial connection cleanup. diff --git a/src/mhd2/stream_process_reply.c b/src/mhd2/stream_process_reply.c @@ -392,18 +392,9 @@ check_connection_reply (struct MHD_Connection *restrict c) } -/** - * 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_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (1) bool -get_date_str (char *date) +mhd_build_date_str (char date[MHD_FN_PAR_FIX_ARR_SIZE_ (29)]) { static const char *const days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" @@ -497,7 +488,7 @@ get_date_header (char *header) header[3] = 'e'; header[4] = ':'; header[5] = ' '; - if (! get_date_str (header + 6)) + if (! mhd_build_date_str (header + 6)) { header[0] = 0; return false; diff --git a/src/mhd2/stream_process_reply.h b/src/mhd2/stream_process_reply.h @@ -63,7 +63,18 @@ MHD_INTERNAL void mhd_stream_call_dcc_cleanup_if_needed (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; - +/** + * 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 available space. + */ +MHD_INTERNAL bool +mhd_build_date_str (char date[MHD_FN_PAR_FIX_ARR_SIZE_ (29)]) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (1); /** * Allocate the connection's write buffer and fill it with all of the * headers from the response. diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c @@ -468,109 +468,99 @@ /** - * Detect standard HTTP request method - * - * @param connection the connection to process + * Parse HTTP method string. + * @param len the length of the @a mtd string + * @param mtd the method string, does not need to be zero-terminated + * @return enum mhd_HTTP_Method value */ -static MHD_FN_PAR_NONNULL_ALL_ void -parse_http_std_method (struct MHD_Connection *restrict connection) +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) +MHD_FN_PURE_ enum mhd_HTTP_Method +mhd_parse_http_method (size_t len, + const char mtd[MHD_FN_PAR_DYN_ARR_SIZE_ (len)]) { - const char *const restrict m = connection->rq.method.cstr; /**< short alias */ - const size_t len = connection->rq.method.len; /**< short alias */ - mhd_assert (NULL != m); - mhd_assert (0 != len); - switch (len) { case 3: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_GET) */ /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_PUT) */ mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_GET)); mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_PUT)); - if (0 == memcmp (m, + if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_GET, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_GET))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_GET; - return; - } - else if (0 == memcmp (m, + return mhd_HTTP_METHOD_GET; + else if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_PUT, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_PUT))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_PUT; - return; - } + return mhd_HTTP_METHOD_PUT; break; case 4: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_HEAD) */ /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_POST) */ mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_HEAD)); mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_POST)); - if (0 == memcmp (m, + if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_HEAD, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_HEAD))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_HEAD; - return; - } - else if (0 == memcmp (m, + return mhd_HTTP_METHOD_HEAD; + else if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_POST, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_POST))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_POST; - return; - } + return mhd_HTTP_METHOD_POST; break; case 6: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_DELETE) */ mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_DELETE)); - if (0 == memcmp (m, + if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_DELETE, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_DELETE))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_DELETE; - return; - } + return mhd_HTTP_METHOD_DELETE; break; case 7: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_CONNECT) */ /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_OPTIONS) */ mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_CONNECT)); mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_OPTIONS)); - if (0 == memcmp (m, + if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_CONNECT, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_CONNECT))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_CONNECT; - return; - } - else if (0 == memcmp (m, + return mhd_HTTP_METHOD_CONNECT; + else if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_OPTIONS, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_OPTIONS))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_OPTIONS; - return; - } + return mhd_HTTP_METHOD_OPTIONS; break; case 5: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_TRACE) */ mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_TRACE)); - if (0 == memcmp (m, + if (0 == memcmp (mtd, MHD_HTTP_METHOD_STR_TRACE, mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_TRACE))) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_TRACE; - return; - } + return mhd_HTTP_METHOD_TRACE; break; case 1: /* mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_ASTERISK) */ mhd_assert (len == mhd_SSTR_LEN (MHD_HTTP_METHOD_STR_ASTERISK)); - if ('*' == m[0]) - { - connection->rq.http_mthd = mhd_HTTP_METHOD_ASTERISK; - return; - } + if ('*' == mtd[0]) + return mhd_HTTP_METHOD_ASTERISK; break; default: break; /* Handled after the "switch()" body */ } - connection->rq.http_mthd = mhd_HTTP_METHOD_OTHER; + return mhd_HTTP_METHOD_OTHER; +} + + +/** + * Detect standard HTTP request method + * + * @param connection the connection to process + */ +static MHD_FN_PAR_NONNULL_ALL_ void +parse_http_std_method (struct MHD_Connection *restrict connection) +{ + const char *const restrict m = connection->rq.method.cstr; /**< short alias */ + const size_t len = connection->rq.method.len; /**< short alias */ + mhd_assert (NULL != m); + mhd_assert (0 != len); + + connection->rq.http_mthd = mhd_parse_http_method (len, + m); } diff --git a/src/mhd2/stream_process_request.h b/src/mhd2/stream_process_request.h @@ -56,6 +56,7 @@ #include "sys_base_types.h" #include "mhd_str_types.h" +#include "http_method.h" struct MHD_Connection; /* forward declaration */ struct MHD_UploadAction; /* forward declaration */ @@ -75,6 +76,17 @@ typedef bool const struct MHD_StringNullable *restrict value); /** + * Parse HTTP method string. + * @param len the length of the @a mtd string + * @param m the method string, does not need to be zero-terminated + * @return enum mhd_HTTP_Method value + */ +MHD_INTERNAL enum mhd_HTTP_Method +mhd_parse_http_method (size_t len, + const char mtd[MHD_FN_PAR_DYN_ARR_SIZE_ (len)]) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_(2,1) MHD_FN_PURE_; + +/** * Parse and unescape the arguments given by the client * as part of the HTTP request URI. * diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c @@ -62,6 +62,11 @@ #include "mhd_connection.h" #include "mhd_response.h" +#include "mhd_comm_layer_state.h" +#ifdef MHD_SUPPORT_HTTP2 +# include "h2/h2_comm.h" +#endif + #include "stream_process_states.h" #include "stream_funcs.h" #include "stream_process_request.h" @@ -87,6 +92,14 @@ mhd_conn_event_loop_state_update (struct MHD_Connection *restrict c) (mhd_CONN_STATE_TCP_CONNECTED == c->conn_state)); #endif /* MHD_SUPPORT_HTTPS */ +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_C_IS_HTTP2 (c)) + { + mhd_h2_conn_state_update (c); + return; + } +#endif /* MHD_SUPPORT_HTTP2 */ + switch (c->stage) { case mhd_HTTP_STAGE_INIT: @@ -202,6 +215,47 @@ mhd_conn_event_loop_state_update (struct MHD_Connection *restrict c) /** + * Process HTTP communication layer states and events + * @param c the connection to process + * @return #mhd_COMM_LAYER_PROCESSING if setting up HTTP connection is + * in progress, + * #mhd_COMM_LAYER_OK if HTTP communication can be performed now, + * #mhd_COMM_LAYER_BROKEN if connection is broken and should be closed. + */ +mhd_static_inline enum mhd_CommLayerState +process_http_comm_layer (struct MHD_Connection *restrict c) +{ +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_HTTP_LAYER_CONNECTED == c->h_layer.state) + return mhd_COMM_LAYER_OK; /* Shortcut for the most common case */ + + switch (c->h_layer.state) + { + case mhd_HTTP_LAYER_PREFACE: + return mhd_h2_process_preface (c); + case mhd_HTTP_LAYER_CONNECTED: + mhd_UNREACHABLE (); /* Handled above */ + return mhd_COMM_LAYER_OK; + case mhd_HTTP_LAYER_CLOSING: + case mhd_HTTP_LAYER_CLOSED: + return mhd_COMM_LAYER_OK; + case mhd_HTTP_LAYER_BROKEN: + return mhd_COMM_LAYER_BROKEN; + default: + break; + } + + mhd_UNREACHABLE (); + return mhd_COMM_LAYER_BROKEN; + +#else /* ! MHD_SUPPORT_HTTP2 */ + + return mhd_COMM_LAYER_OK; +#endif /* ! MHD_SUPPORT_HTTP2 */ +} + + +/** * Finalise resuming of the connection * @param c the connection to resume */ @@ -245,13 +299,16 @@ update_active_state (struct MHD_Connection *restrict c) mhd_conn_event_loop_state_update (c); - if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info)) + if (! mhd_C_IS_HTTP2 (c)) { - /* Check whether the space is available to receive data */ - if (! mhd_stream_check_and_grow_read_buffer_space (c)) + if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info)) { - mhd_assert (c->discard_request); - return false; + /* Check whether the space is available to receive data */ + if (! mhd_stream_check_and_grow_read_buffer_space (c)) + { + mhd_assert (c->discard_request); + return false; + } } } @@ -274,6 +331,33 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) struct MHD_Daemon *const d = c->daemon; bool daemon_closing; + switch (process_http_comm_layer (c)) + { + case mhd_COMM_LAYER_OK: + break; /* Process HTTP data */ + case mhd_COMM_LAYER_PROCESSING: + return true; /* Too early for HTTP */ + case mhd_COMM_LAYER_BROKEN: + mhd_assert (c->dbg.closing_started); + return false; /* Connection is broken */ + default: + mhd_UNREACHABLE (); + return false; + } + +#ifdef MHD_SUPPORT_HTTP2 + if (mhd_C_IS_HTTP2 (c)) + { + if (! mhd_h2_conn_process_data (c)) + return false; + update_active_state (c); + return true; + } +#endif /* MHD_SUPPORT_HTTP2 */ + + mhd_assert (mhd_D_IS_HTTP1_ENABLED (d) || (! mhd_C_IS_HTTP2 (c)) || \ + c->stop_with_error); + if (c->suspended) return true; diff --git a/src/mhd2/tls_gnu_daemon_data.h b/src/mhd2/tls_gnu_daemon_data.h @@ -47,6 +47,8 @@ #include "mhd_sys_options.h" +#include "sys_bool_type.h" + #ifndef MHD_SUPPORT_GNUTLS #error This header can be used only if GnuTLS is enabled #endif @@ -74,6 +76,18 @@ struct mhd_TlsGnuDaemonData * TLS priorities cache */ gnutls_priority_t pri_cache; + +#ifdef mhd_TLS_GNU_HAS_ALPN + /** + * Enabled protocols for ALPN + */ + gnutls_datum_t alpn_prots[3]; + + /** + * Number of elements set in the @a alpn_prots + */ + unsigned int num_alpn_prots; +#endif /* mhd_TLS_GNU_HAS_ALPN */ }; #endif /* ! MHD_TLS_GNU_DAEMON_DATA_H */ diff --git a/src/mhd2/tls_gnu_funcs.c b/src/mhd2/tls_gnu_funcs.c @@ -95,6 +95,32 @@ mhd_tls_gnu_debug_print (int level, const char *msg) #endif /* mhd_USE_TLS_DEBUG_MESSAGES */ +#ifdef mhd_TLS_GNU_HAS_ALPN +static const char mhd_alpn_str_http1_0[] = "http/1.0"; /* Registered value for HTTP/1.0 */ +static const char mhd_alpn_str_http1_1[] = "http/1.1"; /* Registered value for HTTP/1.1 */ +# ifdef MHD_SUPPORT_HTTP2 +static const char mhd_alpn_str_http2[] = "h2"; /* Registered value for HTTP/2 over TLS */ +# endif +# if 0 /* Disabled code */ +static const char alpn_http_3[] = "h3"; /* Registered value for HTTP/3 */ +# endif +static const gnutls_datum_t mhd_alpn_dat_http1_0 = { + (unsigned char *) mhd_DROP_CONST (mhd_alpn_str_http1_0), + mhd_SSTR_LEN (mhd_alpn_str_http1_0) +}; +static const gnutls_datum_t mhd_alpn_dat_http1_1 = { + (unsigned char *) mhd_DROP_CONST (mhd_alpn_str_http1_1), + mhd_SSTR_LEN (mhd_alpn_str_http1_1) +}; +# ifdef MHD_SUPPORT_HTTP2 +static const gnutls_datum_t mhd_alpn_dat_http2 = { + (unsigned char *) mhd_DROP_CONST (mhd_alpn_str_http2), + mhd_SSTR_LEN (mhd_alpn_str_http2) +}; +# endif +#endif /* mhd_TLS_GNU_HAS_ALPN */ + + /* ** Global initialisation / de-initialisation ** */ static bool gnutls_lib_inited = false; @@ -439,6 +465,25 @@ mhd_tls_gnu_daemon_init3 (struct MHD_Daemon *restrict d, if (NULL == d_tls) return MHD_SC_DAEMON_MEM_ALLOC_FAILURE; +#ifdef mhd_TLS_GNU_HAS_ALPN + // TODO: use daemon option to disable ALPN + // TODO: use daemon option to select protocols for ALPN + d_tls->num_alpn_prots = 0; + +#ifdef MHD_SUPPORT_HTTP2 + if (1 /* enabled HTTP/2 ? */) + d_tls->alpn_prots[d_tls->num_alpn_prots++] = mhd_alpn_dat_http2; +#endif /* MHD_SUPPORT_HTTP2 */ + + if (1 /* enabled HTTP/1.x ? */) + { + d_tls->alpn_prots[d_tls->num_alpn_prots++] = mhd_alpn_dat_http1_1; + d_tls->alpn_prots[d_tls->num_alpn_prots++] = mhd_alpn_dat_http1_0; + } + + mhd_assert (mhd_ARR_NUM_ELEMS (d_tls->alpn_prots) >= d_tls->num_alpn_prots); +#endif /* mhd_TLS_GNU_HAS_ALPN */ + res = daemon_init_credentials (d, d_tls, s); @@ -544,23 +589,10 @@ mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls, /* The basic TLS session properties has been set. The rest is optional settings. */ #ifdef mhd_TLS_GNU_HAS_ALPN - if (1) + if (0 != d_tls->num_alpn_prots) { - static const char alpn_http_1_0[] = "http/1.0"; /* Registered value for HTTP/1.0 */ - static const char alpn_http_1_1[] = "http/1.1"; /* Registered value for HTTP/1.1 */ -# if 0 /* Disabled code */ - static const char alpn_http_2[] = "h2"; /* Registered value for HTTP/2 over TLS */ - static const char alpn_http_3[] = "h3"; /* Registered value for HTTP/3 */ -# endif - gnutls_datum_t prots[] = { - { (unsigned char *) mhd_DROP_CONST (alpn_http_1_1), - mhd_SSTR_LEN (alpn_http_1_1) } - , - { (unsigned char *) mhd_DROP_CONST (alpn_http_1_0), - mhd_SSTR_LEN (alpn_http_1_0) } - }; - unsigned int alpn_flags; int alpn_res; + unsigned int alpn_flags; alpn_flags = 0; # if 0 @@ -569,8 +601,8 @@ mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls, alpn_res = gnutls_alpn_set_protocols (c_tls->sess, - prots, - (unsigned int) mhd_ARR_NUM_ELEMS (prots), + d_tls->alpn_prots, + d_tls->num_alpn_prots, alpn_flags); (void) alpn_res; /* Ignore any possible ALPN set errors */ } @@ -850,3 +882,39 @@ mhd_tls_gnu_conn_get_tls_ver (struct mhd_TlsGnuConnData *restrict c_tls, return true; } + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_TlsAlpnProt +mhd_tls_gnu_conn_get_alpn_prot (struct mhd_TlsGnuConnData *restrict c_tls) +{ +#ifdef mhd_TLS_GNU_HAS_ALPN + gnutls_datum_t sel_prot; + + if (GNUTLS_E_SUCCESS != + gnutls_alpn_get_selected_protocol (c_tls->sess, &sel_prot)) + return mhd_TLS_ALPN_PROT_NOT_SELECTED; + +# ifdef MHD_SUPPORT_HTTP2 + if ((sel_prot.size == mhd_alpn_dat_http2.size) + && (0 == memcmp (sel_prot.data, + mhd_alpn_dat_http2.data, + sel_prot.size))) + return mhd_TLS_ALPN_PROT_HTTP2; +# endif /* MHD_SUPPORT_HTTP2 */ + + if ((sel_prot.size == mhd_alpn_dat_http1_1.size) + && (0 == memcmp (sel_prot.data, + mhd_alpn_dat_http1_1.data, + sel_prot.size))) + return mhd_TLS_ALPN_PROT_HTTP1_1; + + if ((sel_prot.size == mhd_alpn_dat_http1_0.size) + && (0 == memcmp (sel_prot.data, + mhd_alpn_dat_http1_0.data, + sel_prot.size))) + return mhd_TLS_ALPN_PROT_HTTP1_0; + +#endif /* mhd_TLS_GNU_HAS_ALPN */ + + return mhd_TLS_ALPN_PROT_NOT_SELECTED; +} diff --git a/src/mhd2/tls_gnu_funcs.h b/src/mhd2/tls_gnu_funcs.h @@ -297,4 +297,14 @@ mhd_tls_gnu_conn_get_tls_ver (struct mhd_TlsGnuConnData *restrict c_tls, struct mhd_StctTlsVersion *restrict tls_ver_out) MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2); +/** + * Get a protocol selected by ALPN + * @param c_tls the connection TLS handle + * @return the selected protocol code + */ +MHD_INTERNAL enum mhd_TlsAlpnProt +mhd_tls_gnu_conn_get_alpn_prot (struct mhd_TlsGnuConnData *restrict c_tls) +MHD_FN_PAR_NONNULL_ALL_; + + #endif /* ! MHD_TLS_GNU_FUNCS_H */ diff --git a/src/mhd2/tls_multi_funcs.c b/src/mhd2/tls_multi_funcs.c @@ -709,3 +709,31 @@ mhd_tls_multi_conn_get_tls_ver (struct mhd_TlsMultiConnData *restrict c_tls, } return false; } + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_TlsAlpnProt +mhd_tls_multi_conn_get_alpn_prot (struct mhd_TlsMultiConnData *restrict c_tls) +{ + switch (c_tls->choice) + { +#ifdef MHD_SUPPORT_GNUTLS + case mhd_TLS_MULTI_ROUTE_GNU: + return mhd_tls_gnu_conn_get_alpn_prot (c_tls->data.gnutls); +#endif +#ifdef MHD_SUPPORT_OPENSSL + case mhd_TLS_MULTI_ROUTE_OPEN: + return mhd_tls_open_conn_get_alpn_prot (c_tls->data.openssl); +#endif +#ifndef MHD_SUPPORT_GNUTLS + case MHD_TLS_BACKEND_GNUTLS: +#endif /* ! MHD_SUPPORT_GNUTLS */ +#ifndef MHD_SUPPORT_OPENSSL + case MHD_TLS_BACKEND_OPENSSL: +#endif /* ! MHD_SUPPORT_OPENSSL */ + case mhd_TLS_MULTI_ROUTE_NONE: + default: + mhd_UNREACHABLE (); + break; + } + return mhd_TLS_ALPN_PROT_NOT_SELECTED; +} diff --git a/src/mhd2/tls_multi_funcs.h b/src/mhd2/tls_multi_funcs.h @@ -276,4 +276,13 @@ mhd_tls_multi_conn_get_tls_ver (struct mhd_TlsMultiConnData *restrict c_tls, struct mhd_StctTlsVersion *restrict tls_ver_out) MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2); +/** + * Get a protocol selected by ALPN + * @param c_tls the connection TLS handle + * @return the selected protocol code + */ +MHD_INTERNAL enum mhd_TlsAlpnProt +mhd_tls_multi_conn_get_alpn_prot (struct mhd_TlsMultiConnData *restrict c_tls) +MHD_FN_PAR_NONNULL_ALL_; + #endif /* ! MHD_TLS_MULTI_FUNCS_H */ diff --git a/src/mhd2/tls_open_daemon_data.h b/src/mhd2/tls_open_daemon_data.h @@ -69,6 +69,16 @@ struct mhd_TlsOpenDaemonData * The server context */ SSL_CTX *ctx; + + /** + * Enabled protocols for ALPN + */ + const unsigned char *alpn_prots; + + /** + * The size of the data pointed by @a alpn_prots + */ + unsigned int alpn_prots_size; }; #endif /* ! MHD_TLS_OPEN_DAEMON_DATA_H */ diff --git a/src/mhd2/tls_open_funcs.c b/src/mhd2/tls_open_funcs.c @@ -364,16 +364,41 @@ daemon_deinit_lib_ctx (struct mhd_TlsOpenDaemonData *restrict d_tls) } -static const unsigned char alpn_codes_list[] = { +#define mhd_ALPN_CODE_HTTP1_0 \ + 'h', 't', 't', 'p', '/', '1', '.', '0' /* Registered value for HTTP/1.0 */ +#define mhd_ALPN_CODE_HTTP1_1 \ + 'h', 't', 't', 'p', '/', '1', '.', '1' /* Registered value for HTTP/1.1 */ +#ifdef MHD_SUPPORT_HTTP2 +#define mhd_ALPN_CODE_HTTP2 \ + 'h', '2' /* Registered value for HTTP/2 over TLS */ +#endif /* MHD_SUPPORT_HTTP2 */ #if 0 /* Disabled code */ - 2u, 'h', '3' /* Registered value for HTTP/3 */ +#define mhd_ALPN_CODE_HTTP3 \ + 'h', '3' /* Registered value for HTTP/3 */ +#endif /* Disabled code */ + +#ifdef MHD_SUPPORT_HTTP2 +static const char alpn_code_http2[] = { mhd_ALPN_CODE_HTTP2 }; +#endif /* MHD_SUPPORT_HTTP2 */ +static const char alpn_code_http1_1[] = { mhd_ALPN_CODE_HTTP1_1 }; +static const char alpn_code_http1_0[] = { mhd_ALPN_CODE_HTTP1_0 }; + +#ifdef MHD_SUPPORT_HTTP2 +static const unsigned char alpn_list_http2_1x[] = { + sizeof(alpn_code_http2), mhd_ALPN_CODE_HTTP2 , - 2u, 'h', '2' /* Registered value for HTTP/2 over TLS */ + sizeof(alpn_code_http1_1), mhd_ALPN_CODE_HTTP1_1 , -#endif /* Disabled code */ - 8u, 'h', 't', 't', 'p', '/', '1', '.', '1' /* Registered value for HTTP/1.1 */ + sizeof(alpn_code_http1_0), mhd_ALPN_CODE_HTTP1_0 +}; +static const unsigned char alpn_list_http2_only[] = { + sizeof(alpn_code_http2), mhd_ALPN_CODE_HTTP2 +}; +#endif /* MHD_SUPPORT_HTTP2 */ +static const unsigned char alpn_list_http1x_only[] = { + sizeof(alpn_code_http1_1), mhd_ALPN_CODE_HTTP1_1 , - 8u, 'h', 't', 't', 'p', '/', '1', '.', '0' /* Registered value for HTTP/1.0 */ + sizeof(alpn_code_http1_0), mhd_ALPN_CODE_HTTP1_0 }; #ifndef OPENSSL_NO_NEXTPROTONEG @@ -391,9 +416,11 @@ get_npn_list (SSL *sess, unsigned int *outlen, void *cls) { - (void) sess; (void) cls; /* Unused */ - *out = alpn_codes_list; - *outlen = sizeof(alpn_codes_list); + struct mhd_TlsOpenDaemonData *const d_tls = + (struct mhd_TlsOpenDaemonData *) cls; + (void) sess; /* Unused */ + *out = d_tls->alpn_prots; + *outlen = d_tls->alpn_prots_size; return SSL_TLSEXT_ERR_OK; } @@ -419,14 +446,16 @@ select_alpn_prot (SSL *sess, unsigned int inlen, void *cls) { - (void) sess; (void) cls; /* Unused */ + struct mhd_TlsOpenDaemonData *const d_tls = + (struct mhd_TlsOpenDaemonData *) cls; + (void) sess; /* Unused */ if (OPENSSL_NPN_NEGOTIATED == SSL_select_next_proto ((unsigned char **) mhd_DROP_CONST (out), outlen, in, inlen, - alpn_codes_list, - sizeof(alpn_codes_list))) + d_tls->alpn_prots, + d_tls->alpn_prots_size)) return SSL_TLSEXT_ERR_OK; /* Success */ return SSL_TLSEXT_ERR_ALERT_FATAL; /* Failure */ @@ -514,14 +543,37 @@ daemon_init_ctx (struct MHD_Daemon *restrict d, ! 0); /* ALPN and NPN */ - // TODO: use daemon option to disable them + // TODO: use daemon option to disable ALPN + // TODO: use daemon option to select protocols for ALPN +#ifdef MHD_SUPPORT_HTTP2 + if (1 /* enabled both HTTP/2 and HTTP/1.x */) + { + d_tls->alpn_prots = alpn_list_http2_1x; + d_tls->alpn_prots_size = sizeof(alpn_list_http2_1x); + } + else if (0 /* HTTP/2 only */) + { + d_tls->alpn_prots = alpn_list_http2_only; + d_tls->alpn_prots_size = sizeof(alpn_list_http2_only); + } + else +#endif /* MHD_SUPPORT_HTTP2 */ + if (1 /* HTTP/1.x only */) + { + d_tls->alpn_prots = alpn_list_http1x_only; + d_tls->alpn_prots_size = sizeof(alpn_list_http1x_only); + } + else + { + mhd_UNREACHABLE (); + } SSL_CTX_set_alpn_select_cb (d_tls->ctx, &select_alpn_prot, - NULL); + d_tls); #ifndef OPENSSL_NO_NEXTPROTONEG SSL_CTX_set_next_protos_advertised_cb (d_tls->ctx, &get_npn_list, - NULL); + d_tls); #endif /* ! OPENSSL_NO_NEXTPROTONEG */ return MHD_SC_OK; @@ -1270,3 +1322,50 @@ mhd_tls_open_conn_get_tls_ver (struct mhd_TlsOpenConnData *restrict c_tls, return true; } + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_TlsAlpnProt +mhd_tls_open_conn_get_alpn_prot (struct mhd_TlsOpenConnData *restrict c_tls) +{ + const unsigned char *sel_prot; + unsigned int sel_prot_len; + + SSL_get0_alpn_selected (c_tls->sess, + &sel_prot, + &sel_prot_len); + +#ifndef OPENSSL_NO_NEXTPROTONEG + if ((NULL == sel_prot) || + (0 == sel_prot_len)) + { + SSL_get0_next_proto_negotiated (c_tls->sess, + &sel_prot, + &sel_prot_len); + } +#endif /* ! OPENSSL_NO_NEXTPROTONEG */ + + if (NULL == sel_prot) + return mhd_TLS_ALPN_PROT_NOT_SELECTED; + +#ifdef MHD_SUPPORT_HTTP2 + if ((sel_prot_len == sizeof(alpn_code_http2)) + && (0 == memcmp (sel_prot, + alpn_code_http2, + sel_prot_len))) + return mhd_TLS_ALPN_PROT_HTTP2; +#endif /* MHD_SUPPORT_HTTP2 */ + + if ((sel_prot_len == sizeof(alpn_code_http1_1)) + && (0 == memcmp (sel_prot, + alpn_code_http1_1, + sel_prot_len))) + return mhd_TLS_ALPN_PROT_HTTP1_1; + + if ((sel_prot_len == sizeof(alpn_code_http1_0)) + && (0 == memcmp (sel_prot, + alpn_code_http1_0, + sel_prot_len))) + return mhd_TLS_ALPN_PROT_HTTP1_0; + + return mhd_TLS_ALPN_PROT_NOT_SELECTED; +} diff --git a/src/mhd2/tls_open_funcs.h b/src/mhd2/tls_open_funcs.h @@ -280,4 +280,13 @@ mhd_tls_open_conn_get_tls_ver (struct mhd_TlsOpenConnData *restrict c_tls, struct mhd_StctTlsVersion *restrict tls_ver_out) MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (2); +/** + * Get a protocol selected by ALPN + * @param c_tls the connection TLS handle + * @return the selected protocol code + */ +MHD_INTERNAL enum mhd_TlsAlpnProt +mhd_tls_open_conn_get_alpn_prot (struct mhd_TlsOpenConnData *restrict c_tls) +MHD_FN_PAR_NONNULL_ALL_; + #endif /* ! MHD_TLS_OPEN_FUNCS_H */