1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! # kekse
//!
//! A strict, dependency-light cookie codec. It reads — and writes — a request
//! `Cookie:` header through a [`CookieJar`] of [`Cookie`]s (over the lower-level
//! [`parse_pairs`] iterators), builds and parses response `Set-Cookie:` values through the
//! [`SetCookie`] type, and converts one straight into an `http` `HeaderValue` —
//! all on the RFC 6265 §4.1.1 grammar. It carries no cookie *store* (no
//! persistence, eviction, or domain/path send-matching) and no signing or
//! encryption, but it does parse and render dates: a lifetime is `Max-Age`
//! seconds (`u64`) or an `Expires` timestamp (an `OffsetDateTime`). It is
//! designed not to panic on untrusted input.
//!
//! ## Three types, two headers
//!
//! A [`Cookie`] is the request `Cookie:` cookie — a `name=value` kernel (plus its
//! wire [`ValueEncoding`]) with no attributes, because a `Cookie:` header carries
//! only pairs. A [`SetCookie`] is the response `Set-Cookie:` cookie — a [`Cookie`]
//! kernel plus [`CookieAttributes`] (`HttpOnly`, `Secure`, `SameSite`, `Path`,
//! `Domain`, `Expires`, `Max-Age`). A `Set-Cookie` line is fully observed, so the
//! flags are
//! plain `bool` — whether an attribute is *known* is answered by which type you
//! hold, not by an `Option`.
//!
//! Set attributes with the fluent verbs — the valueless flags
//! [`secure`](SetCookie::secure) / [`http_only`](SetCookie::http_only) are
//! nullary (calling adds the attribute), the rest take a value
//! ([`same_site`](SetCookie::same_site), [`path`](SetCookie::path), …) — and read
//! them back as fields through [`attributes`](SetCookie::attributes)
//! (`sc.attributes().secure`). The same verbs build a [`CookieAttributes`]
//! standalone, so a hardened policy can be defined once and reused across cookies.
//!
//! Completing a request [`Cookie`] into a [`SetCookie`] is the deliberate, typed
//! transform [`Cookie::into_set_cookie`] (default attributes) or
//! [`Cookie::with_attributes`] (a prebuilt set); [`SetCookie::into_cookie`] /
//! [`SetCookie::cookie`] demote back to the kernel. Render the request form with
//! [`Cookie::to_request_pair`] and the response form with
//! [`SetCookie::to_set_cookie`] or `HeaderValue::try_from` (the managed encodings
//! are always valid header bytes; only [`Raw`](ValueEncoding::Raw) can fail). A
//! [`CookieJar`] is the in-order, typed view of a request `Cookie:` header
//! ([`get`](CookieJar::get) / [`get_all`](CookieJar::get_all) / iterate); it is
//! also writable — [`add`](CookieJar::add) / [`replace`](CookieJar::replace) /
//! [`remove`](CookieJar::remove), then render the whole header back with
//! [`to_header_value`](CookieJar::to_header_value), re-encoded canonically. A
//! parsed-and-rebuildable view of kernels, not a stateful store.
//!
//! ## Encoding a value
//!
//! RFC 6265 lets a *cookie-value* carry only "cookie-octets"
//! (`%x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E`). Anything else — a space, a
//! `;`, a `"`, a control byte, any non-ASCII — has to be escaped to travel on
//! the wire. [`Cookie::with_encoding`] (and [`SetCookie::with_encoding`]) pick
//! how, via [`ValueEncoding`]:
//!
//! * [`Auto`](ValueEncoding::Auto) — emits the value bare when it is already
//! cookie-octets, **wraps it in quotes** when it needs to carry whitespace (so
//! `a b` rides as `"a b"`, not `a%20b`), and percent-encodes everything else
//! losslessly. "Quotes where necessary."
//! * [`Percent`](ValueEncoding::Percent) (default) — always percent-encode,
//! never quote. The most compatible form, understood by every cookie parser;
//! the sane default unless you choose otherwise.
//! * [`Quoted`](ValueEncoding::Quoted) — always wrap in quotes (percent-encoding
//! inside any byte the bare quoted form cannot carry).
//! * [`Raw`](ValueEncoding::Raw) — emit verbatim. The escape hatch for uncommon
//! but deliberate shapes; the caller owns wire-correctness.
//!
//! Every managed encoding is lossless and unambiguous: `%` always self-encodes
//! to `%25`, and `"`/`\` inside a quoted value become `%22`/`%5C`, so the
//! wrapping quotes can never be faked and no backslash-escaping is needed.
//!
//! ## Parsing a header
//!
//! [`parse_pairs`] is the lenient, general reader — the inverse of every
//! [`ValueEncoding`] above: it strips one wrapping quote pair, accepts raw
//! whitespace in the value, and percent-decodes. [`parse_pairs_strict`] is its
//! security-grade sibling: it accepts *only* cookie-octets — whitespace and
//! every other non-octet are refused — which is what a session-cookie read
//! should use. Both are fail-soft (a malformed pair is skipped, never aborting
//! the header, so attacker-appended junk can never evict a later valid cookie)
//! and both refuse the injection-dangerous bytes (`;`, CR, LF, NUL, other
//! controls, raw non-ASCII) in every mode — the lenient/strict difference is
//! only whether raw whitespace is tolerated.
//!
//! Both readers also come as byte-level twins, [`parse_pairs_bytes`] /
//! [`parse_pairs_bytes_strict`] (and [`CookieJar::parse_bytes`] /
//! [`CookieJar::parse_bytes_strict`]), for callers holding raw header bytes: an
//! `http` `HeaderValue` may legally carry obs-text (`>= 0x80`) that `to_str()`
//! refuses *wholesale*. The bytes readers accept nothing extra — raw non-ASCII
//! stays outside the grammar — but they keep fail-soft **per pair**: the pair
//! carrying a stray byte is refused individually and its well-formed neighbors
//! survive, instead of the whole header dying at a UTF-8 boundary.
//!
//! Fail-soft is the default, not the ceiling: every reader has a **reporting**
//! twin running the same pipeline, so a skip is data instead of silence. The
//! [`try_parse_pairs`] family yields `Result` items (`.collect::<Result<Vec<_>, _>>()`
//! is fail-hard for free), [`CookieJar::parse_reported`] and its twins return a
//! [`Reported`] — the jar plus every refused pair as a [`PairIssue`] — and
//! [`SetCookie::try_parse`] / [`SetCookie::try_parse_strict`] report what the
//! attribute loop dropped as [`SetCookieIssue`]s (an ignored unknown attribute,
//! a duplicate, a malformed known value). Strictness decides which issues are
//! *fatal*; the report lets a caller be stricter than strict — gate on
//! [`Reported::is_clean`] and nothing is ever dropped silently.
//!
//! On the response side, [`SetCookie::parse`] reads one `Set-Cookie` header value
//! back into a [`SetCookie`] (RFC 6265 §5.2, attributes matched
//! case-insensitively). Per §5.2 an **unrecognised attribute is ignored** and the
//! cookie kept (so a newer attribute like `Partitioned` never costs the cookie);
//! [`SetCookie::parse_strict`] rejects on an unknown attribute instead. `Expires`
//! is parsed into an `OffsetDateTime` by the `rfc_6265` crate — the lenient
//! [`parse`](SetCookie::parse) accepts the RFC 6265 §5.1.1 cookie-date, the strict
//! [`parse_strict`](SetCookie::parse_strict) only the RFC 7231 IMF-fixdate.
//!
//! ## An axum extractor (optional)
//!
//! With the `axum` feature, `CookieJarBuf` is a `FromRequestParts` extractor: it
//! owns the request `Cookie:` header and lends a borrowed [`CookieJar`] through
//! its `jar()` (lenient) / `jar_strict()` (strict) views, so the *handler* picks
//! the read mode. Extraction is infallible — a missing or malformed header just
//! yields an empty jar — and it pulls in only `axum-core`, not the whole
//! framework. A handler that would rather refuse a mangled header than serve a
//! partial jar opts out per read: `cookies.try_jar_strict()?` turns any
//! malformed pair into a ready-made `400 Bad Request` (`BadCookieHeader`), and
//! the `jar_reported()` views hand back the jar together with the issue list.
//!
//! ## Hardening (optional)
//!
//! By default kekse is a pure codec that stores whatever `Domain` the wire carries. The opt-in
//! `hardened` feature (= `psl` + `idna`) makes it *enforce* policy on the `Domain` attribute.
//! Either sub-feature first requires LDH host-name syntax (after stripping the RFC 6265 §5.2.3
//! leading dot): a `Domain` like `ex_ample.com` or `a..b` that could never domain-match is refused
//! instead of stored as dead weight. On top of that, `psl` refuses a public-suffix value (`com`,
//! `co.uk`, …) — the supercookie defense — and `idna` refuses malformed punycode. Both pull extra
//! tables (the Public Suffix List / IDNA, via `rfc_6265`), so they are not in the default,
//! dependency-light build. Independently,
//! [`SetCookie::parse_strict`] rejects a `Set-Cookie` carrying a duplicate attribute.
//!
//! ## A single source of truth for the grammar
//!
//! Cookie *names* are RFC 6265 cookie-names (RFC 7230 tokens), and cookie-name / cookie-octet /
//! av-octet membership all come from the `rfc_6265` crate, where each predicate is a `const fn`
//! pinned by an exhaustive byte sweep. kekse's percent-encode set is tested to stay the exact
//! complement of [`is_cookie_octet`], so the writer and the reader can never drift.
//!
//! ## Module layout
//!
//! One concept per module — `grammar` (the value codec's percent-encode sets, on top of
//! `rfc_6265`'s byte classes), `wire` (the shared byte-level `name=value` segmentation both
//! readers run), `encoding` (the value codec), `same_site`, `cookie` (the request
//! [`Cookie`] kernel), `attributes` (the response [`CookieAttributes`]),
//! `set_cookie` (the response [`SetCookie`] = kernel + attributes, with its
//! `Set-Cookie` parse/serialize), `jar` (the request-`Cookie:` reader *and*
//! writer), and `report` (what fail-soft dropped, as data — [`Reported`] and the
//! issue types) — all re-exported flat from the crate root. With the `axum`
//! feature, an `axum` module adds the `CookieJarBuf` extractor.
pub use ;
pub use ;
pub use Cookie;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
/// The timestamp type used by the `Expires` attribute, re-exported from `rfc_6265` (itself the
/// `time` crate's `OffsetDateTime`) so callers can name it without depending on `time` directly.
pub use OffsetDateTime;