Skip to content

Commit de0c334

Browse files
etrclaude
andcommitted
chore: readability comment sweep + WIP routing/cookie refactor checkpoint
Phase 1 of the feature/v2.0 readability pass, plus the in-progress routing (exact-tier-first lookup) and cookie-mirror refactor that was already in the working tree. Bundled as a single known-good checkpoint before the Phase 2 mechanical renames. Phase 1 (comment-only, verified against a pre-sweep snapshot): - stripped provenance breadcrumbs (TASK-/DR-/finding-# / spec cites) from comment text, keeping the behavioral rationale; refs remain only inside string literals (static_assert / [[deprecated]]), which are code. - fixed actively-misleading comments (any_hooks_ memory-ordering contract, hook_handle detach()/ctor discriminator, methods_allowed_ TOCTOU overclaim, matched_path_template, use_regex default, etc.). - added missing invariant comments (route-tier self-match caveat, route_cache hash-sync, request-lifecycle state machine, ip_representation shape invariants, path-normalization chain, ...). Build green (make -j4, exit 0); full suite 109/109 prior to checkpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9af15a1 commit de0c334

206 files changed

Lines changed: 2207 additions & 2076 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/cookie.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
USA
1919
*/
2020

21-
// TASK-064: implementation of the structured cookie value type.
21+
// Implementation of the structured cookie value type.
2222
// See src/httpserver/cookie.hpp for the public contract.
2323

2424
#include "httpserver/cookie.hpp"

src/create_test_request.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ namespace httpserver {
3232

3333
namespace {
3434

35-
// TASK-016: heap-deleter used for the test-request impl. The live-request
35+
// Heap-deleter used for the test-request impl. The live-request
3636
// constructor in http_request.cpp uses an internal-linkage delete_impl_heap
3737
// of the same shape; reproducing it here avoids exposing it across TUs.
3838
// Both implementations call operator delete, matching v1 lifetime exactly.
@@ -103,7 +103,7 @@ http_request create_test_request::build() {
103103
req.impl_->tls_enabled_local = _tls_enabled;
104104
#endif // HAVE_GNUTLS
105105

106-
// TASK-057: propagate the test-builder opt-in into the impl. The
106+
// Propagate the test-builder opt-in into the impl. The
107107
// webserver-dispatch path does the equivalent assignment in
108108
// webserver_impl::requests_answer_first_step.
109109
req.set_expose_credentials_in_logs(_expose_credentials_in_logs);

src/create_webserver.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
namespace httpserver {
4141

42-
// TASK-034 / PRD-FLG-REQ-001: defined here (not as a default member
42+
// Defined here (not as a default member
4343
// initializer in the header) so the public header carries no
4444
// #ifdef HAVE_BAUTH. The library was compiled with the right
4545
// HAVE_BAUTH state; the consumer TU's HAVE_BAUTH is irrelevant.
@@ -51,7 +51,7 @@ bool create_webserver::basic_auth_default() noexcept {
5151
#endif
5252
}
5353

54-
// PRD-FLG-REQ-001: same pattern as basic_auth_default(). Returns true
54+
// Same pattern as basic_auth_default(). Returns true
5555
// on HAVE_DAUTH-on builds (preserving historical behaviour) and false
5656
// on HAVE_DAUTH-off builds so an unmodified builder doesn't trip the
5757
// feature_unavailable guard in webserver::webserver().

src/detail/body.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ namespace detail {
4444
// Layout-pinning static_asserts for iovec_entry → MHD_IoVec / struct iovec.
4545
// These asserts live next to the cast site (iovec_body::materialize below)
4646
// to catch platform layout drift at compile time rather than at runtime.
47-
// iovec_response.cpp was removed in TASK-013 (the subclass hierarchy was
48-
// replaced by http_response value-type factories in TASK-010).
4947
//
5048
// The POSIX `struct iovec` asserts are gated on !_WIN32 (no <sys/uio.h> on
5149
// MSYS2/mingw); the MHD_IoVec asserts run everywhere because that's the
@@ -98,7 +96,7 @@ MHD_Response* empty_body::materialize() {
9896
MHD_Response* string_body::materialize() {
9997
// _static variant: accepts const void* directly, no const_cast needed,
10098
// and documents the same PERSISTENT ownership semantics — the buffer is
101-
// owned by *this and outlives the MHD_Response (TASK-009). Requires
99+
// owned by *this and outlives the MHD_Response. Requires
102100
// MHD >= 0x00097701, well below the project minimum 0x01000000.
103101
return MHD_create_response_from_buffer_static(
104102
content_.size(),
@@ -109,7 +107,7 @@ MHD_Response* string_body::materialize() {
109107
// file_body — opens the file and fstat's it at construction so size() is
110108
// accurate immediately. materialize() uses fstat's st_size; it never calls
111109
// lseek(), so the fd's read position remains at 0 when handed to
112-
// MHD_create_response_from_fd (security-reviewer-iter1-1 / CWE-367).
110+
// MHD_create_response_from_fd (CWE-367).
113111
// ---------------------------------------------------------------------------
114112
file_body::file_body(std::string path) noexcept
115113
: path_(std::move(path)) {
@@ -128,7 +126,7 @@ file_body::file_body(std::string path) noexcept
128126
}
129127

130128
// Use fstat's st_size directly — no lseek, no TOCTOU, no fd-position
131-
// side-effect (security-reviewer-iter1-1 / performance-reviewer-iter1-4).
129+
// side-effect.
132130
//
133131
// CWE-190 guard: on 32-bit platforms std::size_t is 32 bits while off_t
134132
// can be 64 bits; a file larger than 4 GiB would silently truncate via
@@ -234,8 +232,8 @@ MHD_Response* pipe_body::materialize() {
234232
// ---------------------------------------------------------------------------
235233
ssize_t deferred_body::trampoline(void* cls, std::uint64_t pos,
236234
char* buf, std::size_t max) {
237-
// Guard against null cls or empty producer_ (security-reviewer-iter1-3 /
238-
// CWE-476). MHD's callback mechanism does not catch C++ exceptions, so
235+
// Guard against null cls or empty producer_ (CWE-476).
236+
// MHD's callback mechanism does not catch C++ exceptions, so
239237
// throwing std::bad_function_call here would call std::terminate().
240238
// Return MHD_CONTENT_READER_END_WITH_ERROR instead.
241239
auto* self = static_cast<deferred_body*>(cls);
@@ -248,13 +246,13 @@ ssize_t deferred_body::trampoline(void* cls, std::uint64_t pos,
248246
MHD_Response* deferred_body::materialize() {
249247
// Block size 1024 mirrors v1 deferred_response::get_raw_response_helper.
250248
// Free-callback is nullptr because *this owns producer_ and outlives the
251-
// MHD_Response (TASK-009 enforces this via http_response's lifetime).
249+
// MHD_Response (http_response's lifetime enforces this).
252250
return MHD_create_response_from_callback(
253251
MHD_SIZE_UNKNOWN, 1024, &deferred_body::trampoline, this, nullptr);
254252
}
255253

256254
// ---------------------------------------------------------------------------
257-
// digest_challenge_body — TASK-062 / RFC 7616.
255+
// digest_challenge_body — RFC 7616 Digest auth challenge.
258256
//
259257
// Returns a body-only MHD_Response carrying the "access denied" payload.
260258
// The WWW-Authenticate header itself is NOT attached here -- the dispatch

src/detail/hook_phase_dispatch.cpp

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@
2020

2121
// hook_phase_dispatch.cpp -- per-phase hook firing (the fire_* family).
2222
//
23-
// Carved out of src/hook_handle.cpp in TASK-086 to keep both translation
24-
// units under the project per-file LOC ceiling (FILE_LOC_MAX in
23+
// Split out of src/hook_handle.cpp to keep both translation units under
24+
// the project per-file LOC ceiling (FILE_LOC_MAX in
2525
// scripts/check-file-size.sh). hook_handle.cpp retains the hook_handle
2626
// lifetime ops (move/dtor/detach/remove and the per-phase erase
2727
// templates); this file holds the dispatch helpers and every
28-
// detail::webserver_impl::fire_* member. No behaviour change: the bodies
29-
// are moved verbatim.
28+
// detail::webserver_impl::fire_* member.
3029

3130
#include <exception>
3231
#include <optional>
@@ -44,20 +43,13 @@
4443

4544
namespace httpserver {
4645

47-
// ---- fire_* (TASK-046) ---------------------------------------------------
46+
// ---- fire_*: lifecycle phases --------------------------------------------
4847
//
49-
// Per the plan: snapshot the phase vector under a shared_lock, release
50-
// the lock, iterate the snapshot inside a try/catch routed through
51-
// log_dispatch_error. Mirrors the TASK-027 route-cache promotion pattern
52-
// (shared_lock + atomic gate). Reentrancy: a hook may call
53-
// ws.add_hook() / handle.remove() because we no longer hold the table
54-
// lock by the time the user code runs.
55-
56-
// (Previously an unused kHookSnapshotReserve constant lived here.
57-
// The thread_local snapshot buffers below are now sized lazily on
58-
// first use; the constant was dead code as of TASK-048's perf rework.
59-
// Removed in TASK-049 to silence -Wunused-const-variable under
60-
// --enable-debug.)
48+
// Snapshot the phase vector under a shared_lock, release the lock,
49+
// iterate the snapshot inside a try/catch routed through
50+
// log_dispatch_error. Reentrancy: a hook may call ws.add_hook() /
51+
// handle.remove() because we no longer hold the table lock by the time
52+
// the user code runs.
6153

6254
// fire_hooks_for_phase: shared dispatch template for all void-returning
6355
// lifecycle hook phases. Snapshots the caller-supplied vector under a
@@ -68,12 +60,12 @@ namespace httpserver {
6860
// callers are noexcept and will std::terminate on uncaught throws; the
6961
// inner catches ensure no exception can propagate.
7062
//
71-
// TASK-048 perf: the snapshot vector is thread_local so the per-template-
63+
// Perf: the snapshot vector is thread_local so the per-template-
7264
// instantiation per-thread buffer is reused across calls — no heap
7365
// allocation after the first request on each thread (warm path).
7466

7567
// One-allocation log helpers shared by fire_hooks_for_phase and
76-
// fire_short_circuit_hooks_for_phase (TASK-049 review). Free helpers
68+
// fire_short_circuit_hooks_for_phase. Free helpers
7769
// (not lambdas captured per-instance) so the two templates' inner
7870
// catch blocks can stay one-liners without divergent strings.
7971
static void log_hook_threw(detail::webserver_impl* impl,
@@ -144,7 +136,7 @@ void detail::webserver_impl::fire_connection_closed(
144136
fire_hooks_for_phase(this, hooks_connection_closed_, ctx, "connection_closed");
145137
}
146138

147-
// ---- fire_* (TASK-047) ---------------------------------------------------
139+
// ---- fire_*: pre-handler short-circuit phases ----------------------------
148140
//
149141
// Short-circuit-capable firing helpers. Mirrors fire_hooks_for_phase but:
150142
// - the entry's std::function returns hook_action,
@@ -153,9 +145,9 @@ void detail::webserver_impl::fire_connection_closed(
153145
// extracted http_response.
154146
//
155147
// A throwing hook is caught + logged and treated as if it had returned
156-
// hook_action::pass() -- same DR-009 §5.2 routing as the void variant.
148+
// hook_action::pass() -- same exception routing as the void variant.
157149
//
158-
// TASK-048 perf: thread_local snapshot buffer (same rationale as
150+
// Perf: thread_local snapshot buffer (same rationale as
159151
// fire_hooks_for_phase above).
160152
template <typename Ctx>
161153
static std::optional<::httpserver::http_response>
@@ -208,7 +200,7 @@ detail::webserver_impl::fire_body_chunk(
208200
this, hooks_body_chunk_, ctx, "body_chunk");
209201
}
210202

211-
// ---- fire_* (TASK-048) ---------------------------------------------------
203+
// ---- fire_*: route_resolved + before_handler -----------------------------
212204

213205
void detail::webserver_impl::fire_route_resolved(
214206
const ::httpserver::route_resolved_ctx& ctx) noexcept {
@@ -222,7 +214,7 @@ detail::webserver_impl::fire_before_handler(
222214
this, hooks_before_handler_, ctx, "before_handler");
223215
}
224216

225-
// ---- fire_* (TASK-049) ---------------------------------------------------
217+
// ---- fire_*: handler_exception -------------------------------------------
226218
//
227219
// handler_exception is the only short-circuit-capable phase whose ctx is
228220
// passed as `const&` (the user cannot mutate the in-flight exception or
@@ -235,10 +227,16 @@ detail::webserver_impl::fire_before_handler(
235227
// try/catch -- with an extra tail that invokes the alias slot after the
236228
// user vector is exhausted.
237229
//
238-
// Structural differences preventing template reuse (findings #12, #13):
230+
// Structural differences vs fire_short_circuit_hooks_for_phase:
239231
// 1. ctx is `const Ctx&` (not `Ctx&`) -- the template takes mutable.
240232
// 2. The alias tail (handler_exception_alias_) sits AFTER the user
241233
// vector and has its own throw-containment block.
234+
// 3. No empty-vector early-return under the lock: the template bails
235+
// out before copying when the phase vector is empty; here the
236+
// snapshot copy runs unconditionally. Behaviourally irrelevant --
237+
// copying an empty vector into the already-cleared thread_local
238+
// buffer allocates nothing, and control must fall through to the
239+
// alias tail regardless (it fires even with zero user hooks).
242240
// If the template is extended to support const-ctx or alias-tail in a
243241
// future task, fire_handler_exception should be collapsed into it and
244242
// the rationale comment updated accordingly.
@@ -248,7 +246,7 @@ detail::webserver_impl::fire_handler_exception(
248246
using EntryVec = std::vector<phase_entry<
249247
::httpserver::hook_action(
250248
const ::httpserver::handler_exception_ctx&)>>;
251-
// Per-thread cost note (finding #30): this thread_local EntryVec is a
249+
// Per-thread cost note: this thread_local EntryVec is a
252250
// SECOND per-thread snapshot buffer in addition to the one inside
253251
// fire_short_circuit_hooks_for_phase. Both buffers are warm after the
254252
// first invocation on a given thread (no heap allocation on subsequent
@@ -280,13 +278,13 @@ detail::webserver_impl::fire_handler_exception(
280278
}
281279
// Tail: invoke the alias slot, if any. Read without synchronisation:
282280
// the slot is single-writer-at-construction, immutable after
283-
// webserver::start() (DR-012 / §4.10).
281+
// webserver::start().
284282
//
285283
// Throw containment: a throwing alias is logged with the legacy
286-
// "internal_error_handler threw" prefix so the DR-009 §5.2 point 4
287-
// log contract (and its tests in basic.cpp) is preserved verbatim
288-
// even though the call site has moved from
289-
// run_internal_error_handler_safely into the hook chain.
284+
// "internal_error_handler threw" prefix so the error-log contract
285+
// (and its tests in basic.cpp) is preserved verbatim even though
286+
// the call site has moved from run_internal_error_handler_safely
287+
// into the hook chain.
290288
if (handler_exception_alias_) {
291289
try {
292290
auto action = handler_exception_alias_(ctx);
@@ -305,7 +303,7 @@ detail::webserver_impl::fire_handler_exception(
305303
return std::nullopt;
306304
}
307305

308-
// ---- fire_* (TASK-050) ---------------------------------------------------
306+
// ---- fire_*: post-handler phases -----------------------------------------
309307
//
310308
// after_handler is the post-handler short-circuit. Returns engaged
311309
// optional iff a hook short-circuited with respond_with(); the caller
@@ -320,13 +318,13 @@ detail::webserver_impl::fire_after_handler(
320318
}
321319

322320
// response_sent: void-returning user hooks, then the log_access_alias_
323-
// slot. Same alias-tail pattern as fire_handler_exception (TASK-049).
321+
// slot. Same alias-tail pattern as fire_handler_exception.
324322
void detail::webserver_impl::fire_response_sent(
325323
const ::httpserver::response_sent_ctx& ctx) noexcept {
326324
fire_hooks_for_phase(this, hooks_response_sent_, ctx, "response_sent");
327325
// Tail: invoke the alias slot, if any. Read without synchronisation:
328326
// the slot is single-writer-at-construction, immutable after
329-
// webserver::start() (DR-012 / §4.10).
327+
// webserver::start().
330328
if (log_access_alias_) {
331329
try {
332330
log_access_alias_(ctx);

src/detail/http_endpoint.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,19 @@ bool http_endpoint::operator <(const http_endpoint& b) const {
164164
bool http_endpoint::match(const http_endpoint& url) const {
165165
if (!reg_compiled) throw std::invalid_argument("Cannot run match. Regex suppressed.");
166166

167+
// Family (prefix) rule: a family endpoint matches when the FIRST N
168+
// request segments match the registered pattern, where N is the
169+
// pattern's own segment count (url_pieces.size()). A request with
170+
// fewer segments than the pattern can never satisfy that prefix
171+
// rule, so it falls through to plain full-string matching below
172+
// (which also handles every non-family endpoint).
167173
if (!family_url || url.url_pieces.size() < url_pieces.size()) {
168174
return regex_match(url.url_complete, re_url_normalized);
169175
}
170176

177+
// Rebuild the request path truncated to the pattern's first N
178+
// segments in `nn`, so the prefix can be regex-matched against
179+
// re_url_normalized (which matches whole strings, not prefixes).
171180
string nn = "/";
172181
bool first = true;
173182
for (unsigned int i = 0; i < url_pieces.size(); i++) {

0 commit comments

Comments
 (0)