Skip to content

Commit 2c15687

Browse files
committed
Merge TASK-034: Build-flag-independent public API + webserver::features() (PRD-FLG-REQ-001..005)
2 parents 3832d3b + 8b4ed30 commit 2c15687

22 files changed

Lines changed: 1032 additions & 92 deletions

specs/tasks/M5-routing-lifecycle/TASK-034.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
Remove `#ifdef HAVE_*` from public headers and provide runtime feature reporting plus documented sentinel/throw behavior when a build-disabled feature is invoked.
99

1010
**Action Items:**
11-
- [ ] Remove `#ifdef HAVE_BAUTH | HAVE_DAUTH | HAVE_GNUTLS | HAVE_WEBSOCKET` guards from every public header — the methods are now declared unconditionally.
12-
- [ ] Implementation files: when the relevant `HAVE_*` is undefined, the implementation either returns the documented sentinel (empty `string_view`, `false`, `-1`) or throws `feature_unavailable` per §7.
13-
- [ ] Add `webserver::features()` returning `struct features { bool basic_auth; bool digest_auth; bool tls; bool websocket; };`. Implementation reads compile-time `HAVE_*` and returns a value.
14-
- [ ] `create_webserver::use_ssl(true)` on a non-TLS build throws `feature_unavailable` at `webserver` construction time (consistent across all features per §7).
15-
- [ ] `register_ws_resource` on a non-WebSocket build throws `feature_unavailable`.
16-
- [ ] Confirm `feature_unavailable.what()` always names both feature and the controlling flag (TASK-003 invariant).
11+
- [x] Remove `#ifdef HAVE_BAUTH | HAVE_DAUTH | HAVE_GNUTLS | HAVE_WEBSOCKET` guards from every public header — the methods are now declared unconditionally.
12+
- [x] Implementation files: when the relevant `HAVE_*` is undefined, the implementation either returns the documented sentinel (empty `string_view`, `false`, `-1`) or throws `feature_unavailable` per §7.
13+
- [x] Add `webserver::features()` returning `struct features { bool basic_auth; bool digest_auth; bool tls; bool websocket; };`. Implementation reads compile-time `HAVE_*` and returns a value.
14+
- [x] `create_webserver::use_ssl(true)` on a non-TLS build throws `feature_unavailable` at `webserver` construction time (consistent across all features per §7).
15+
- [x] `register_ws_resource` on a non-WebSocket build throws `feature_unavailable`.
16+
- [x] Confirm `feature_unavailable.what()` always names both feature and the controlling flag (TASK-003 invariant).
1717

1818
**Dependencies:**
1919
- Blocked by: TASK-003, TASK-019, TASK-033
@@ -29,4 +29,4 @@ Remove `#ifdef HAVE_*` from public headers and provide runtime feature reporting
2929
**Related Requirements:** PRD-FLG-REQ-001..005
3030
**Related Decisions:** §7
3131

32-
**Status:** Not Started
32+
**Status:** Done

specs/tasks/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Nominally: **13 sequential tasks**, each S–XL. Most other tasks parallelize of
116116
| TASK-031 | Handler error-propagation contract (DR-009) | M5 | Done | TASK-027, TASK-030 |
117117
| TASK-032 | Thread-safety contract stress test (DR-008) | M5 | Done | TASK-027, TASK-031 |
118118
| TASK-033 | `create_webserver` builder cleanup | M5 | Done | TASK-006, TASK-014 |
119-
| TASK-034 | Build-flag-independent public API + `webserver::features()` | M5 | Not Started | TASK-003, TASK-019, TASK-033 |
119+
| TASK-034 | Build-flag-independent public API + `webserver::features()` | M5 | Done | TASK-003, TASK-019, TASK-033 |
120120
| TASK-035 | Smart-pointer `register_ws_resource` overloads | M5 | Not Started | TASK-014, TASK-034 |
121121
| TASK-036 | Handler return-by-value dispatch cutover | M5 | Not Started | TASK-022, TASK-025, TASK-027, TASK-031 |
122122
| TASK-037 | CI test for build-flag invariance | M6 | Not Started | TASK-034 |

