Skip to content

Commit 39dd971

Browse files
etrclaude
andcommitted
refactor: split http_request.cpp 4-way (1175 → 392)
Step 6 of the FILE_LOC_MAX ratchet. The 1175-line TU is decomposed along the section markers already in the file into four self-contained units, each well below the 500-line target: src/detail/http_request_impl.cpp 356 detail::http_request_impl method bodies — non-TLS section (connection-value lookup, headerlike caches, build_request_args + the DoS-guard accumulator, build_request_querystring, populate_args, path-pieces cache, set_arg family, fetch_user_pass under HAVE_BAUTH); plus the http_request_impl_deleter dispatch. src/detail/http_request_impl_tls.cpp 265 HAVE_GNUTLS section. scoped_x509_cert RAII helper, has_tls_session, get_tls_session, has_client_certificate, the anonymous-namespace x509 extractors (extract_x509_*, verify_peer_certificate), and populate_all_cert_fields. Whole TU wrapped in `#ifdef HAVE_GNUTLS` so non-TLS builds contribute nothing. src/http_request_auth.cpp 286 Public-API forwarders for the auth/credentials surface: get_user / get_pass / get_digested_user, check_digest_auth / check_digest_auth_digest, and the high-level TLS / client-cert accessors (has_tls_session, has_client_certificate, get_client_cert_*, is_client_cert_verified, get_client_cert_not_before/ after). Matches the http_request_auth.hpp declaration grouping. src/http_request.cpp 392 Residual: ctors / dtor / public-API forwarders for everything that isn't auth (path, method, version, content, header, cookie, footer, args, files, querystring), the connection-arena ctor wiring (pick_resource + delete_impl_heap / destroy_impl_arena, both `static` so they stay co-located with the ctor that takes their address), private setters used by webserver_impl dispatch, and operator<<. All method bodies are byte-for-byte unchanged. Wiring up: - libhttpserver_la_SOURCES gains the three new TUs. - No header changes needed — http_request_impl.hpp already declares every method body the new TUs implement. FILE_LOC_MAX stays at 2700 -- webserver.cpp (2673) is now the lone remaining offender. Step 7 takes it down and drops the bar to 500. Verification: make check ALL PASS (includes hygiene, install-layout, doxygen, examples, readme, release-notes) ./scripts/check-file-size.sh PASS at FILE_LOC_MAX=2700 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d6a2728 commit 39dd971

6 files changed

Lines changed: 910 additions & 787 deletions

File tree

scripts/check-file-size.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
#
3232
# Current offenders above the long-term 500-line target (2026-05-22):
3333
# src/webserver.cpp 2673
34-
# src/http_request.cpp 1175
3534
#
3635
# FILE_LOC_MAX is pinned by the largest unfixed file (webserver.cpp at
3736
# 2673), so it cannot drop until the top offender is decomposed. The

src/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ lib_LTLIBRARIES = libhttpserver.la
2525
# builds. The WS-off branch in websocket_handler.cpp provides stub
2626
# definitions (every member throws feature_unavailable except is_valid()
2727
# which returns false).
28-
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp create_webserver.cpp create_test_request.cpp websocket_handler.cpp hook_handle.cpp detail/http_endpoint.cpp detail/body.cpp detail/ip_representation.cpp
28+
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_request_auth.cpp http_response.cpp create_webserver.cpp create_test_request.cpp websocket_handler.cpp hook_handle.cpp detail/http_endpoint.cpp detail/body.cpp detail/ip_representation.cpp detail/http_request_impl.cpp detail/http_request_impl_tls.cpp
2929
# noinst_HEADERS: shipped in the tarball but NEVER installed under $prefix/include.
3030
# Detail headers (httpserver/detail/*.hpp) live here so they cannot leak to
3131
# downstream consumers — the public surface comes in through <httpserver.hpp>.

