|
| 1 | +/* |
| 2 | + This file is part of libhttpserver |
| 3 | + Copyright (C) 2011-2026 Sebastiano Merlino |
| 4 | +
|
| 5 | + This library is free software; you can redistribute it and/or |
| 6 | + modify it under the terms of the GNU Lesser General Public |
| 7 | + License as published by the Free Software Foundation; either |
| 8 | + version 2.1 of the License, or (at your option) any later version. |
| 9 | +
|
| 10 | + This library is distributed in the hope that it will be useful, |
| 11 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | + Lesser General Public License for more details. |
| 14 | +
|
| 15 | + You should have received a copy of the GNU Lesser General Public |
| 16 | + License along with this library; if not, write to the Free Software |
| 17 | + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 |
| 18 | + USA |
| 19 | +*/ |
| 20 | + |
| 21 | +// TASK-014: webserver PIMPL backing class. |
| 22 | +// |
| 23 | +// This header is *internal*. It is reachable only when compiling the |
| 24 | +// libhttpserver translation units themselves (HTTPSERVER_COMPILATION |
| 25 | +// is supplied through src/Makefile.am AM_CPPFLAGS). It is NOT included |
| 26 | +// from the public umbrella <httpserver.hpp>, so the gate is the strict |
| 27 | +// one-mode form, not the dual-mode form used by other detail headers. |
| 28 | +#if !defined(HTTPSERVER_COMPILATION) |
| 29 | +#error "webserver_impl.hpp is internal; only reachable when compiling libhttpserver." |
| 30 | +#endif |
| 31 | + |
| 32 | +#ifndef SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_ |
| 33 | +#define SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_ |
| 34 | + |
| 35 | +#include <microhttpd.h> |
| 36 | +#include <pthread.h> |
| 37 | +#include <stdarg.h> |
| 38 | + |
| 39 | +#include <list> |
| 40 | +#include <map> |
| 41 | +#include <memory> |
| 42 | +#include <mutex> |
| 43 | +#include <set> |
| 44 | +#include <shared_mutex> |
| 45 | +#include <string> |
| 46 | +#include <unordered_map> |
| 47 | +#include <utility> |
| 48 | + |
| 49 | +#ifdef HAVE_GNUTLS |
| 50 | +#include <gnutls/gnutls.h> |
| 51 | +#endif // HAVE_GNUTLS |
| 52 | + |
| 53 | +#include "httpserver/http_utils.hpp" |
| 54 | +#include "httpserver/detail/http_endpoint.hpp" |
| 55 | + |
| 56 | +#if MHD_VERSION < 0x00097002 |
| 57 | +typedef int MHD_Result; |
| 58 | +#endif |
| 59 | + |
| 60 | +namespace httpserver { |
| 61 | + |
| 62 | +class webserver; |
| 63 | +class http_resource; |
| 64 | +class http_response; |
| 65 | +#ifdef HAVE_WEBSOCKET |
| 66 | +class websocket_handler; |
| 67 | +#endif // HAVE_WEBSOCKET |
| 68 | + |
| 69 | +namespace detail { |
| 70 | + |
| 71 | +struct modded_request; |
| 72 | + |
| 73 | +// connection_state: per-MHD_Connection arena anchor. |
| 74 | +// |
| 75 | +// Defined as a near-empty type so downstream tasks (TASK-016) can add |
| 76 | +// members (e.g. std::pmr::monotonic_buffer_resource arena_) without |
| 77 | +// retouching the public header. Copy/move are deleted now so adding |
| 78 | +// non-copyable/non-movable members later does not change the trait. |
| 79 | +struct connection_state { |
| 80 | + connection_state() = default; |
| 81 | + connection_state(const connection_state&) = delete; |
| 82 | + connection_state& operator=(const connection_state&) = delete; |
| 83 | + connection_state(connection_state&&) = delete; |
| 84 | + connection_state& operator=(connection_state&&) = delete; |
| 85 | +}; |
| 86 | + |
| 87 | +// webserver_impl: backing object holding all backend-coupled state of |
| 88 | +// `webserver` (MHD daemon, mutexes, ban/allowance sets, route table, |
| 89 | +// route cache, websocket registry, optional GnuTLS SNI cache) plus the |
| 90 | +// dispatch helpers and MHD trampolines that operate on those. |
| 91 | +// |
| 92 | +// Members are deliberately public: webserver and the free-function MHD |
| 93 | +// callbacks all need direct access. The boundary that matters is between |
| 94 | +// the public header and this internal class -- not between webserver and |
| 95 | +// its own impl. |
| 96 | +class webserver_impl { |
| 97 | + public: |
| 98 | + explicit webserver_impl(webserver* parent); |
| 99 | + ~webserver_impl(); |
| 100 | + webserver_impl(const webserver_impl&) = delete; |
| 101 | + webserver_impl& operator=(const webserver_impl&) = delete; |
| 102 | + webserver_impl(webserver_impl&&) = delete; |
| 103 | + webserver_impl& operator=(webserver_impl&&) = delete; |
| 104 | + |
| 105 | + // Back-pointer used by the dispatch helpers to read the const config |
| 106 | + // bag still living on `webserver` (port, max_threads, certs, etc.). |
| 107 | + // Set in the constructor to the owning webserver. |
| 108 | + webserver* parent = nullptr; |
| 109 | + |
| 110 | + struct MHD_Daemon* daemon = nullptr; |
| 111 | + MHD_socket bind_socket = 0; |
| 112 | + |
| 113 | + pthread_mutex_t mutexwait; |
| 114 | + pthread_cond_t mutexcond; |
| 115 | + |
| 116 | + bool running = false; |
| 117 | + |
| 118 | + std::shared_mutex registered_resources_mutex; |
| 119 | + std::map<detail::http_endpoint, ::httpserver::http_resource*> registered_resources; |
| 120 | + std::map<std::string, ::httpserver::http_resource*> registered_resources_str; |
| 121 | + std::map<detail::http_endpoint, ::httpserver::http_resource*> registered_resources_regex; |
| 122 | + |
| 123 | + struct route_cache_entry { |
| 124 | + detail::http_endpoint matched_endpoint; |
| 125 | + ::httpserver::http_resource* resource; |
| 126 | + }; |
| 127 | + static constexpr size_t ROUTE_CACHE_MAX_SIZE = 256; |
| 128 | + std::mutex route_cache_mutex; |
| 129 | + std::list<std::pair<std::string, route_cache_entry>> route_cache_list; |
| 130 | + std::unordered_map<std::string, |
| 131 | + std::list<std::pair<std::string, route_cache_entry>>::iterator> |
| 132 | + route_cache_map; |
| 133 | + |
| 134 | + std::shared_mutex bans_mutex; |
| 135 | + std::set<http::ip_representation> bans; |
| 136 | + |
| 137 | + std::shared_mutex allowances_mutex; |
| 138 | + std::set<http::ip_representation> allowances; |
| 139 | + |
| 140 | +#ifdef HAVE_WEBSOCKET |
| 141 | + std::map<std::string, ::httpserver::websocket_handler*> registered_ws_handlers; |
| 142 | + |
| 143 | + struct ws_upgrade_data { |
| 144 | + webserver_impl* impl; |
| 145 | + ::httpserver::websocket_handler* handler; |
| 146 | + }; |
| 147 | + |
| 148 | + static void upgrade_handler(void *cls, struct MHD_Connection* connection, |
| 149 | + void *req_cls, const char *extra_in, |
| 150 | + size_t extra_in_size, MHD_socket sock, |
| 151 | + struct MHD_UpgradeResponseHandle *urh); |
| 152 | +#endif // HAVE_WEBSOCKET |
| 153 | + |
| 154 | +#if defined(HAVE_GNUTLS) && defined(MHD_OPTION_HTTPS_CERT_CALLBACK) |
| 155 | + mutable std::map<std::string, gnutls_certificate_credentials_t> |
| 156 | + sni_credentials_cache; |
| 157 | + mutable std::shared_mutex sni_credentials_mutex; |
| 158 | +#endif // HAVE_GNUTLS && MHD_OPTION_HTTPS_CERT_CALLBACK |
| 159 | + |
| 160 | + // Dispatch helpers (formerly methods on webserver). Each of these |
| 161 | + // touches both backend state on this impl and const config on the |
| 162 | + // owning webserver (via `parent`). |
| 163 | + std::shared_ptr<::httpserver::http_response> not_found_page(modded_request* mr) const; |
| 164 | + std::shared_ptr<::httpserver::http_response> method_not_allowed_page(modded_request* mr) const; |
| 165 | + std::shared_ptr<::httpserver::http_response> internal_error_page(modded_request* mr, |
| 166 | + bool force_our = false) const; |
| 167 | + bool should_skip_auth(const std::string& path) const; |
| 168 | + void invalidate_route_cache(); |
| 169 | + |
| 170 | + MHD_Result requests_answer_first_step(MHD_Connection* connection, modded_request* mr); |
| 171 | + MHD_Result requests_answer_second_step(MHD_Connection* connection, |
| 172 | + const char* method, const char* version, const char* upload_data, |
| 173 | + size_t* upload_data_size, modded_request* mr); |
| 174 | + MHD_Result finalize_answer(MHD_Connection* connection, modded_request* mr, |
| 175 | + const char* method); |
| 176 | + MHD_Result complete_request(MHD_Connection* connection, modded_request* mr, |
| 177 | + const char* version, const char* method); |
| 178 | + struct MHD_Response* get_raw_response_with_fallback(modded_request* mr); |
| 179 | + |
| 180 | + static struct MHD_Response* materialize_response(::httpserver::http_response* resp); |
| 181 | + static void decorate_mhd_response(struct MHD_Response* response, |
| 182 | + const ::httpserver::http_response& resp); |
| 183 | + |
| 184 | + // MHD trampolines registered with libmicrohttpd. Closure pointer is |
| 185 | + // `this` (webserver_impl*) for answer_to_connection, otherwise the |
| 186 | + // owning `webserver*` (so callbacks can read the const config bag). |
| 187 | + static void request_completed(void* cls, struct MHD_Connection* connection, |
| 188 | + void** con_cls, enum MHD_RequestTerminationCode toe); |
| 189 | + static MHD_Result answer_to_connection(void* cls, MHD_Connection* connection, |
| 190 | + const char* url, const char* method, const char* version, |
| 191 | + const char* upload_data, size_t* upload_data_size, void** con_cls); |
| 192 | + static MHD_Result post_iterator(void* cls, enum MHD_ValueKind kind, |
| 193 | + const char* key, const char* filename, const char* content_type, |
| 194 | + const char* transfer_encoding, const char* data, uint64_t off, |
| 195 | + size_t size); |
| 196 | + |
| 197 | + // Auxiliary MHD callbacks (formerly free functions in webserver.cpp). |
| 198 | + // Each takes `cls = webserver*` so it can read the const config bag. |
| 199 | + static MHD_Result policy_callback(void* cls, const struct sockaddr* addr, |
| 200 | + socklen_t addrlen); |
| 201 | + static void error_log(void* cls, const char* fmt, va_list ap); |
| 202 | + static void* uri_log(void* cls, const char* uri, |
| 203 | + struct MHD_Connection* con); |
| 204 | + static void access_log(::httpserver::webserver* cls, const std::string& uri); |
| 205 | + static size_t unescaper_func(void* cls, struct MHD_Connection* c, char* s); |
| 206 | + |
| 207 | +#ifdef HAVE_GNUTLS |
| 208 | + static int psk_cred_handler_func(void* cls, struct MHD_Connection* connection, |
| 209 | + const char* username, void** psk, |
| 210 | + size_t* psk_size); |
| 211 | +#ifdef MHD_OPTION_HTTPS_CERT_CALLBACK |
| 212 | + static int sni_cert_callback_func(void* cls, struct MHD_Connection* connection, |
| 213 | + const char* server_name, |
| 214 | + gnutls_certificate_credentials_t* creds); |
| 215 | +#endif // MHD_OPTION_HTTPS_CERT_CALLBACK |
| 216 | +#endif // HAVE_GNUTLS |
| 217 | +}; |
| 218 | + |
| 219 | +} // namespace detail |
| 220 | +} // namespace httpserver |
| 221 | + |
| 222 | +#endif // SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_ |
0 commit comments