specs/unworked_review_issues/2026-05-14_211841_task-034.md

Lines changed: 155 additions & 0 deletions
Large diffs are not rendered by default.

src/Makefile.am

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@
1919
AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/ -DHTTPSERVER_COMPILATION
2020
METASOURCES = AUTO
2121
lib_LTLIBRARIES = libhttpserver.la
22-
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp http_resource.cpp create_webserver.cpp create_test_request.cpp detail/http_endpoint.cpp detail/body.cpp
22+
# TASK-034 / PRD-FLG-REQ-001: websocket_handler.cpp is built and
23+
# websocket_handler.hpp is installed unconditionally so the public
24+
# surface is identical across HAVE_WEBSOCKET-on and HAVE_WEBSOCKET-off
25+
# builds. The WS-off branch in websocket_handler.cpp provides stub
26+
# definitions (every member throws feature_unavailable except is_valid()
27+
# which returns false).
28+
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp http_resource.cpp create_webserver.cpp create_test_request.cpp websocket_handler.cpp detail/http_endpoint.cpp detail/body.cpp
2329
# noinst_HEADERS: shipped in the tarball but NEVER installed under $prefix/include.
2430
# Detail headers (httpserver/detail/*.hpp) live here so they cannot leak to
2531
# downstream consumers — the public surface comes in through <httpserver.hpp>.
2632
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/detail/modded_request.hpp httpserver/detail/http_endpoint.hpp httpserver/detail/body.hpp httpserver/detail/webserver_impl.hpp httpserver/detail/http_request_impl.hpp httpserver/detail/route_entry.hpp httpserver/detail/lambda_resource.hpp httpserver/detail/radix_tree.hpp httpserver/detail/route_cache.hpp gettext.h
27-
nobase_include_HEADERS = httpserver.hpp httpserver/body_kind.hpp httpserver/constants.hpp httpserver/create_webserver.hpp httpserver/create_test_request.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/feature_unavailable.hpp httpserver/iovec_entry.hpp httpserver/http_arg_value.hpp httpserver/http_method.hpp
28-
29-
if HAVE_WEBSOCKET
30-
libhttpserver_la_SOURCES += websocket_handler.cpp
31-
nobase_include_HEADERS += httpserver/websocket_handler.hpp
32-
endif
33+
nobase_include_HEADERS = httpserver.hpp httpserver/body_kind.hpp httpserver/constants.hpp httpserver/create_webserver.hpp httpserver/create_test_request.hpp httpserver/webserver.hpp httpserver/websocket_handler.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/feature_unavailable.hpp httpserver/iovec_entry.hpp httpserver/http_arg_value.hpp httpserver/http_method.hpp
3334

3435
AM_CXXFLAGS += -fPIC -Wall
3536

src/create_webserver.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,31 @@
3939

