-
-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathhttp_request_impl.hpp
More file actions
394 lines (357 loc) · 18.3 KB
/
Copy pathhttp_request_impl.hpp
File metadata and controls
394 lines (357 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/
// http_request PIMPL backing class.
//
// This header is *internal*. It is reachable only when compiling the
// libhttpserver translation units themselves (HTTPSERVER_COMPILATION
// is supplied through src/Makefile.am AM_CPPFLAGS). It is NOT included
// from the public umbrella <httpserver.hpp>, so the gate is the strict
// one-mode form, mirroring detail/webserver_impl.hpp.
//
// Members below are accessed from src/http_request.cpp; cppcheck analyses
// each TU in isolation and cannot see the uses, so the unusedStructMember
// check must be suppressed at the file level.
// cppcheck-suppress-file unusedStructMember
#if !defined(HTTPSERVER_COMPILATION)
#error "http_request_impl.hpp is internal; only reachable when compiling libhttpserver."
#endif
#ifndef SRC_HTTPSERVER_DETAIL_HTTP_REQUEST_IMPL_HPP_
#define SRC_HTTPSERVER_DETAIL_HTTP_REQUEST_IMPL_HPP_
#include <microhttpd.h>
#ifdef HAVE_GNUTLS
#include <gnutls/gnutls.h>
#endif // HAVE_GNUTLS
#include <stddef.h>
#include <cstdint>
#include <ctime>
#include <map>
#include <memory_resource>
#include <string>
#include <string_view>
#include <vector>
#include "httpserver/cookie.hpp"
#include "httpserver/create_webserver.hpp"
#include "httpserver/file_info.hpp"
#include "httpserver/http_arg_value.hpp"
#include "httpserver/http_utils.hpp"
#if MHD_VERSION < 0x00097002
typedef int MHD_Result;
#endif
namespace httpserver::detail {
// http_request_impl: backing object for http_request. Holds every field
// that depends on libmicrohttpd or GnuTLS (the live MHD_Connection*, the
// per-request unescaper, the file table, the lazy parsed-args / cookies
// / cert caches), plus the helpers and MHD trampolines that operate on
// those fields.
//
// Members are deliberately public: http_request just forwards every
// public-API call into a one-line `impl_->...` dispatch. The boundary
// that matters is between the public <httpserver/http_request.hpp>
// header and this internal header -- not between http_request and its
// own impl.
//
// What stays on http_request (the outer):
// - path / method / content / version (std::string, backend-agnostic)
// - content_size_limit (size_t)
// - the impl_ unique_ptr itself
//
// Everything else lives here.
class http_request_impl {
public:
// Default constructor: heap-backed (default resource). Used by
// create_test_request; semantics unchanged from v1.
http_request_impl()
: http_request_impl(nullptr, nullptr,
std::pmr::polymorphic_allocator<>{
std::pmr::get_default_resource()}) {}
// Allocator-aware constructor. The PMR-aware containers in
// this impl propagate `alloc` through their value_types via the
// standard scoped-allocator semantics built into
// std::pmr::polymorphic_allocator. Wire this from the dispatch path
// (webserver_impl::requests_answer_first_step) with the per-connection
// arena's allocator, and per-request impl_construction stops touching
// the global heap on the warm path.
http_request_impl(MHD_Connection* connection, unescaper_ptr unescaper,
std::pmr::polymorphic_allocator<> alloc)
: connection_(connection),
unescaper_(unescaper),
#ifdef HAVE_BAUTH
username(alloc),
password(alloc),
#endif // HAVE_BAUTH
querystring(alloc),
requestor_ip(alloc),
#ifdef HAVE_DAUTH
digested_user(alloc),
#endif // HAVE_DAUTH
unescaped_args(alloc)
#ifdef HAVE_GNUTLS
, client_cert_dn(alloc),
client_cert_issuer_dn(alloc),
client_cert_cn(alloc),
client_cert_fingerprint_sha256(alloc)
#endif // HAVE_GNUTLS
{
}
http_request_impl(const http_request_impl&) = delete;
http_request_impl& operator=(const http_request_impl&) = delete;
// Move operations: explicitly defaulted to document intent.
// The impl is held through a custom-deleter unique_ptr, so http_request's
// own move ctor/assign operate on the unique_ptr, never on the impl
// directly. These defaults are unused in practice but explicit = default
// is clearer than relying on the implicit definition.
http_request_impl(http_request_impl&&) = default;
http_request_impl& operator=(http_request_impl&&) = default;
// --- per-request backend handles ---
MHD_Connection* connection_ = nullptr;
unescaper_ptr unescaper_ = nullptr;
file_cleanup_callback_ptr file_cleanup_callback_ = nullptr;
// files_ stays default-allocated. Rationale: the entries describe
// disk-side state -- ~http_request (http_request.cpp) walks this
// map and issues remove() calls for uploaded temp files (file_info
// itself has no destructor logic). Keeping the map decoupled from
// the per-connection arena lifecycle simplifies reasoning about
// when those file removals run; uploads are also a comparatively
// cold path (no allocations on the warm GET path).
std::map<std::string, std::map<std::string, http::file_info>> files_;
// --- test-request local storage ---
// When connection_ is null (create_test_request path), get_header /
// get_footer / get_cookie / get_headerlike_values / get_requestor_port /
// has_tls_session fall back to these instead of calling MHD APIs. These
// stay default-allocated because the test-request path has no arena
// and the create_test_request builder hands them in by std::move from
// its own default-allocated http::header_map.
http::header_map headers_local;
http::header_map footers_local;
http::header_map cookies_local;
uint16_t requestor_port_local = 0;
#ifdef HAVE_GNUTLS
bool tls_enabled_local = false;
#endif // HAVE_GNUTLS
// --- lazy caches (formerly the http_request_data_cache struct) ---
// All marked mutable: const accessors lazily populate them. PMR-aware
// so populations on the warm path (lookups, querystring assembly,
// unescaped-arg parsing) hit the per-connection arena instead of the
// global heap.
#ifdef HAVE_BAUTH
mutable std::pmr::string username;
mutable std::pmr::string password;
#endif // HAVE_BAUTH
mutable std::pmr::string querystring;
mutable std::pmr::string requestor_ip;
#ifdef HAVE_DAUTH
mutable std::pmr::string digested_user;
#endif // HAVE_DAUTH
mutable std::pmr::map<std::pmr::string, std::pmr::vector<std::pmr::string>,
http::arg_comparator> unescaped_args;
mutable bool args_populated = false;
// Guard for the lazily-assembled querystring: once get_querystring()
// has run the MHD round-trip (or noticed the test-request path), it is
// never rebuilt. Mirrors the args_populated pattern; keeps
// get_querystring() noexcept while avoiding eager per-request work.
mutable bool querystring_built = false;
#ifdef HAVE_BAUTH
// Guard for fetch_user_pass(): once the MHD round-trip has been made
// (whether or not the request carries a Basic-Auth header), further
// calls to get_user()/get_pass() skip the MHD call entirely.
// Using a dedicated boolean (rather than checking username.empty())
// matches the args_populated pattern and avoids a redundant MHD call
// on requests with no auth credentials where the credential strings
// remain empty after the first fetch.
mutable bool user_pass_fetched = false;
#endif // HAVE_BAUTH
// Cache guard for get_requestor(). Using a dedicated boolean (rather
// than checking requestor_ip.empty()) is consistent with the boolean-
// flag pattern used by args_populated, and is robust if the connection
// layer ever returns an empty IP string.
mutable bool requestor_ip_cached = false;
// Per-request caches for the six container getters. These
// are typed in the public-API container types (default-allocator) so
// http_request::get_*() can return `const ContainerType&` aliasing
// impl-owned storage. Each is built lazily on the first getter call
// and reused on subsequent calls.
//
// Allocator note: the public container types embed std::string_view
// (header_view_map / arg_view_map) or std::string (path_pieces_cached_)
// and use the default allocator. They cannot be made PMR without
// changing the public surface.
// The first call therefore allocates on the global heap; subsequent
// calls are O(1) and zero-allocating -- a strict win over v1, which
// paid the allocation on every call.
//
// The header/footer/cookie caches alias MHD-owned strings via
// string_view, so they share the request's lifetime; the arg-view
// cache aliases the impl's pmr-backed `unescaped_args`, same lifetime.
mutable http::header_view_map headers_cached_;
mutable http::header_view_map footers_cached_;
mutable http::header_view_map cookies_cached_;
mutable bool headers_cache_built_ = false;
mutable bool footers_cache_built_ = false;
mutable bool cookies_cache_built_ = false;
// View-map cache backing get_args(). INVALIDATION RULE: every
// mutator of unescaped_args that can run after the cache is built
// (both set_arg overloads, set_arg_flat, set_args, grow_last_arg)
// must reset args_view_cache_built_ to false before mutating, so the
// next get_args() call rebuilds the view map from the updated
// unescaped_args instead of serving stale views of pre-mutation data.
mutable http::arg_view_map args_view_cached_;
mutable bool args_view_cache_built_ = false;
// Cached "first value per key"
// view used by get_args_flat(). Aliases the pmr-backed unescaped_args
// storage via string_view, so it shares the request's lifetime.
// Avoids the O(n log n) reconstruction the by-value form paid on
// every call.
mutable std::map<std::string_view, std::string_view, http::arg_comparator>
args_flat_view_cached_;
mutable bool args_flat_view_cache_built_ = false;
// Tokenised path pieces. Single cache populated lazily on the first
// get_path_pieces() / get_path_piece(int) call. Stored as
// std::vector<std::string> so get_path_pieces() can return it by
// const& without an ABI break.
//
// No internal arena consumer needs a pmr-backed path-pieces vector,
// so this single heap-allocated cache suffices (no pmr arena copy +
// public mirror pair). If a future radix/route-matching path needs
// arena-allocated pieces, reintroduce a pmr cache alongside.
mutable std::vector<std::string> path_pieces_cached_;
mutable bool path_pieces_cache_built_ = false;
// Per-request cache for the structured cookie vector
// returned by http_request::get_cookies_parsed(). First call parses
// the request's `Cookie:` header (or walks `cookies_local` for
// test-requests with no MHD connection) and populates this vector;
// subsequent calls are O(1) and reuse the same buffer.
//
// The vector uses the default allocator -- it is part of the public
// surface (`const std::vector<cookie>&`) and cannot be PMR without
// an ABI break, matching the `path_pieces_cached_` rationale.
mutable std::vector<cookie> cookies_parsed_cached_;
mutable bool cookies_parsed_cache_built_ = false;
// When true, http_request::operator<< streams credential
// material verbatim (v1 verbose form). Default false: the four
// credential surfaces (pass, Authorization / Proxy-Authorization
// header values, all cookie values) are replaced by the fixed
// token "<redacted>". Plumbed from webserver::expose_credentials_in_logs
// via webserver_impl::requests_answer_first_step, or directly via
// create_test_request::expose_credentials_in_logs() for unit tests.
bool expose_credentials_in_logs_ = false;
#ifdef HAVE_GNUTLS
// Cache fields for the high-level cert accessors. The two
// time fields are spelled std::int64_t (not std::time_t) so they
// match the public API one-for-one and so the value is portable
// across platforms where time_t may be 32-bit (e.g. some Windows
// builds).
mutable bool client_cert_fields_cached = false;
mutable std::pmr::string client_cert_dn;
mutable std::pmr::string client_cert_issuer_dn;
mutable std::pmr::string client_cert_cn;
mutable std::pmr::string client_cert_fingerprint_sha256;
mutable std::int64_t client_cert_not_before = -1;
mutable std::int64_t client_cert_not_after = -1;
mutable bool client_cert_verified = false;
#endif // HAVE_GNUTLS
// --- helpers (moved out of http_request public header) ---
// Map MHD_ValueKind to the corresponding test-request local-storage map
// pointer (nullptr for kinds with no local counterpart). Centralises the
// kind→map dispatch so get_connection_value() and ensure_headerlike_cache()
// share one switch body.
const http::header_map* local_map_for(MHD_ValueKind kind) const noexcept;
std::string_view get_connection_value(std::string_view key, MHD_ValueKind kind) const;
// Ensures the cache for `kind` (HEADER / FOOTER / COOKIE) is
// populated and returns a const reference to it. First call fills the
// map (test-request fallback or MHD scan); subsequent calls return
// the same reference in O(1).
const http::header_view_map& ensure_headerlike_cache(MHD_ValueKind kind) const;
void populate_args() const;
// Populates path_pieces_cached_ from the tokenised request path on
// first call. Subsequent calls are O(1) and zero-allocating.
void ensure_path_pieces_cached(std::string_view path) const;
// Populates cookies_parsed_cached_ from the request's
// `Cookie:` header (live path) or from cookies_local
// (test-request path) on first call. O(1) thereafter.
void ensure_cookies_parsed_cached() const;
// Populates the arg view-map cache from unescaped_args.
// Called from get_args() so the build loop lives inside the impl
// class, keeping all cache-maintenance code in one place.
void ensure_args_view_cached() const;
// Populates args_flat_view_cached_ from unescaped_args, picking the
// first value for each key. Called from get_args_flat().
void ensure_args_flat_view_cached() const;
void set_arg(const std::string& key, const std::string& value, std::size_t content_size_limit);
void set_arg(const char* key, const char* value, std::size_t size, std::size_t content_size_limit);
void set_arg_flat(const std::string& key, const std::string& value, std::size_t content_size_limit);
void set_args(const std::map<std::string, std::string>& args, std::size_t content_size_limit);
void grow_last_arg(const std::string& key, const std::string& value);
#ifdef HAVE_BAUTH
void fetch_user_pass() const;
#endif // HAVE_BAUTH
#ifdef HAVE_GNUTLS
bool has_tls_session() const;
gnutls_session_t get_tls_session() const;
bool has_client_certificate() const;
void populate_all_cert_fields() const;
#endif // HAVE_GNUTLS
// MHD trampolines. Closure pointer is whatever the caller passes
// (usually `this`, or a header_view_map* / std::string* sink).
static MHD_Result build_request_header(void* cls, MHD_ValueKind kind,
const char* key, const char* value);
static MHD_Result build_request_args(void* cls, MHD_ValueKind kind,
const char* key, const char* value);
static MHD_Result build_request_querystring(void* cls, MHD_ValueKind kind,
const char* key, const char* value);
};
// Accumulator passed as cls to build_request_args via
// MHD_get_connection_values. Lives in this header (rather than an
// anonymous namespace in a .cpp) so unit tests can drive
// build_request_args directly and verify the DoS guard.
//
// Security limits:
// max_args_count: maximum number of distinct argument keys to accept
// before returning MHD_NO. Prevents arena exhaustion from crafted
// requests with thousands of unique GET parameters.
// max_args_bytes: maximum total key+value bytes accumulated before
// returning MHD_NO. Applies the same protection on a byte basis.
//
// Defaults are deliberately generous (64 unique keys / 64 KiB total bytes)
// so existing callers that construct the accumulator without setting these
// fields remain compatible. Operators can override via
// create_webserver::max_args_count() / max_args_bytes(); populate_args()
// reads the per-connection override from connection_state when present
// and falls back to these compile-time defaults otherwise.
//
// NOTE: POST argument limits are handled upstream by MHD_OPTION_CONNECTION_
// MEMORY_LIMIT; the guards here apply only to GET arguments processed via
// build_request_args / populate_args.
struct arguments_accumulator {
unescaper_ptr unescaper = nullptr;
// Points at the impl's pmr-backed map.
std::pmr::map<std::pmr::string, std::pmr::vector<std::pmr::string>,
http::arg_comparator>* arguments = nullptr;
// Per-request hard limits.
static constexpr std::size_t DEFAULT_MAX_ARGS_COUNT = 64;
static constexpr std::size_t DEFAULT_MAX_ARGS_BYTES = 65536;
std::size_t max_args_count = DEFAULT_MAX_ARGS_COUNT;
// Bounds pre-unescape (raw) key+value bytes only. A user-registered
// unescaper (unescaper_ptr above) that expands its input (e.g.
// base64 or HTML-entity decoding) is NOT constrained by this limit —
// the budget check runs against the raw wire bytes before the
// callback runs.
std::size_t max_args_bytes = DEFAULT_MAX_ARGS_BYTES;
// Running byte total (key + value lengths) across all calls.
std::size_t accumulated_bytes = 0;
};
} // namespace httpserver::detail
#endif // SRC_HTTPSERVER_DETAIL_HTTP_REQUEST_IMPL_HPP_