Commit 2172190
TASK-064: structured cookie value type for v2 http_response/http_request
Replaces the v1 string-blob `http_response::with_cookie(name, value)`
surface with a structured `httpserver::cookie` value type carrying
`name`, `value`, `domain`, `path`, `expires`, `max_age`, `secure`,
`http_only`, and `same_site` attributes. Each cookie renders to a single
RFC 6265 §4.1-compliant `Set-Cookie` header on the wire via
`cookie::to_set_cookie_header()`. A paired RFC 6265 §5.4 parser exposes
`cookie::parse_cookie_header` so request `Cookie:` headers round-trip
through the same code path.
Why now:
- The follow-up was explicitly deferred at the v1 `with_cookie(string,
string)` site (`src/httpserver/http_response.hpp`).
- The v1 path renders the value verbatim into `Set-Cookie`, so a `;` in
the value silently injects attributes (attribute-injection / CWE-113).
v2 enforces the RFC ABNF via setter validation and a single render
source.
- PRD-RSP-REQ-004 (fluent return) and PRD §2 API minimalism call for
typed values over string blobs on the response surface.
What's new (public surface):
- `src/httpserver/cookie.hpp` (installed, umbrella-included): value type
with fluent `&` / `&&` setter pairs mirroring `http_response`'s style;
`enum class same_site_mode { unset, strict, lax, none }`;
`to_set_cookie_header()` renderer; static `parse_cookie_header()`
parser. Setters reject CR/LF/NUL / `;` / `=` / whitespace per RFC 6265
§4.1.1 ABNF.
- `http_response::with_cookie(cookie)` lvalue + rvalue overloads;
`http_response::get_cookies_parsed()` returning
`const std::vector<cookie>&`.
- `http_request::get_cookies_parsed()` returning
`const std::vector<cookie>&`; lazily built from the request's
`Cookie:` header via `parse_cookie_header`, cached on the per-request
impl following the TASK-016/017 arena pattern.
Wire rendering (the headline correctness fix):
- `decorate_mhd_response` now reads from
`resp.get_cookies_parsed()` and emits one `Set-Cookie` header per
entry via `cookie::to_set_cookie_header()`. Attributes (Domain, Path,
Expires, Max-Age, Secure, HttpOnly, SameSite) propagate to the wire.
- The legacy `cookies_` name->value mirror map survives only to back the
deprecated `get_cookie(name)` / `get_cookies()` accessors; it is no
longer a render source.
Deprecation / migration (transitional, one release):
- `http_response::with_cookie(string, string)` and the legacy
`get_cookies()` / `get_cookie(string_view)` accessors are
`[[deprecated]]`. The legacy `with_cookie` forwards through the
structured path so wire output is unchanged for unmodified callers.
- v2.1 removes the deprecated string-blob path entirely. The deprecation
message points callers at the structured overload.
Tests (TDD, all passing in isolation):
- `cookie_header_sentinel_test.cpp`: class shape, default state, enum,
copy/move-constructibility.
- `cookie_render_test.cpp`: 43 tests / 82 checks covering fluent-setter
ref-qualifier shape, setter validation (CWE-113), full RFC 6265 §4.1
rendering (attribute ordering, Max-Age zero/negative,
IMF-fixdate using the canonical §4.1 example 784111777,
SameSite=None auto-Secure coercion, byte-transparency, empty-name
throw), §5.4 parsing (DQUOTE stripping, malformed-skip, case-preserving,
no percent-decode), and the AC round-trip pin.
- `http_response_cookie_wire_test.cpp`: `with_cookie(cookie)` integration
on http_response, get_cookies_parsed shape + lifetime, structured
cookies survive move ctor, legacy/structured interop, no double-emit
when mixing both paths.
- `http_request_cookies_parsed_test.cpp`: shape, lifetime, cache
stability across calls and across unrelated getters.
- `cookie_deprecation_sentinel_test.cpp`: legacy path still compiles
(under `#pragma GCC diagnostic ignored "-Wdeprecated-declarations"`),
legacy return types unchanged.
- Existing `http_response_test.cpp`, `http_response_sbo_test.cpp`,
`http_response_move_sanitizer_test.cpp` wrap their legacy
`with_cookie(string, string)` calls in a deprecation-warning
suppression pragma; runtime behaviour unchanged.
Architecture docs updated under `specs/architecture/04-components/`
(http-response.md, http-request.md). RELEASE_NOTES.md updated with the
new public surface, the deprecation timeline, and the wire-render shift.
Acceptance:
- `http_response::string("...").with_cookie(cookie{}.with_name("sid")
.with_secure(true).with_same_site(same_site_mode::strict))` compiles
and renders to `sid=; Secure; SameSite=Strict` on the wire.
- `http_request::get_cookies_parsed()` returns
`const std::vector<cookie>&`; second call O(1) and zero-allocating.
- RFC 6265 §4.1 canonical example round-trips byte-exact.
- Deprecated string-blob path still compiles with a `[[deprecated]]`
warning; `request_with_cookie` integ test still passes end-to-end.
Verification:
- All 5 new cookie test programs PASS (sentinel + render + wire + parsed
+ deprecation): 73 tests / 138 checks total.
- Unit-test suite for http_response/http_request all PASS in isolation.
- Clean build (no compile warnings).
- `make check` integ-test FAILures on this host are pre-existing CURL
error-7 (couldn't-connect) flake from port exhaustion on rapid
sequential daemon-start tests — unrelated to TASK-064; the cookie
integration test `request_with_cookie` in basic.cpp passes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>1 parent 5e0abd5 commit 2172190
24 files changed
Lines changed: 1828 additions & 15 deletions
File tree
- specs
- architecture/04-components
- tasks/M7-v2-cleanup
- src
- detail
- httpserver
- detail
- test
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
231 | 231 | | |
232 | 232 | | |
233 | 233 | | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
234 | 252 | | |
235 | 253 | | |
236 | 254 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
27 | 29 | | |
28 | 30 | | |
29 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
| 32 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
0 commit comments