You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Merge TASK-064: structured cookie API on v2 http_response/http_request
Replaces the string-blob cookie surface with a structured `http::cookie`
type per RFC 6265, including render-time guards on name/value, parser
hardening, and accompanying tests. Marks TASK-064 Completed and records
remaining review findings under specs/unworked_review_issues.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-`get_header(key)`, `get_cookie(key)`, `get_footer(key)`, `get_arg(key)`, `get_arg_flat(key)` returning `string_view` (empty on miss; never insert)
12
+
-`get_cookies_parsed()` (TASK-064) returning `const std::vector<httpserver::cookie>&`: structured RFC 6265 §5.4 parse of the request's `Cookie:` header. Each entry carries `name` and `value` (request cookies have no attributes per the spec). Backed by a per-request lazy cache that follows the TASK-016/TASK-017 arena pattern: the first call parses and populates the vector; subsequent calls are O(1) and reuse the same buffer (`reference_stable_across_calls`, `second_call_does_not_reallocate` pinned by `http_request_cookies_parsed_test`).
12
13
-`get_user()`, `get_pass()`, `get_digested_user()` returning `string_view` (empty when basic/digest auth disabled at build)
Copy file name to clipboardExpand all lines: specs/architecture/04-components/http-response.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,6 +24,8 @@ The body subclasses (`detail::string_body`, `file_body`, `iovec_body`, `pipe_bod
24
24
- Fluent setters: `with_header`, `with_footer`, `with_cookie`, `with_status` — each has two ref-qualified overloads: `& → http_response&` (mutate-in-place on an lvalue) and `&& → http_response&&` (return the object by rvalue-reference for zero-copy rvalue factory chains, e.g. `http_response::string("body").with_header("X-Foo", "bar").with_status(201)`).
25
25
-`const` accessors: `get_header`, `get_footer`, `get_cookie` returning `string_view` (empty on miss; do not insert).
**Cookie surface (TASK-064):** the v2.0 cookie API is structured. A new public header `<httpserver/cookie.hpp>` declares `httpserver::cookie` (a copyable + movable value type) with fluent `with_name`, `with_value`, `with_domain`, `with_path`, `with_expires`, `with_max_age`, `with_secure`, `with_http_only`, `with_same_site` setters, plus an `enum class same_site_mode { unset, strict, lax, none }`. `http_response::with_cookie(cookie)` appends to a `std::vector<cookie>` carried directly on the response (separate field from the legacy `cookies_` map). The dispatch path (`detail/webserver_request.cpp::decorate_mhd_response`) renders one `Set-Cookie` header per entry via `cookie::to_set_cookie_header()`, which produces an RFC 6265 §4.1 well-formed serialization with fixed attribute ordering (`name=value; Expires=...; Max-Age=...; Domain=...; Path=...; Secure; HttpOnly; SameSite=...`) and auto-coerces `Secure` when `SameSite=None` is set. `cookie::parse_cookie_header(string_view)` is the matching RFC 6265 §5.4 request-side parser (byte-transparent, skips entries without `=`, strips outer DQUOTE pairs). The legacy `with_cookie(string, string)`, `get_cookie(...)`, and `get_cookies()` accessors are `[[deprecated]]` and will be removed in v2.1; they keep working through a thin shim that forwards through the structured path and mirrors name/value into the legacy `cookies_` map for source-compatibility with v1 callers.
27
29
-`kind()` returning `body_kind`.
28
30
- The virtuals `get_raw_response`, `decorate_response`, `enqueue_response` are removed from the public API (PRD-HDR-REQ-005). The MHD response object is constructed inside the library's dispatch path from the `http_response` value's `body_->materialize()` (or equivalent internal API on `detail::body`).
Copy file name to clipboardExpand all lines: specs/product_specs.md
+41Lines changed: 41 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -184,6 +184,47 @@ The response hierarchy has eight subclasses (`string_response`, `file_response`,
184
184
185
185
---
186
186
187
+
### 3.5.1 Structured Cookie Type (API-CKY)
188
+
189
+
**Problem / outcome**
190
+
The v1 `with_cookie(string, string)` / `get_cookie(string)` API stored cookies as opaque string blobs, making RFC 6265 attribute fields (Secure, HttpOnly, SameSite, Path, Domain, Expires, Max-Age) inaccessible to callers and allowing header-injection via unguarded semicolons in values. After TASK-064 the library exposes `httpserver::cookie`, a structured value type, alongside `http_response::with_cookie(cookie)` for responses and `http_request::get_cookies_parsed()` for requests. The string-blob path is retained as a `[[deprecated]]` shim for one transitional v2.0 release.
191
+
192
+
**In scope**
193
+
-`httpserver::cookie` value type with fluent `with_name`, `with_value`, `with_domain`, `with_path`, `with_expires`, `with_max_age`, `with_secure`, `with_http_only`, `with_same_site` setters.
194
+
-`enum class same_site_mode { unset, strict, lax, none }`.
195
+
-`cookie::to_set_cookie_header()` renders a fully RFC 6265 §4.1 conformant `Set-Cookie` value.
196
+
-`cookie::parse_cookie_header(string_view)` parses a `Cookie:` request header into a `std::vector<cookie>`, byte-transparent and lenient.
-`http_request::get_cookies_parsed()` returns `const std::vector<cookie>&`, parsed once and cached.
199
+
- Injection guard: CR, LF, NUL, and `;` are rejected in name, value, domain, and path at setter time (CWE-113).
200
+
- SameSite=None auto-coerces `Secure` at render time (browser requirement per draft-west-cookie-incrementalism).
201
+
- Transitional policy: legacy `get_cookie(string)`, `get_cookies()`, and `with_cookie(string, string)` are `[[deprecated]]` and compile with a diagnostic in v2.0.
202
+
203
+
**Out of scope**
204
+
- Removing the deprecated string-blob path before v2.1.
-`PRD-CKY-REQ-001` When a user constructs a `httpserver::cookie` then the system shall provide fluent `with_*` setters that return `cookie&` on lvalue and `cookie&&` on rvalue, mirroring the `http_response` fluent style.
209
+
-`PRD-CKY-REQ-002` When a user calls `with_name`, `with_value`, `with_domain`, or `with_path` with a value containing CR, LF, NUL, or `;` then the system shall throw `std::invalid_argument`.
210
+
-`PRD-CKY-REQ-003` When a user calls `with_name` with a value containing `=` or ASCII whitespace then the system shall throw `std::invalid_argument`.
211
+
-`PRD-CKY-REQ-004` When a user calls `cookie::to_set_cookie_header()` then the system shall produce a string conforming to RFC 6265 §4.1 with attributes in the canonical order: Expires, Max-Age, Domain, Path, Secure, HttpOnly, SameSite.
212
+
-`PRD-CKY-REQ-005` When a user calls `cookie::to_set_cookie_header()` with `same_site_mode::none` set and `with_secure(false)` then the system shall include `Secure` in the output (browser requirement).
213
+
-`PRD-CKY-REQ-006` When a user calls `cookie::parse_cookie_header(s)` then the system shall return a `std::vector<cookie>` parsed from the `Cookie:` wire format, stripping outer DQUOTE pairs from values and tolerating arbitrary ASCII whitespace around tokens.
214
+
-`PRD-CKY-REQ-007` When a user calls `http_response::with_cookie(cookie)` then the system shall append the structured cookie to the response's cookie list; `get_cookies_parsed()` shall reflect it without copying the cookie object.
215
+
-`PRD-CKY-REQ-008` When a user calls `http_request::get_cookies_parsed()` then the system shall return a `const std::vector<cookie>&` backed by a parse-once cached representation; subsequent calls shall not allocate.
216
+
-`PRD-CKY-REQ-009` When v2.0 ships then `http_response::with_cookie(string, string)`, `http_response::get_cookie(string_view)`, and `http_response::get_cookies()` shall be `[[deprecated]]` and shall not be removed until v2.1.
217
+
-`PRD-CKY-REQ-010` When a user calls the legacy `with_cookie(string, string)` shim with a name or value that violates RFC 6265 cookie-name or cookie-value rules then the system shall throw `std::invalid_argument` before mutating any internal state.
218
+
219
+
**Acceptance criteria**
220
+
-`http_response::string("body").with_cookie(cookie{}.with_name("sid").with_secure(true).with_same_site(same_site_mode::strict))` compiles and produces a single well-formed `Set-Cookie` value.
221
+
-`http_request::get_cookies_parsed()` returns `const std::vector<cookie>&`; pointer identity is stable across unrelated mutations.
Copy file name to clipboardExpand all lines: specs/tasks/M7-v2-cleanup/TASK-064.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,11 +8,11 @@
8
8
Replace the string-blob cookie surface on `http_response` with a structured `httpserver::cookie` value type carrying `name`, `value`, `domain`, `path`, `expires`, `max_age`, `secure`, `http_only`, `same_site`. The follow-up was explicitly deferred at `src/httpserver/http_response.hpp:304-313`.
9
9
10
10
**Action Items:**
11
-
-[] Design the `httpserver::cookie` value type in a new public header `src/httpserver/cookie.hpp`. Default-construct empty; provide fluent `with_*` setters mirroring `http_response`'s style. Include enum `same_site_mode { unset, strict, lax, none }`.
12
-
-[] Add `http_response::with_cookie(cookie)` and `http_response::with_cookie(std::string name, std::string value)` overloads. Internally render to the `Set-Cookie` header per RFC 6265 §4.1.
13
-
-[] Provide `http_request::get_cookies()` returning a structured view (parsed once, cached on the request impl per TASK-016 arena pattern).
14
-
-[] Document migration: legacy string-blob path remains as a `[[deprecated]]` thin shim for one transitional release.
15
-
-[] Add a unit test pinning round-trip parsing/rendering against RFC 6265 examples.
11
+
-[x] Design the `httpserver::cookie` value type in a new public header `src/httpserver/cookie.hpp`. Default-construct empty; provide fluent `with_*` setters mirroring `http_response`'s style. Include enum `same_site_mode { unset, strict, lax, none }`.
12
+
-[x] Add `http_response::with_cookie(cookie)` and `http_response::with_cookie(std::string name, std::string value)` overloads. Internally render to the `Set-Cookie` header per RFC 6265 §4.1.
13
+
-[x] Provide `http_request::get_cookies()` returning a structured view (parsed once, cached on the request impl per TASK-016 arena pattern).
14
+
-[x] Document migration: legacy string-blob path remains as a `[[deprecated]]` thin shim for one transitional release.
15
+
-[x] Add a unit test pinning round-trip parsing/rendering against RFC 6265 examples.
16
16
17
17
**Dependencies:**
18
18
- Blocked by: TASK-016 (arena), TASK-009 (http_response value type)
@@ -29,4 +29,4 @@ Replace the string-blob cookie surface on `http_response` with a structured `htt
29
29
**Related Requirements:** PRD-RSP-REQ-004 (fluent return), PRD §2 API minimalism
0 commit comments