src/detail/http_request_impl.cpp

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011-2019 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+
// detail::http_request_impl method bodies (non-TLS section). The TLS /
22+
// client-cert section lives in src/detail/http_request_impl_tls.cpp;
23+
// the public-API forwarders + ctor live in src/http_request.cpp; the
24+
// auth-surface public-API forwarders live in src/http_request_auth.cpp.
25+
26+
#include "httpserver/http_request.hpp"
27+
28+
#include <algorithm>
29+
#include <map>
30+
#include <memory_resource>
31+
#include <string>
32+
#include <string_view>
33+
#include <tuple>
34+
#include <utility>
35+
#include <vector>
36+
37+
#include <microhttpd.h>
38+
39+
#include "httpserver/detail/http_request_impl.hpp"
40+
#include "httpserver/http_utils.hpp"
41+
42+
namespace httpserver {
43+
44+
namespace detail {
45+
46+
std::string_view http_request_impl::get_connection_value(std::string_view key, MHD_ValueKind kind) const {
47+
// Test-request path: connection_ is null, fall back to local storage.
48+
if (connection_ == nullptr) {
49+
const auto* map = [&]() -> const http::header_map* {
50+
switch (kind) {
51+
case MHD_HEADER_KIND: return &headers_local;
52+
case MHD_FOOTER_KIND: return &footers_local;
53+
case MHD_COOKIE_KIND: return &cookies_local;
54+
default: return nullptr;
55+
}
56+
}();
57+
if (map != nullptr) {
58+
auto it = map->find(std::string(key));
59+
if (it != map->end()) return it->second;
60+
}
61+
return http_request::EMPTY;
62+
}
63+
64+
const char* header_c = MHD_lookup_connection_value(connection_, kind, key.data());
65+
66+
if (header_c == nullptr) return http_request::EMPTY;
67+
68+
return header_c;
69+
}
70+
71+
MHD_Result http_request_impl::build_request_header(void* cls, MHD_ValueKind kind,
72+
const char* key, const char* value) {
73+
// Parameters needed to respect MHD interface, but not used in the implementation.
74+
std::ignore = kind;
75+
76+
http::header_view_map* dhr = static_cast<http::header_view_map*>(cls);
77+
(*dhr)[key] = value;
78+
return MHD_YES;
79+
}
80+
81+
const http::header_view_map& http_request_impl::ensure_headerlike_cache(MHD_ValueKind kind) const {
82+
// Pick the cache slot and build-flag matching `kind`. We resolve them
83+
// up front so the cold (build) and warm (return) paths share a single
84+
// reference without re-switching.
85+
http::header_view_map* cache = nullptr;
86+
bool* built = nullptr;
87+
const http::header_map* local_fallback = nullptr;
88+
switch (kind) {
89+
case MHD_HEADER_KIND:
90+
cache = &headers_cached_;
91+
built = &headers_cache_built_;
92+
local_fallback = &headers_local;
93+
break;
94+
case MHD_FOOTER_KIND:
95+
cache = &footers_cached_;
96+
built = &footers_cache_built_;
97+
local_fallback = &footers_local;
98+
break;
99+
case MHD_COOKIE_KIND:
100+
cache = &cookies_cached_;
101+
built = &cookies_cache_built_;
102+
local_fallback = &cookies_local;
103+
break;
104+
default:
105+
// Unsupported kind: hand back the headers cache (kept empty)
106+
// as a safe fallback; the public API never reaches here.
107+
cache = &headers_cached_;
108+
built = &headers_cache_built_;
109+
local_fallback = &headers_local;
110+
break;
111+
}
112+
113+
if (*built) {
114+
return *cache;
115+
}
116+
117+
// Test-request path: connection_ is null, build the view map from
118+
// local owning storage (the create_test_request builder populated it).
119+
if (connection_ == nullptr) {
120+
if (local_fallback != nullptr) {
121+
for (const auto& [k, v] : *local_fallback) {
122+
(*cache)[k] = v;
123+
}
124+
}
125+
*built = true;
126+
return *cache;
127+
}
128+
129+
// Live-request path: ask MHD to enumerate values for this kind into
130+
// the cache. The string_view keys/values alias MHD-owned storage that
131+
// outlives the request handler.
132+
MHD_get_connection_values(connection_, kind, &http_request_impl::build_request_header,
133+
reinterpret_cast<void*>(cache));
134+
*built = true;
135+
return *cache;
136+
}
137+
138+
MHD_Result http_request_impl::build_request_args(void* cls, MHD_ValueKind kind,
139+
const char* key, const char* arg_value) {
140+
// Parameters needed to respect MHD interface, but not used in the implementation.
141+
std::ignore = kind;
142+
143+
arguments_accumulator* aa = static_cast<arguments_accumulator*>(cls);
144+
145+
// Security guard (security-reviewer-iter1-2): reject requests that
146+
// exceed the per-request argument count or total byte budget. Both
147+
// limits prevent a crafted request with thousands of unique GET
148+
// arguments from exhausting the per-connection arena and the heap
149+
// upstream. Returning MHD_NO stops MHD's iteration over remaining
150+
// arguments immediately.
151+
std::string_view key_sv(key);
152+
std::string_view val_sv((arg_value != nullptr) ? arg_value : "");
153+
154+
// Apply count limit: check how many unique keys exist so far.
155+
auto& args = *aa->arguments;
156+
const std::size_t new_unique =
157+
(args.find(key_sv) == args.end()) ? 1u : 0u;
158+
if (args.size() + new_unique > aa->max_args_count) {
159+
return MHD_NO;
160+
}
161+
162+
// Apply byte limit: count key + value bytes accumulated so far.
163+
const std::size_t this_pair_bytes = key_sv.size() + val_sv.size();
164+
if (aa->accumulated_bytes + this_pair_bytes > aa->max_args_bytes) {
165+
return MHD_NO;
166+
}
167+
aa->accumulated_bytes += this_pair_bytes;
168+
169+
// Unescape into a temporary std::string (the C-style unescaper is
170+
// string-typed). The unescape itself touches the global heap if the
171+
// key/value spill out of std::string's small-buffer; tracked by
172+
// TASK-018 (move the unescape onto the arena too).
173+
std::string value(val_sv);
174+
http::base_unescaper(&value, aa->unescaper);
175+
176+
// Look up via heterogeneous string_view (no allocation), insert the
177+
// key as pmr::string in the map's allocator domain on miss. The
178+
// value vector is allocator-constructed in place via the same
179+
// allocator (scoped propagation gives nested pmr::strings the
180+
// right allocator too).
181+
auto pmr_alloc = args.get_allocator();
182+
auto it = args.find(key_sv);
183+
if (it == args.end()) {
184+
std::pmr::vector<std::pmr::string> empty(pmr_alloc);
185+
auto inserted = args.emplace(
186+
std::pmr::string(key_sv.data(), key_sv.size(), pmr_alloc),
187+
std::move(empty));
188+
it = inserted.first;
189+
}
190+
// emplace_back into a pmr::vector<pmr::string>: use (ptr, size); the
191+
// outer vector's allocator-propagating construct wires the inner
192+
// pmr::string's allocator automatically. Passing the allocator
193+
// ourselves leads to double-injection via uses-allocator construction.
194+
it->second.emplace_back(value.data(), value.size());
195+
return MHD_YES;
196+
}
197+
198+
MHD_Result http_request_impl::build_request_querystring(void* cls, MHD_ValueKind kind,
199+
const char* key_value, const char* arg_value) {
200+
// Parameters needed to respect MHD interface, but not used in the implementation.
201+
std::ignore = kind;
202+
203+
// TASK-016: cls is a pmr::string* into impl_->querystring; growth
204+
// allocates from the per-connection arena.
205+
std::pmr::string* qs = static_cast<std::pmr::string*>(cls);
206+
207+
std::string_view key = key_value;
208+
std::string_view value = ((arg_value == nullptr) ? "" : arg_value);
209+
210+
// Limit to a single allocation.
211+
qs->reserve(qs->size() + key.size() + value.size() + 3);
212+
213+
*qs += (qs->empty() ? "?" : "&");
214+
*qs += key;
215+
*qs += "=";
216+
*qs += value;
217+
218+
return MHD_YES;
219+
}
220+
221+
void http_request_impl::populate_args() const {
222+
if (args_populated) {
223+
return;
224+
}
225+
// Test-request path: connection_ is null, args already set directly.
226+
if (connection_ == nullptr) {
227+
args_populated = true;
228+
return;
229+
}
230+
arguments_accumulator aa;
231+
aa.unescaper = unescaper_;
232+
aa.arguments = &unescaped_args;
233+
MHD_get_connection_values(connection_, MHD_GET_ARGUMENT_KIND,
234+
&http_request_impl::build_request_args,
235+
reinterpret_cast<void*>(&aa));
236+
237+
args_populated = true;
238+
}
239+
240+
void http_request_impl::ensure_path_pieces_cached(std::string_view path) const {
241+
if (path_pieces_cached) {
242+
return;
243+
}
244+
// tokenize_url returns std::vector<std::string> (default-allocator).
245+
// Copy element-wise into the pmr-backed cache so the stored strings
246+
// live on the arena, not the heap.
247+
auto tokens = http::http_utils::tokenize_url(std::string(path));
248+
path_pieces.clear();
249+
path_pieces.reserve(tokens.size());
250+
for (auto& t : tokens) {
251+
// Vector's allocator-propagating construct wires the inner
252+
// pmr::string's allocator automatically.
253+
path_pieces.emplace_back(t.data(), t.size());
254+
}
255+
path_pieces_cached = true;
256+
}
257+
258+
namespace {
259+
260+
// Helper: look up `key` via heterogeneous string_view (no alloc), insert
261+
// a pmr::string key + an empty vector if missing, then append `value`.
262+
// All allocations use the map's allocator (the per-connection arena).
263+
inline auto& find_or_insert_arg(
264+
std::pmr::map<std::pmr::string, std::pmr::vector<std::pmr::string>,
265+
http::arg_comparator>& args,
266+
std::string_view key) {
267+
auto pmr_alloc = args.get_allocator();
268+
auto it = args.find(key);
269+
if (it == args.end()) {
270+
std::pmr::vector<std::pmr::string> empty(pmr_alloc);
271+
auto inserted = args.emplace(
272+
std::pmr::string(key.data(), key.size(), pmr_alloc),
273+
std::move(empty));
274+
it = inserted.first;
275+
}
276+
return it->second;
277+
}
278+
279+
inline void append_arg(
280+
std::pmr::map<std::pmr::string, std::pmr::vector<std::pmr::string>,
281+
http::arg_comparator>& args,
282+
std::string_view key, std::string_view value) {
283+
auto& vec = find_or_insert_arg(args, key);
284+
// emplace_back forwards (ptr, size) to pmr::string's (ptr, size, alloc)
285+
// ctor; the trailing allocator is supplied by the vector's
286+
// allocator-propagating construct.
287+
vec.emplace_back(value.data(), value.size());
288+
}
289+
290+
} // namespace
291+
292+
void http_request_impl::set_arg(const std::string& key, const std::string& value,
293+
std::size_t content_size_limit) {
294+
append_arg(unescaped_args, key,
295+
std::string_view(value).substr(
296+
0, std::min(value.size(), content_size_limit)));
297+
}
298+
299+
void http_request_impl::set_arg(const char* key, const char* value, std::size_t size,
300+
std::size_t content_size_limit) {
301+
append_arg(unescaped_args, key,
302+
std::string_view(value, std::min(size, content_size_limit)));
303+
}
304+
305+
void http_request_impl::set_arg_flat(const std::string& key, const std::string& value,
306+
std::size_t content_size_limit) {
307+
auto& vec = find_or_insert_arg(unescaped_args, key);
308+
vec.clear();
309+
const auto bounded_size = std::min(value.size(), content_size_limit);
310+
vec.emplace_back(value.data(), bounded_size);
311+
}
312+
313+
void http_request_impl::set_args(const std::map<std::string, std::string>& args,
314+
std::size_t content_size_limit) {
315+
for (auto const& [key, value] : args) {
316+
append_arg(unescaped_args, key,
317+
std::string_view(value).substr(
318+
0, std::min(value.size(), content_size_limit)));
319+
}
320+
}
321+
322+
void http_request_impl::grow_last_arg(const std::string& key, const std::string& value) {
323+
auto& vec = find_or_insert_arg(unescaped_args, key);
324+
if (!vec.empty()) {
325+
vec.back() += value;
326+
} else {
327+
vec.emplace_back(value.data(), value.size());
328+
}
329+
}
330+
331+
#ifdef HAVE_BAUTH
332+
void http_request_impl::fetch_user_pass() const {
333+
// Test-request path: connection_ is null, credentials already set.
334+
if (connection_ == nullptr) {
335+
return;
336+
}
337+
struct MHD_BasicAuthInfo* info = MHD_basic_auth_get_username_password3(connection_);
338+
339+
if (info != nullptr) {
340+
username.assign(info->username, info->username_len);
341+
if (info->password != nullptr) {
342+
password.assign(info->password, info->password_len);
343+
}
344+
MHD_free(info);
345+
}
346+
}
347+
#endif // HAVE_BAUTH
348+
349+
void http_request_impl_deleter::operator()(http_request_impl* p) const noexcept {
350+
if (fn != nullptr) {
351+
fn(p);
352+
}
353+
}
354+
355+
} // namespace detail
356+
} // namespace httpserver

0 commit comments

Comments
 (0)