4040
namespace httpserver {
4141

42+
// TASK-034 / PRD-FLG-REQ-001: defined here (not as a default member
43+
// initializer in the header) so the public header carries no
44+
// #ifdef HAVE_BAUTH. The library was compiled with the right
45+
// HAVE_BAUTH state; the consumer TU's HAVE_BAUTH is irrelevant.
46+
bool create_webserver::basic_auth_default() noexcept {
47+
#ifdef HAVE_BAUTH
48+
return true;
49+
#else
50+
return false;
51+
#endif
52+
}
53+
54+
// security-reviewer-iter1-1 / PRD-FLG-REQ-001: same pattern as
55+
// basic_auth_default(). Returns true on HAVE_DAUTH-on builds
56+
// (preserving historical behaviour) and false on HAVE_DAUTH-off builds
57+
// so that an unmodified builder doesn't trip the feature_unavailable
58+
// throw added to webserver::webserver() for HAVE_DAUTH-off builds.
59+
bool create_webserver::digest_auth_default() noexcept {
60+
#ifdef HAVE_DAUTH
61+
return true;
62+
#else
63+
return false;
64+
#endif
65+
}
66+
4267
create_webserver& create_webserver::bind_address(const std::string& ip) {
4368
_bind_address_storage = std::make_shared<struct sockaddr_storage>();
4469
std::memset(_bind_address_storage.get(), 0, sizeof(struct sockaddr_storage));

src/http_request.cpp

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -744,13 +744,21 @@ const std::string http_request::get_path_piece(int index) const {
744744
return EMPTY;
745745
}
746746

747-
#ifdef HAVE_DAUTH
748747
http::http_utils::digest_auth_result http_request::check_digest_auth(
749748
const std::string& realm,
750749
const std::string& password,
751750
unsigned int nonce_timeout,
752751
uint32_t max_nc,
753752
http::http_utils::digest_algorithm algo) const {
753+
#ifdef HAVE_DAUTH
754+
// CWE-476 guard: test-request path sets connection_ to nullptr.
755+
// Passing nullptr to MHD_digest_auth_check3 is undefined behaviour;
756+
// return the same WRONG_HEADER sentinel used on the HAVE_DAUTH-off
757+
// path and by get_digested_user() when connection_ is null.
758+
if (impl_->connection_ == nullptr) {
759+
return http::http_utils::digest_auth_result::WRONG_HEADER;
760+
}
761+
754762
std::string_view digested_user = get_digested_user();
755763

756764
enum MHD_DigestAuthResult result = MHD_digest_auth_check3(
@@ -764,6 +772,18 @@ http::http_utils::digest_auth_result http_request::check_digest_auth(
764772
static_cast<MHD_DigestAuthMultiAlgo3>(algo));
765773

766774
return static_cast<http::http_utils::digest_auth_result>(result);
775+
#else
776+
// TASK-034 §7 sentinel: DAUTH disabled at build time -> the
777+
// call is documented to "return a sentinel result". WRONG_HEADER
778+
// is the most explicit "this request was never authenticated"
779+
// terminal value of the digest_auth_result enum.
780+
(void)realm;
781+
(void)password;
782+
(void)nonce_timeout;
783+
(void)max_nc;
784+
(void)algo;
785+
return http::http_utils::digest_auth_result::WRONG_HEADER;
786+
#endif
767787
}
768788

769789
http::http_utils::digest_auth_result http_request::check_digest_auth_digest(
@@ -773,6 +793,12 @@ http::http_utils::digest_auth_result http_request::check_digest_auth_digest(
773793
unsigned int nonce_timeout,
774794
uint32_t max_nc,
775795
http::http_utils::digest_algorithm algo) const {
796+
#ifdef HAVE_DAUTH
797+
// CWE-476 guard: same null-connection guard as check_digest_auth.
798+
if (impl_->connection_ == nullptr) {
799+
return http::http_utils::digest_auth_result::WRONG_HEADER;
800+
}
801+
776802
std::string_view digested_user = get_digested_user();
777803

778804
enum MHD_DigestAuthResult result = MHD_digest_auth_check_digest3(
@@ -787,8 +813,16 @@ http::http_utils::digest_auth_result http_request::check_digest_auth_digest(
787813
static_cast<MHD_DigestAuthMultiAlgo3>(algo));
788814

789815
return static_cast<http::http_utils::digest_auth_result>(result);
816+
#else
817+
(void)realm;
818+
(void)userdigest;
819+
(void)userdigest_size;
820+
(void)nonce_timeout;
821+
(void)max_nc;
822+
(void)algo;
823+
return http::http_utils::digest_auth_result::WRONG_HEADER;
824+
#endif
790825
}
791-
#endif // HAVE_DAUTH
792826

793827
std::string_view http_request::get_header(std::string_view key) const {
794828
return impl_->get_connection_value(key, MHD_HEADER_KIND);
@@ -886,26 +920,33 @@ std::string_view http_request::get_querystring() const noexcept {
886920
return impl_->querystring;
887921
}
888922

889-
#ifdef HAVE_BAUTH
890923
std::string_view http_request::get_user() const {
924+
#ifdef HAVE_BAUTH
891925
if (!impl_->username.empty()) {
892926
return impl_->username;
893927
}
894928
impl_->fetch_user_pass();
895929
return impl_->username;
930+
#else
931+
// TASK-034 §7 sentinel: BAUTH disabled at build time -> empty view.
932+
return std::string_view{};
933+
#endif
896934
}
897935

898936
std::string_view http_request::get_pass() const {
937+
#ifdef HAVE_BAUTH
899938
if (!impl_->password.empty()) {
900939
return impl_->password;
901940
}
902941
impl_->fetch_user_pass();
903942
return impl_->password;
943+
#else
944+
return std::string_view{};
945+
#endif
904946
}
905-
#endif // HAVE_BAUTH
906947

907-
#ifdef HAVE_DAUTH
908948
std::string_view http_request::get_digested_user() const {
949+
#ifdef HAVE_DAUTH
909950
if (!impl_->digested_user.empty()) {
910951
return impl_->digested_user;
911952
}
@@ -926,8 +967,11 @@ std::string_view http_request::get_digested_user() const {
926967
}
927968

928969
return impl_->digested_user;
970+
#else
971+
// TASK-034 §7 sentinel: DAUTH disabled at build time -> empty view.
972+
return std::string_view{};
973+
#endif
929974
}
930-
#endif // HAVE_DAUTH
931975

932976
// ----------------------------------------------------------------------
933977
// TASK-019: high-level GnuTLS accessors. Public surface is unconditional
@@ -1104,9 +1148,10 @@ void http_request::set_file_cleanup_callback(file_cleanup_callback_ptr callback)
11041148

11051149
std::ostream& operator<< (std::ostream& os, const http_request& r) {
11061150
os << r.get_method() << " Request [";
1107-
#ifdef HAVE_BAUTH
1151+
// TASK-034: get_user/get_pass are unconditional; they return empty
1152+
// on HAVE_BAUTH-off builds, so the dump prints two empty quoted
1153+
// strings in that case (harmless).
11081154
os << "user:\"" << r.get_user() << "\" pass:\"" << r.get_pass() << "\"";
1109-
#endif // HAVE_BAUTH
11101155
os << "] path:\"" << r.get_path() << "\"" << std::endl;
11111156

11121157
http::dump_header_map(os, "Headers", r.get_headers());

src/httpserver.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
#include "httpserver/iovec_entry.hpp"
4040
#include "httpserver/file_info.hpp"
4141
#include "httpserver/webserver.hpp"
42-
#ifdef HAVE_WEBSOCKET
42+
// TASK-034: included unconditionally. websocket_handler.hpp is safe to
43+
// include in both HAVE_WEBSOCKET-on and HAVE_WEBSOCKET-off builds; the
44+
// member-function bodies in src/websocket_handler.cpp handle the
45+
// disabled-build sentinel behaviour (PRD-FLG-REQ-001).
4346
#include "httpserver/websocket_handler.hpp"
44-
#endif // HAVE_WEBSOCKET
4547

4648
#undef _HTTPSERVER_HPP_INSIDE_
4749

src/httpserver/create_test_request.hpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ class create_test_request {
8383
return *this;
8484
}
8585

86-
#ifdef HAVE_BAUTH
86+
// TASK-034: setters are unconditional. On HAVE_BAUTH-off /
87+
// HAVE_DAUTH-off builds the values are silently dropped on .build()
88+
// (matching the sentinel-empty contract of the corresponding
89+
// http_request accessors).
8790
create_test_request& user(const std::string& user) {
8891
_user = user;
8992
return *this;
@@ -93,14 +96,11 @@ class create_test_request {
9396
_pass = pass;
9497
return *this;
9598
}
96-
#endif // HAVE_BAUTH
9799

98-
#ifdef HAVE_DAUTH
99100
create_test_request& digested_user(const std::string& digested_user) {
100101
_digested_user = digested_user;
101102
return *this;
102103
}
103-
#endif // HAVE_DAUTH
104104

105105
create_test_request& requestor(const std::string& requestor) {
106106
_requestor = requestor;
@@ -112,12 +112,13 @@ class create_test_request {
112112
return *this;
113113
}
114114

115-
#ifdef HAVE_GNUTLS
115+
// TASK-034: setter is unconditional. On HAVE_GNUTLS-off builds the
116+
// value is silently dropped (has_tls_session() returns false
117+
// regardless — TASK-019 sentinel contract).
116118
create_test_request& tls_enabled(bool enabled = true) {
117119
_tls_enabled = enabled;
118120
return *this;
119121
}
120-
#endif // HAVE_GNUTLS
121122

122123
http_request build();
123124

@@ -131,18 +132,16 @@ class create_test_request {
131132
http::header_map _cookies;
132133
std::map<std::string, std::vector<std::string>, http::arg_comparator> _args;
133134
std::string _querystring;
134-
#ifdef HAVE_BAUTH
135+
// TASK-034: fields stored unconditionally. On HAVE_*-off builds the
136+
// values are populated by the setters but silently dropped during
137+
// .build() propagation, matching the sentinel-empty contract on
138+
// the http_request accessor side.
135139
std::string _user;
136140
std::string _pass;
137-
#endif // HAVE_BAUTH
138-
#ifdef HAVE_DAUTH
139141
std::string _digested_user;
140-
#endif // HAVE_DAUTH
141142
std::string _requestor = "127.0.0.1";
142143
uint16_t _requestor_port = 0;
143-
#ifdef HAVE_GNUTLS
144144
bool _tls_enabled = false;
145-
#endif // HAVE_GNUTLS
146145
};
147146

148147
} // namespace httpserver

src/httpserver/create_webserver.hpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,11 @@ class create_webserver {
121121
create_webserver& nonce_nc_size(int v) { check_non_negative("nonce_nc_size", v); _nonce_nc_size = v; return *this; }
122122
create_webserver& default_policy(const http::http_utils::policy_T& v) { _default_policy = v; return *this; }
123123

124-
#ifdef HAVE_BAUTH
124+
// TASK-034 / PRD-FLG-REQ-001: setter is unconditional. The actual
125+
// validation lives in webserver(const create_webserver&), which
126+
// throws feature_unavailable when this is set to true on a
127+
// HAVE_BAUTH-off build.
125128
create_webserver& basic_auth(bool enable = true) { _basic_auth_enabled = enable; return *this; }
126-
#endif // HAVE_BAUTH
127129
create_webserver& digest_auth(bool enable = true) { _digest_auth_enabled = enable; return *this; }
128130
create_webserver& deferred(bool enable = true) { _deferred_enabled = enable; return *this; }
129131
create_webserver& regex_checking(bool enable = true) { _regex_checking = enable; return *this; }
@@ -209,10 +211,18 @@ class create_webserver {
209211
std::string _digest_auth_random = "";
210212
int _nonce_nc_size = 0;
211213
http::http_utils::policy_T _default_policy = http::http_utils::ACCEPT;
212-
#ifdef HAVE_BAUTH
213-
bool _basic_auth_enabled = true;
214-
#endif // HAVE_BAUTH
215-
bool _digest_auth_enabled = true;
214+
// TASK-034: stored unconditionally. The default values are computed
215+
// by basic_auth_default() and digest_auth_default() in
216+
// create_webserver.cpp, where the HAVE_BAUTH / HAVE_DAUTH build
217+
// flags are reachable — that keeps the public header free of
218+
// build-flag preprocessor gates (PRD-FLG-REQ-001) while preserving
219+
// the historical defaults (true on the respective auth-on builds;
220+
// false on auth-off builds so an unmodified builder doesn't trip
221+
// the feature_unavailable throw at construction time).
222+
static bool basic_auth_default() noexcept;
223+
static bool digest_auth_default() noexcept;
224+
bool _basic_auth_enabled = basic_auth_default();
225+
bool _digest_auth_enabled = digest_auth_default();
216226
bool _regex_checking = true;
217227
bool _ban_system_enabled = true;
218228
bool _post_process_enabled = true;

0 commit comments

Comments
 (0)