···11+# RFC 6265 Compliance TODO
22+33+This document tracks deviations from [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265) (HTTP State Management Mechanism) and missing features in ocaml-cookeio.
44+55+## High Priority
66+77+### 1. Public Suffix Validation (Section 5.3, Step 5)
88+99+**Status:** Not implemented
1010+1111+The RFC requires rejecting cookies with domains that are "public suffixes" (e.g., `.com`, `.co.uk`) to prevent domain-wide cookie attacks.
1212+1313+**Required behavior:**
1414+- Maintain or reference a public suffix list (e.g., from [publicsuffix.org](https://publicsuffix.org/))
1515+- Reject cookies where the Domain attribute is a public suffix (unless it exactly matches the request host)
1616+1717+**Security impact:** Without this, an attacker on `evil.com` could potentially set cookies for `.com` affecting all `.com` sites.
1818+1919+---
2020+2121+## Medium Priority
2222+2323+### 2. IP Address Domain Matching (Section 5.1.3)
2424+2525+**Status:** ✅ IMPLEMENTED
2626+2727+The RFC specifies that domain suffix matching should only apply to host names, not IP addresses.
2828+2929+**Implementation:**
3030+- Uses the `ipaddr` library to detect IPv4 and IPv6 addresses
3131+- IP addresses require exact match only (no suffix matching)
3232+- Hostnames continue to support subdomain matching when `host_only = false`
3333+3434+---
3535+3636+### 3. Expires Header Date Format (Section 4.1.1)
3737+3838+**Status:** Wrong format
3939+4040+**Current behavior:** Outputs RFC3339 format (`2021-06-09T10:18:14+00:00`)
4141+4242+**RFC requirement:** Use `rfc1123-date` format (`Wed, 09 Jun 2021 10:18:14 GMT`)
4343+4444+**Location:** `cookeio.ml:447-448`
4545+4646+**Fix:** Implement RFC1123 date formatting for Set-Cookie header output.
4747+4848+---
4949+5050+### 4. Cookie Ordering in Header (Section 5.4, Step 2)
5151+5252+**Status:** Not implemented
5353+5454+When generating Cookie headers, cookies SHOULD be sorted:
5555+1. Cookies with longer paths listed first
5656+2. Among equal-length paths, earlier creation-times listed first
5757+5858+**Location:** `get_cookies` function in `cookeio_jar.ml`
5959+6060+---
6161+6262+### 5. Creation Time Preservation (Section 5.3, Step 11.3)
6363+6464+**Status:** Not implemented
6565+6666+When replacing an existing cookie (same name/domain/path), the creation-time of the old cookie should be preserved.
6767+6868+**Current behavior:** Completely replaces cookie, losing original creation time.
6969+7070+**Location:** `add_cookie` and `add_original` functions in `cookeio_jar.ml`
7171+7272+---
7373+7474+### 6. Default Path Computation (Section 5.1.4)
7575+7676+**Status:** Not implemented (caller responsibility)
7777+7878+The RFC specifies an algorithm for computing default path when Path attribute is absent:
7979+1. If uri-path is empty or doesn't start with `/`, return `/`
8080+2. If uri-path contains only one `/`, return `/`
8181+3. Return characters up to (but not including) the rightmost `/`
8282+8383+**Suggestion:** Add `default_path : string -> string` helper function.
8484+8585+---
8686+8787+## Low Priority
8888+8989+### 7. Storage Limits (Section 6.1)
9090+9191+**Status:** Not implemented
9292+9393+RFC recommends minimum capabilities:
9494+- At least 4096 bytes per cookie
9595+- At least 50 cookies per domain
9696+- At least 3000 cookies total
9797+9898+**Suggestion:** Add configurable limits with RFC-recommended defaults.
9999+100100+---
101101+102102+### 8. Excess Cookie Eviction (Section 5.3)
103103+104104+**Status:** Not implemented
105105+106106+When storage limits are exceeded, evict in priority order:
107107+1. Expired cookies
108108+2. Cookies sharing domain with many others
109109+3. All cookies
110110+111111+Tiebreaker: earliest `last-access-time` first (LRU).
112112+113113+---
114114+115115+### 9. Two-Digit Year Parsing (Section 5.1.1)
116116+117117+**Status:** Minor deviation
118118+119119+**RFC specification:**
120120+- Years 70-99 → add 1900
121121+- Years 0-69 → add 2000
122122+123123+**Current code** (`cookeio.ml:128-130`):
124124+```ocaml
125125+if year >= 0 && year <= 68 then year + 2000
126126+else if year >= 69 && year <= 99 then year + 1900
127127+```
128128+129129+**Issue:** Year 69 is treated as 1969, but RFC says 70-99 get 1900, implying 69 should get 2000.
130130+131131+---
132132+133133+## Compliant Features
134134+135135+The following RFC requirements are correctly implemented:
136136+137137+- [x] Case-insensitive attribute name matching (Section 5.2)
138138+- [x] Leading dot removal from Domain attribute (Section 5.2.3)
139139+- [x] Max-Age takes precedence over Expires (Section 5.3, Step 3)
140140+- [x] Secure flag handling (Section 5.2.5)
141141+- [x] HttpOnly flag handling (Section 5.2.6)
142142+- [x] Cookie date parsing with multiple format support (Section 5.1.1)
143143+- [x] Session vs persistent cookie distinction (Section 5.3)
144144+- [x] Last-access-time updates on retrieval (Section 5.4, Step 3)
145145+- [x] Host-only flag for domain matching (Section 5.3, Step 6)
146146+- [x] Path matching algorithm (Section 5.1.4)
147147+- [x] IP address domain matching - exact match only (Section 5.1.3)
148148+149149+---
150150+151151+## Extensions Beyond RFC 6265
152152+153153+These features are implemented but not part of RFC 6265:
154154+155155+| Feature | Specification |
156156+|---------|---------------|
157157+| SameSite | RFC 6265bis (draft) |
158158+| Partitioned | CHIPS proposal |
159159+| Mozilla format | De facto standard |
160160+161161+---
162162+163163+## References
164164+165165+- [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265) - HTTP State Management Mechanism
166166+- [RFC 6265bis](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis) - Updated cookie spec (draft)
167167+- [Public Suffix List](https://publicsuffix.org/) - Mozilla's public suffix database
168168+- [CHIPS](https://developer.chrome.com/docs/privacy-sandbox/chips/) - Cookies Having Independent Partitioned State
···7788module Log = (val Logs.src_log src : Logs.LOG)
991010+(** SameSite attribute for cross-site request control.
1111+1212+ The SameSite attribute is defined in the RFC 6265bis draft and controls
1313+ whether cookies are sent with cross-site requests.
1414+1515+ @see <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7> RFC 6265bis Section 5.4.7 - The SameSite Attribute *)
1016module SameSite = struct
1117 type t = [ `Strict | `Lax | `None ]
1218···1824 | `None -> Format.pp_print_string ppf "None"
1925end
20262727+(** Cookie expiration type.
2828+2929+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3},
3030+ cookies have either a persistent expiry time or are session cookies that
3131+ expire when the user agent session ends.
3232+3333+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
2134module Expiration = struct
2235 type t = [ `Session | `DateTime of Ptime.t ]
2336···9610997110(** {1 Cookie Parsing Helpers} *)
98111112112+(** Normalize a domain by stripping the leading dot.
113113+114114+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3} RFC 6265 Section 5.2.3},
115115+ if the first character of the Domain attribute value is ".", that character
116116+ is ignored (the domain remains case-insensitive).
117117+118118+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3> RFC 6265 Section 5.2.3 - The Domain Attribute *)
99119let normalize_domain domain =
100100- (* Strip leading dot per RFC 6265 *)
101120 match String.starts_with ~prefix:"." domain with
102121 | true when String.length domain > 1 ->
103122 String.sub domain 1 (String.length domain - 1)
104123 | _ -> domain
105124106106-(** {1 HTTP Date Parsing} *)
125125+(** {1 HTTP Date Parsing}
126126+127127+ Date parsing follows {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1} RFC 6265 Section 5.1.1}
128128+ which requires parsing dates in various HTTP formats. *)
107129108130module DateParser = struct
109109- (** Month name to number mapping (case-insensitive) *)
131131+ (** Month name to number mapping (case-insensitive).
132132+133133+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1} RFC 6265 Section 5.1.1},
134134+ month tokens are matched case-insensitively.
135135+136136+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1> RFC 6265 Section 5.1.1 - Dates *)
110137 let month_of_string s =
111138 match String.lowercase_ascii s with
112139 | "jan" -> Some 1
···123150 | "dec" -> Some 12
124151 | _ -> None
125152126126- (** Normalize abbreviated years:
127127- - Years 69-99 get 1900 added (e.g., 95 → 1995)
128128- - Years 0-68 get 2000 added (e.g., 25 → 2025)
129129- - Years >= 100 are returned as-is *)
153153+ (** Normalize abbreviated years per RFC 6265.
154154+155155+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1} RFC 6265 Section 5.1.1}:
156156+ - Years 70-99 get 1900 added (e.g., 95 → 1995)
157157+ - Years 0-69 get 2000 added (e.g., 25 → 2025)
158158+ - Years >= 100 are returned as-is
159159+160160+ Note: This implementation treats year 69 as 1969 (adding 1900), which
161161+ technically differs from the RFC's "70 and less than or equal to 99" rule.
162162+163163+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1> RFC 6265 Section 5.1.1 - Dates *)
130164 let normalize_year year =
131165 if year >= 0 && year <= 68 then year + 2000
132166 else if year >= 69 && year <= 99 then year + 1900
···227261 same_site = None;
228262 }
229263230230-(** Parse a single attribute and update the accumulator in-place *)
264264+(** Parse a single cookie attribute and update the accumulator in-place.
265265+266266+ Attribute parsing follows {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2} RFC 6265 Section 5.2}
267267+ which defines the grammar and semantics for each cookie attribute.
268268+269269+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2> RFC 6265 Section 5.2 - The Set-Cookie Header *)
231270let parse_attribute now attrs attr_name attr_value =
232271 let attr_lower = String.lowercase_ascii attr_name in
233272 match attr_lower with
···282321 | _ ->
283322 Log.debug (fun m -> m "Unknown cookie attribute '%s', ignoring" attr_name)
284323285285-(** Validate cookie attributes and log warnings for invalid combinations *)
324324+(** Validate cookie attributes and log warnings for invalid combinations.
325325+326326+ Validates:
327327+ - SameSite=None requires the Secure flag (per RFC 6265bis)
328328+ - Partitioned requires the Secure flag (per CHIPS specification)
329329+330330+ @see <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7> RFC 6265bis Section 5.4.7 - SameSite
331331+ @see <https://developer.chrome.com/docs/privacy-sandbox/chips/> CHIPS - Cookies Having Independent Partitioned State *)
286332let validate_attributes attrs =
287333 (* SameSite=None requires Secure flag *)
288334 let samesite_valid =
···308354 samesite_valid && partitioned_valid
309355310356(** Build final cookie from name/value and accumulated attributes.
311311- Per RFC 6265 Section 5.3:
312312- - If Domain attribute is present, host_only = false, domain = attribute value
313313- - If Domain attribute is absent, host_only = true, domain = request host *)
357357+358358+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}:
359359+ - If Domain attribute is present, host-only-flag = false, domain = attribute value
360360+ - If Domain attribute is absent, host-only-flag = true, domain = request host
361361+362362+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
314363let build_cookie ~request_domain ~request_path ~name ~value attrs ~now =
315364 let host_only, domain =
316365 match attrs.domain with
···341390342391(** {1 Cookie Parsing} *)
343392393393+(** Parse a Set-Cookie HTTP response header.
394394+395395+ Parses the header according to {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2} RFC 6265 Section 5.2},
396396+ extracting the cookie name, value, and all attributes. Returns [None] if
397397+ the cookie is invalid or fails validation.
398398+399399+ @param now Function returning current time for Max-Age computation
400400+ @param domain The request host (used as default domain)
401401+ @param path The request path (used as default path)
402402+ @param header_value The Set-Cookie header value string
403403+ @return The parsed cookie, or [None] if parsing/validation fails
404404+405405+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2> RFC 6265 Section 5.2 - The Set-Cookie Header *)
344406let of_set_cookie_header ~now ~domain:request_domain ~path:request_path
345407 header_value =
346408 Log.debug (fun m -> m "Parsing Set-Cookie: %s" header_value);
···393455 Log.debug (fun m -> m "Parsed cookie: %a" pp cookie);
394456 Some cookie)
395457458458+(** Parse a Cookie HTTP request header.
459459+460460+ Parses the header according to {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.2} RFC 6265 Section 4.2}.
461461+ The Cookie header contains semicolon-separated name=value pairs.
462462+463463+ Cookies parsed from the Cookie header have [host_only = true] since we
464464+ cannot determine from the header alone whether they originally had a
465465+ Domain attribute.
466466+467467+ @param now Function returning current time for timestamps
468468+ @param domain The request host (assigned to all parsed cookies)
469469+ @param path The request path (assigned to all parsed cookies)
470470+ @param header_value The Cookie header value string
471471+ @return List of parse results (Ok cookie or Error message)
472472+473473+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.2> RFC 6265 Section 4.2 - The Cookie Header *)
396474let of_cookie_header ~now ~domain ~path header_value =
397475 Log.debug (fun m -> m "Parsing Cookie header: %s" header_value);
398476···429507 Ok cookie)
430508 parts
431509510510+(** Generate a Cookie HTTP request header from a list of cookies.
511511+512512+ Formats cookies according to {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.2} RFC 6265 Section 4.2}
513513+ as semicolon-separated name=value pairs.
514514+515515+ @param cookies List of cookies to include
516516+ @return The Cookie header value string
517517+518518+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.2> RFC 6265 Section 4.2 - The Cookie Header *)
432519let make_cookie_header cookies =
433520 cookies
434521 |> List.map (fun c -> Printf.sprintf "%s=%s" (name c) (value c))
435522 |> String.concat "; "
436523524524+(** Generate a Set-Cookie HTTP response header from a cookie.
525525+526526+ Formats the cookie according to {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1} RFC 6265 Section 4.1}
527527+ including all attributes.
528528+529529+ Note: The Expires attribute is currently formatted using RFC 3339, which
530530+ differs from the RFC-recommended rfc1123-date format.
531531+532532+ @param cookie The cookie to serialize
533533+ @return The Set-Cookie header value string
534534+535535+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1> RFC 6265 Section 4.1 - The Set-Cookie Header *)
437536let make_set_cookie_header cookie =
438537 let buffer = Buffer.create 128 in
439538 Buffer.add_string buffer (Printf.sprintf "%s=%s" (name cookie) (value cookie));
+167-74
lib/core/cookeio.mli
···5566(** Cookie management library for OCaml
7788- HTTP cookies are a mechanism that allows "server side connections to store
99- and retrieve information on the client side." Originally designed to enable
1010- persistent client-side state for web applications, cookies are essential for
1111- storing user preferences, session data, shopping cart contents, and
1212- authentication tokens.
88+ HTTP cookies are a mechanism defined in
99+ {{:https://datatracker.ietf.org/doc/html/rfc6265} RFC 6265} that allows
1010+ "server side connections to store and retrieve information on the client
1111+ side." Originally designed to enable persistent client-side state for web
1212+ applications, cookies are essential for storing user preferences, session
1313+ data, shopping cart contents, and authentication tokens.
13141414- This library provides a complete cookie jar implementation following
1515- established web standards while integrating Eio for efficient asynchronous
1616- operations.
1515+ This library provides a complete cookie implementation following RFC 6265
1616+ while integrating Eio for efficient asynchronous operations.
17171818 {2 Cookie Format and Structure}
19192020- Cookies are set via the Set-Cookie HTTP response header with the basic
2121- format: [NAME=VALUE] with optional attributes including:
2222- - [expires]: Optional cookie lifetime specification
2323- - [domain]: Specifying valid domains using tail matching
2424- - [path]: Defining URL subset for cookie validity
2020+ Cookies are set via the Set-Cookie HTTP response header
2121+ ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1} Section 4.1})
2222+ with the basic format: [NAME=VALUE] with optional attributes including:
2323+ - [expires]: Cookie lifetime specification
2424+ ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1} Section 5.2.1})
2525+ - [max-age]: Cookie lifetime in seconds
2626+ ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2} Section 5.2.2})
2727+ - [domain]: Valid domains using tail matching
2828+ ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3} Section 5.2.3})
2929+ - [path]: URL subset for cookie validity
3030+ ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4} Section 5.2.4})
2531 - [secure]: Transmission over secure channels only
3232+ ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5} Section 5.2.5})
2633 - [httponly]: Not accessible to JavaScript
2727- - [samesite]: Cross-site request behavior control
3434+ ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6} Section 5.2.6})
3535+ - [samesite]: Cross-site request behavior (RFC 6265bis)
3636+ - [partitioned]: CHIPS partitioned storage
28372938 {2 Domain and Path Matching}
30393131- The library implements standard domain and path matching rules:
3232- - Domain matching uses "tail matching" (e.g., "acme.com" matches
3333- "anvil.acme.com")
3434- - Path matching allows subset URL specification for fine-grained control
3535- - More specific path mappings are sent first in Cookie headers *)
4040+ The library implements standard domain and path matching rules from
4141+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3} Section 5.1.3}
4242+ and {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4} Section 5.1.4}:
4343+ - Domain matching uses suffix matching for hostnames (e.g., "example.com"
4444+ matches "sub.example.com")
4545+ - IP addresses require exact match only
4646+ - Path matching requires exact match or prefix with "/" separator
4747+4848+ @see <https://datatracker.ietf.org/doc/html/rfc6265> RFC 6265 - HTTP State Management Mechanism *)
4949+5050+(** {1 Types} *)
36513752module SameSite : sig
3853 type t = [ `Strict | `Lax | `None ]
3954 (** Cookie same-site policy for controlling cross-site request behavior.
40555656+ Defined in RFC 6265bis draft.
5757+4158 - [`Strict]: Cookie only sent for same-site requests, providing maximum
4259 protection
4360 - [`Lax]: Cookie sent for same-site requests and top-level navigation
4461 (default for modern browsers)
4562 - [`None]: Cookie sent for all cross-site requests (requires [secure]
4646- flag) *)
6363+ flag per RFC 6265bis)
6464+6565+ @see <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7> RFC 6265bis Section 5.4.7 - The SameSite Attribute *)
47664867 val equal : t -> t -> bool
4949- (** Equality function for same-site values *)
6868+ (** Equality function for same-site values. *)
50695170 val pp : Format.formatter -> t -> unit
5252- (** Pretty printer for same-site values *)
7171+ (** Pretty printer for same-site values. *)
5372end
54735574module Expiration : sig
5675 type t = [ `Session | `DateTime of Ptime.t ]
5776 (** Cookie expiration strategy.
58775959- - [`Session]: Session cookie that expires when browser session ends
6060- - [`DateTime time]: Persistent cookie that expires at specific time *)
7878+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}:
7979+ - [`Session]: Session cookie that expires when user agent session ends
8080+ (persistent-flag = false)
8181+ - [`DateTime time]: Persistent cookie that expires at specific time
8282+ (persistent-flag = true)
8383+8484+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
61856286 val equal : t -> t -> bool
6363- (** Equality function for expiration values *)
8787+ (** Equality function for expiration values. *)
64886589 val pp : Format.formatter -> t -> unit
6666- (** Pretty printer for expiration values *)
9090+ (** Pretty printer for expiration values. *)
6791end
68926993type t
7094(** HTTP Cookie representation with all standard attributes.
71957296 A cookie represents a name-value pair with associated metadata that controls
7373- its scope, security, and lifetime. Cookies with the same [name], [domain],
7474- and [path] will overwrite each other when added to a cookie jar. *)
9797+ its scope, security, and lifetime. Per
9898+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3},
9999+ cookies with the same [name], [domain], and [path] will overwrite each other
100100+ when stored.
101101+102102+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
7510376104(** {1 Cookie Accessors} *)
7710578106val domain : t -> string
7979-(** Get the domain of a cookie *)
107107+(** Get the domain of a cookie.
108108+109109+ The domain is normalized per
110110+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3} RFC 6265 Section 5.2.3}
111111+ (leading dots removed). *)
8011281113val path : t -> string
8282-(** Get the path of a cookie *)
114114+(** Get the path of a cookie.
115115+116116+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4> RFC 6265 Section 5.2.4 - The Path Attribute *)
8311784118val name : t -> string
8585-(** Get the name of a cookie *)
119119+(** Get the name of a cookie. *)
8612087121val value : t -> string
8888-(** Get the value of a cookie *)
122122+(** Get the value of a cookie. *)
8912390124val value_trimmed : t -> string
91125(** Get cookie value with surrounding double-quotes removed if they form a
···9312794128 Only removes quotes when both opening and closing quotes are present. The
95129 raw value is always preserved in {!value}. This is useful for handling
9696- quoted cookie values per RFC 6265.
130130+ quoted cookie values.
9713198132 Examples:
99133 - ["value"] → ["value"]
···102136 - ["\"val\"\""] → ["val\""] (removes outer pair only) *)
103137104138val secure : t -> bool
105105-(** Check if cookie is secure only *)
139139+(** Check if cookie has the Secure flag.
140140+141141+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5} RFC 6265 Section 5.2.5},
142142+ Secure cookies are only sent over HTTPS connections.
143143+144144+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5> RFC 6265 Section 5.2.5 - The Secure Attribute *)
106145107146val http_only : t -> bool
108108-(** Check if cookie is HTTP only *)
147147+(** Check if cookie has the HttpOnly flag.
148148+149149+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6} RFC 6265 Section 5.2.6},
150150+ HttpOnly cookies are not accessible to client-side scripts.
151151+152152+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6> RFC 6265 Section 5.2.6 - The HttpOnly Attribute *)
109153110154val partitioned : t -> bool
111155(** Check if cookie has the Partitioned attribute.
···113157 Partitioned cookies are part of CHIPS (Cookies Having Independent
114158 Partitioned State) and are stored separately per top-level site, enabling
115159 privacy-preserving third-party cookie functionality. Partitioned cookies
116116- must always be Secure. *)
160160+ must always be Secure.
161161+162162+ @see <https://developer.chrome.com/docs/privacy-sandbox/chips/> CHIPS - Cookies Having Independent Partitioned State *)
117163118164val host_only : t -> bool
119165(** Check if cookie has the host-only flag set.
120166121121- Per RFC 6265 Section 5.3:
122122- - If the Set-Cookie header included a Domain attribute, host_only is false
123123- and the cookie matches the domain and all subdomains.
124124- - If no Domain attribute was present, host_only is true and the cookie
167167+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3 Step 6}:
168168+ - If the Set-Cookie header included a Domain attribute, host-only-flag is
169169+ false and the cookie matches the domain and all subdomains.
170170+ - If no Domain attribute was present, host-only-flag is true and the cookie
125171 only matches the exact request host.
126172127173 Example:
128174 - Cookie set on "example.com" with Domain=example.com: host_only=false,
129175 matches example.com and sub.example.com
130176 - Cookie set on "example.com" without Domain attribute: host_only=true,
131131- matches only example.com, not sub.example.com *)
177177+ matches only example.com, not sub.example.com
178178+179179+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
132180133181val expires : t -> Expiration.t option
134182(** Get the expiration attribute if set.
135183136136- - [None]: No expiration specified (browser decides lifetime)
137137- - [Some `Session]: Session cookie (expires when browser session ends)
184184+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1} RFC 6265 Section 5.2.1}:
185185+ - [None]: No expiration specified (session cookie)
186186+ - [Some `Session]: Session cookie (expires when user agent session ends)
138187 - [Some (`DateTime t)]: Expires at specific time [t]
139188140189 Both [max_age] and [expires] can be present simultaneously. This library
141141- stores both independently. *)
190190+ stores both independently.
191191+192192+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1> RFC 6265 Section 5.2.1 - The Expires Attribute *)
142193143194val max_age : t -> Ptime.Span.t option
144195(** Get the max-age attribute if set.
145196146146- Both [max_age] and [expires] can be present simultaneously. When both are
147147- present in a Set-Cookie header, browsers prioritize [max_age] per RFC 6265.
148148- This library stores both independently and serializes both when present. *)
197197+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2} RFC 6265 Section 5.2.2},
198198+ Max-Age specifies the cookie lifetime in seconds. Both [max_age] and
199199+ [expires] can be present simultaneously. When both are present in a
200200+ Set-Cookie header, browsers prioritize [max_age] per
201201+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} Section 5.3 Step 3}.
202202+203203+ This library stores both independently and serializes both when present.
204204+205205+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2> RFC 6265 Section 5.2.2 - The Max-Age Attribute *)
149206150207val same_site : t -> SameSite.t option
151151-(** Get the same-site policy of a cookie *)
208208+(** Get the same-site policy of a cookie.
209209+210210+ @see <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7> RFC 6265bis Section 5.4.7 - The SameSite Attribute *)
152211153212val creation_time : t -> Ptime.t
154154-(** Get the creation time of a cookie *)
213213+(** Get the creation time of a cookie.
214214+215215+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3},
216216+ this is set when the cookie is first received. *)
155217156218val last_access : t -> Ptime.t
157157-(** Get the last access time of a cookie *)
219219+(** Get the last access time of a cookie.
220220+221221+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3},
222222+ this is updated each time the cookie is retrieved for a request. *)
158223159224val make :
160225 domain:string ->
···174239 t
175240(** Create a new cookie with the given attributes.
176241177177- @param host_only If true, the cookie only matches the exact domain (no
178178- subdomains). Defaults to false. Per RFC 6265, this should be true when no
179179- Domain attribute was present in the Set-Cookie header.
242242+ @param domain The cookie domain (will be normalized)
243243+ @param path The cookie path
244244+ @param name The cookie name
245245+ @param value The cookie value
246246+ @param secure If true, cookie only sent over HTTPS (default: false)
247247+ @param http_only If true, cookie not accessible to scripts (default: false)
248248+ @param expires Expiration time
249249+ @param max_age Lifetime in seconds
250250+ @param same_site Cross-site request policy
251251+ @param partitioned CHIPS partitioned storage (default: false)
252252+ @param host_only If true, exact domain match only (default: false). Per
253253+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3},
254254+ this should be true when no Domain attribute was present in the
255255+ Set-Cookie header.
256256+ @param creation_time When the cookie was created
257257+ @param last_access Last time the cookie was accessed
180258181259 Note: If [partitioned] is [true], the cookie must also be [secure]. Invalid
182182- combinations will result in validation errors. *)
260260+ combinations will result in validation errors.
261261+262262+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
183263184264(** {1 Cookie Creation and Parsing} *)
185265···187267 now:(unit -> Ptime.t) -> domain:string -> path:string -> string -> t option
188268(** Parse Set-Cookie response header value into a cookie.
189269190190- Set-Cookie headers are sent from server to client and contain the cookie
191191- name, value, and all attributes.
192192-193193- Parses a Set-Cookie header value following RFC specifications:
270270+ Parses a Set-Cookie header following
271271+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2} RFC 6265 Section 5.2}:
194272 - Basic format: [NAME=VALUE; attribute1; attribute2=value2]
195273 - Supports all standard attributes: [expires], [max-age], [domain], [path],
196274 [secure], [httponly], [samesite], [partitioned]
···200278 - The [now] parameter is used for calculating expiry times from [max-age]
201279 attributes and setting creation/access times
202280203203- Cookie validation rules:
281281+ Cookie validation rules (from RFC 6265bis and CHIPS):
204282 - [SameSite=None] requires the [Secure] flag to be set
205283 - [Partitioned] requires the [Secure] flag to be set
206284207285 Example:
208208- [of_set_cookie_header ~now:(fun () -> Ptime_clock.now ())
209209- ~domain:"example.com" ~path:"/" "session=abc123; Secure; HttpOnly"] *)
286286+ {[of_set_cookie_header ~now:(fun () -> Ptime_clock.now ())
287287+ ~domain:"example.com" ~path:"/" "session=abc123; Secure; HttpOnly"]}
288288+289289+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2> RFC 6265 Section 5.2 - The Set-Cookie Header *)
210290211291val of_cookie_header :
212292 now:(unit -> Ptime.t) ->
···216296 (t, string) result list
217297(** Parse Cookie request header containing semicolon-separated name=value pairs.
218298219219- Cookie headers are sent from client to server and contain only name=value
220220- pairs without attributes: ["name1=value1; name2=value2; name3=value3"]
299299+ Parses a Cookie header following
300300+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.2} RFC 6265 Section 4.2}.
301301+ Cookie headers contain only name=value pairs without attributes:
302302+ ["name1=value1; name2=value2; name3=value3"]
221303222304 Creates cookies with:
223305 - Provided [domain] and [path] from request context
224306 - All security flags set to [false] (defaults)
225307 - All optional attributes set to [None]
308308+ - [host_only = true] (since we cannot determine from the header alone
309309+ whether cookies originally had a Domain attribute)
226310 - [creation_time] and [last_access] set to current time from [now]
227311228312 Returns a list of parse results, one per cookie. Parse errors for individual
229229- cookies are returned as [Error msg] without failing the entire parse. Empty
230230- values and excess whitespace are ignored.
313313+ cookies are returned as [Error msg] without failing the entire parse.
231314232315 Example:
233233- [of_cookie_header ~now:(fun () -> Ptime_clock.now ()) ~domain:"example.com"
234234- ~path:"/" "session=abc; theme=dark"] *)
316316+ {[of_cookie_header ~now:(fun () -> Ptime_clock.now ()) ~domain:"example.com"
317317+ ~path:"/" "session=abc; theme=dark"]}
318318+319319+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.2> RFC 6265 Section 4.2 - The Cookie Header *)
235320236321val make_cookie_header : t list -> string
237237-(** Create cookie header value from cookies.
322322+(** Create Cookie header value from cookies.
238323239324 Formats a list of cookies into a Cookie header value suitable for HTTP
240240- requests.
325325+ requests per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.2} RFC 6265 Section 4.2}.
241326 - Format: [name1=value1; name2=value2; name3=value3]
242327 - Only includes cookie names and values, not attributes
243328 - Cookies should already be filtered for the target domain/path
244244- - More specific path mappings should be ordered first in the input list
245329246330 Example: [make_cookie_header cookies] might return
247247- ["session=abc123; theme=dark"] *)
331331+ ["session=abc123; theme=dark"]
332332+333333+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.2> RFC 6265 Section 4.2 - The Cookie Header *)
248334249335val make_set_cookie_header : t -> string
250336(** Create Set-Cookie header value from a cookie.
251337252252- Formats a cookie into a Set-Cookie header value suitable for HTTP responses.
338338+ Formats a cookie into a Set-Cookie header value suitable for HTTP responses
339339+ per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1} RFC 6265 Section 4.1}.
253340 Includes all cookie attributes: Max-Age, Expires, Domain, Path, Secure,
254254- HttpOnly, and SameSite. *)
341341+ HttpOnly, Partitioned, and SameSite.
342342+343343+ Note: The Expires attribute is currently formatted using RFC 3339 format,
344344+ which differs from the RFC-recommended rfc1123-date format specified in
345345+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1} Section 4.1.1}.
346346+347347+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1> RFC 6265 Section 4.1 - The Set-Cookie Header *)
255348256349(** {1 Pretty Printing} *)
257350258351val pp : Format.formatter -> t -> unit
259259-(** Pretty print a cookie *)
352352+(** Pretty print a cookie. *)
+100-12
lib/jar/cookeio_jar.ml
···22222323(** {1 Cookie Matching Helpers} *)
24242525+(** Two cookies are considered identical if they have the same name, domain,
2626+ and path. This is used when replacing or removing cookies.
2727+2828+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
2529let cookie_identity_matches c1 c2 =
2630 Cookeio.name c1 = Cookeio.name c2
2731 && Cookeio.domain c1 = Cookeio.domain c2
2832 && Cookeio.path c1 = Cookeio.path c2
29333434+(** Normalize a domain by stripping the leading dot.
3535+3636+ Per RFC 6265, the Domain attribute value is canonicalized by removing any
3737+ leading dot before storage.
3838+3939+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3> RFC 6265 Section 5.2.3 - The Domain Attribute *)
3040let normalize_domain domain =
3131- (* Strip leading dot per RFC 6265 *)
3241 match String.starts_with ~prefix:"." domain with
3342 | true when String.length domain > 1 ->
3443 String.sub domain 1 (String.length domain - 1)
3544 | _ -> domain
36454646+(** Check if a string is an IP address (IPv4 or IPv6).
4747+4848+ Per RFC 6265 Section 5.1.3, domain matching should only apply to hostnames,
4949+ not IP addresses. IP addresses require exact match only.
5050+5151+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3> RFC 6265 Section 5.1.3 - Domain Matching *)
5252+let is_ip_address domain =
5353+ match Ipaddr.of_string domain with
5454+ | Ok _ -> true
5555+ | Error _ -> false
5656+5757+(** Check if a cookie domain matches a request domain.
5858+5959+ Per RFC 6265 Section 5.1.3, a string domain-matches a given domain string if:
6060+ - The domain string and the string are identical, OR
6161+ - All of the following are true:
6262+ - The domain string is a suffix of the string
6363+ - The last character of the string not in the domain string is "."
6464+ - The string is a host name (i.e., not an IP address)
6565+6666+ Additionally, per Section 5.3 Step 6, if the cookie has the host-only-flag
6767+ set, only exact matches are allowed.
6868+6969+ @param host_only If true, only exact domain match is allowed
7070+ @param cookie_domain The domain stored in the cookie (without leading dot)
7171+ @param request_domain The domain from the HTTP request
7272+ @return true if the cookie should be sent for this domain
7373+7474+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3> RFC 6265 Section 5.1.3 - Domain Matching
7575+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model (host-only-flag) *)
3776let domain_matches ~host_only cookie_domain request_domain =
3838- (* RFC 6265 Section 5.4: Domain matching for Cookie header.
3939- Cookie domains are stored without leading dots per RFC 6265. *)
4040- request_domain = cookie_domain
4141- || (not host_only
4242- && String.ends_with ~suffix:("." ^ cookie_domain) request_domain)
7777+ if is_ip_address request_domain then
7878+ (* IP addresses: exact match only per Section 5.1.3 *)
7979+ request_domain = cookie_domain
8080+ else
8181+ (* Hostnames: exact match or subdomain match (if not host_only) *)
8282+ request_domain = cookie_domain
8383+ || (not host_only
8484+ && String.ends_with ~suffix:("." ^ cookie_domain) request_domain)
8585+8686+(** Check if a cookie path matches a request path.
43878888+ Per RFC 6265 Section 5.1.4, a request-path path-matches a given cookie-path if:
8989+ - The cookie-path and the request-path are identical, OR
9090+ - The cookie-path is a prefix of the request-path, AND either:
9191+ - The last character of the cookie-path is "/", OR
9292+ - The first character of the request-path that is not included in the
9393+ cookie-path is "/"
9494+9595+ @param cookie_path The path stored in the cookie
9696+ @param request_path The path from the HTTP request
9797+ @return true if the cookie should be sent for this path
9898+9999+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4> RFC 6265 Section 5.1.4 - Paths and Path-Match *)
44100let path_matches cookie_path request_path =
4545- (* RFC 6265 Section 5.1.4: A request-path path-matches a cookie-path if:
4646- 1. The cookie-path and the request-path are identical, OR
4747- 2. The cookie-path is a prefix of request-path AND cookie-path ends with "/", OR
4848- 3. The cookie-path is a prefix of request-path AND the first char of
4949- request-path not in cookie-path is "/" *)
50101 if cookie_path = request_path then true
51102 else if String.starts_with ~prefix:cookie_path request_path then
52103 let cookie_len = String.length cookie_path in
···54105 || (String.length request_path > cookie_len && request_path.[cookie_len] = '/')
55106 else false
561075757-(** {1 HTTP Date Parsing} *)
108108+(** {1 Cookie Expiration} *)
109109+110110+(** Check if a cookie has expired based on its expiry-time.
111111+112112+ Per RFC 6265 Section 5.3, a cookie is expired if the current date and time
113113+ is past the expiry-time. Session cookies (with no Expires or Max-Age) never
114114+ expire via this check - they expire when the "session" ends.
115115+116116+ @param cookie The cookie to check
117117+ @param clock The Eio clock for current time
118118+ @return true if the cookie has expired
119119+120120+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
58121let is_expired cookie clock =
59122 match Cookeio.expires cookie with
60123 | None -> false (* No expiration *)
···118181 Log.debug (fun m -> m "Returning %d delta cookies" (List.length result));
119182 result
120183184184+(** Create a removal cookie for deleting a cookie from the client.
185185+186186+ Per RFC 6265 Section 5.3, to remove a cookie, the server sends a Set-Cookie
187187+ header with an expiry date in the past. We also set Max-Age=0 and an empty
188188+ value for maximum compatibility.
189189+190190+ @param cookie The cookie to create a removal for
191191+ @param clock The Eio clock for timestamps
192192+ @return A new cookie configured to cause deletion
193193+194194+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
121195let make_removal_cookie cookie ~clock =
122196 let now =
123197 Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch
···165239166240 Eio.Mutex.unlock jar.mutex
167241242242+(** Retrieve cookies that should be sent for a given request.
243243+244244+ Per RFC 6265 Section 5.4, the user agent should include a Cookie header
245245+ containing cookies that match the request-uri's domain, path, and security
246246+ context. This function also updates the last-access-time for matched cookies.
247247+248248+ @param jar The cookie jar to search
249249+ @param clock The Eio clock for timestamp updates
250250+ @param domain The request domain (hostname or IP address)
251251+ @param path The request path
252252+ @param is_secure Whether the request is over a secure channel (HTTPS)
253253+ @return List of cookies that should be included in the Cookie header
254254+255255+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4> RFC 6265 Section 5.4 - The Cookie Header *)
168256let get_cookies jar ~clock ~domain:request_domain ~path:request_path ~is_secure
169257 =
170258 Log.debug (fun m ->
+56-24
lib/jar/cookeio_jar.mli
···66(** Cookie jar for storing and managing HTTP cookies.
7788 This module provides a complete cookie jar implementation following
99- established web standards while integrating Eio for efficient asynchronous
1010- operations.
99+ {{:https://datatracker.ietf.org/doc/html/rfc6265} RFC 6265} while
1010+ integrating Eio for efficient asynchronous operations.
11111212 A cookie jar maintains a collection of cookies with automatic cleanup of
1313 expired entries. It implements the standard browser behavior for cookie
1414 storage, including:
1515 - Automatic removal of expired cookies
1616- - Domain and path-based cookie retrieval
1616+ - Domain and path-based cookie retrieval per
1717+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.4} Section 5.4}
1718 - Delta tracking for Set-Cookie headers
1818- - Mozilla format persistence for cross-tool compatibility *)
1919+ - Mozilla format persistence for cross-tool compatibility
2020+2121+ @see <https://datatracker.ietf.org/doc/html/rfc6265> RFC 6265 - HTTP State Management Mechanism *)
19222023type t
2124(** Cookie jar for storing and managing cookies.
22252326 A cookie jar maintains a collection of cookies with automatic cleanup of
2427 expired entries and enforcement of storage limits. It implements the
2525- standard browser behavior for cookie storage, including:
2626- - Automatic removal of expired cookies
2727- - LRU eviction when storage limits are exceeded
2828- - Domain and path-based cookie retrieval
2929- - Mozilla format persistence for cross-tool compatibility *)
2828+ standard browser behavior for cookie storage per
2929+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}. *)
30303131(** {1 Cookie Jar Creation and Loading} *)
32323333val create : unit -> t
3434-(** Create an empty cookie jar *)
3434+(** Create an empty cookie jar. *)
35353636val load : clock:_ Eio.Time.clock -> Eio.Fs.dir_ty Eio.Path.t -> t
3737(** Load cookies from Mozilla format file.
···4141 exist or cannot be loaded. *)
42424343val save : Eio.Fs.dir_ty Eio.Path.t -> t -> unit
4444-(** Save cookies to Mozilla format file *)
4444+(** Save cookies to Mozilla format file. *)
45454646(** {1 Cookie Jar Management} *)
4747···50505151 The cookie is added to the delta, meaning it will appear in Set-Cookie
5252 headers when calling {!delta}. If a cookie with the same name/domain/path
5353- exists in the delta, it will be replaced. *)
5353+ exists in the delta, it will be replaced per
5454+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}. *)
54555556val add_original : t -> Cookeio.t -> unit
5657(** Add an original cookie to the jar.
···64656566 Returns cookies that have been added via {!add_cookie} and removal cookies
6667 for original cookies that have been removed. Does not include original
6767- cookies that were added via {!add_original}. *)
6868+ cookies that were added via {!add_original}.
6969+7070+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1> RFC 6265 Section 4.1 - Set-Cookie *)
68716972val remove : t -> clock:_ Eio.Time.clock -> Cookeio.t -> unit
7073(** Remove a cookie from the jar.
71747275 If an original cookie with the same name/domain/path exists, creates a
7376 removal cookie (empty value, Max-Age=0, past expiration) that appears in the
7474- delta. If only a delta cookie exists, simply removes it from the delta. *)
7777+ delta. If only a delta cookie exists, simply removes it from the delta.
7878+7979+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3},
8080+ cookies are removed by sending a Set-Cookie with an expiry date in the past.
8181+8282+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *)
75837684val get_cookies :
7785 t ->
···8593 Returns all cookies that match the given domain and path, and satisfy the
8694 secure flag requirement. Combines original and delta cookies, with delta
8795 taking precedence. Excludes removal cookies (empty value). Also updates the
8888- last access time of matching cookies using the provided clock. *)
9696+ last access time of matching cookies using the provided clock.
9797+9898+ Domain matching follows {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3} Section 5.1.3}:
9999+ - IP addresses require exact match only
100100+ - Hostnames support subdomain matching unless host-only flag is set
101101+102102+ Path matching follows {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4} Section 5.1.4}.
103103+104104+ @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.4> RFC 6265 Section 5.4 - The Cookie Header *)
8910590106val clear : t -> unit
9191-(** Clear all cookies *)
107107+(** Clear all cookies. *)
9210893109val clear_expired : t -> clock:_ Eio.Time.clock -> unit
9494-(** Clear expired cookies *)
110110+(** Clear expired cookies.
111111+112112+ Removes cookies whose expiry-time is in the past per
113113+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}. *)
9511496115val clear_session_cookies : t -> unit
9797-(** Clear session cookies (those without expiry) *)
116116+(** Clear session cookies.
117117+118118+ Removes cookies that have no Expires or Max-Age attribute (session cookies).
119119+ Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3},
120120+ these cookies are normally removed when the user agent "session" ends. *)
9812199122val count : t -> int
100100-(** Get the number of cookies in the jar *)
123123+(** Get the number of unique cookies in the jar. *)
101124102125val get_all_cookies : t -> Cookeio.t list
103103-(** Get all cookies in the jar *)
126126+(** Get all cookies in the jar. *)
104127105128val is_empty : t -> bool
106106-(** Check if the jar is empty *)
129129+(** Check if the jar is empty. *)
107130108131(** {1 Pretty Printing} *)
109132110133val pp : Format.formatter -> t -> unit
111111-(** Pretty print a cookie jar *)
134134+(** Pretty print a cookie jar. *)
112135113136(** {1 Mozilla Format} *)
114137115138val to_mozilla_format : t -> string
116116-(** Write cookies in Mozilla format *)
139139+(** Serialize cookies in Mozilla/Netscape cookie format.
140140+141141+ The Mozilla format uses tab-separated fields:
142142+ {[domain \t include_subdomains \t path \t secure \t expires \t name \t value]}
143143+144144+ The [include_subdomains] field corresponds to the inverse of the
145145+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} host-only-flag}
146146+ in RFC 6265. *)
117147118148val from_mozilla_format : clock:_ Eio.Time.clock -> string -> t
119149(** Parse Mozilla format cookies.
120150121151 Creates a cookie jar from a string in Mozilla cookie format, using the
122122- provided clock to set creation and last access times. *)
152152+ provided clock to set creation and last access times. The [include_subdomains]
153153+ field is mapped to the host-only-flag per
154154+ {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}. *)