OCaml HTTP cookie handling library with support for Eio-based storage jars
at main 24 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Cookie management library for OCaml 7 8 HTTP cookies are a mechanism defined in 9 {{:https://datatracker.ietf.org/doc/html/rfc6265} RFC 6265} that allows 10 "server side connections to store and retrieve information on the client 11 side." Originally designed to enable persistent client-side state for web 12 applications, cookies are essential for storing user preferences, session 13 data, shopping cart contents, and authentication tokens. 14 15 This library provides a complete cookie implementation following RFC 6265 16 while integrating Eio for efficient asynchronous operations. 17 18 {2 Cookie Format and Structure} 19 20 Cookies are set via the Set-Cookie HTTP response header 21 ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1} Section 4.1}) 22 with the basic format: [NAME=VALUE] with optional attributes including: 23 - [expires]: Cookie lifetime specification 24 ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1} Section 5.2.1}) 25 - [max-age]: Cookie lifetime in seconds 26 ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2} Section 5.2.2}) 27 - [domain]: Valid domains using tail matching 28 ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3} Section 5.2.3}) 29 - [path]: URL subset for cookie validity 30 ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4} Section 5.2.4}) 31 - [secure]: Transmission over secure channels only 32 ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5} Section 5.2.5}) 33 - [httponly]: Not accessible to JavaScript 34 ({{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6} Section 5.2.6}) 35 - [samesite]: Cross-site request behavior (RFC 6265bis) 36 - [partitioned]: CHIPS partitioned storage 37 38 {2 Domain and Path Matching} 39 40 The library implements standard domain and path matching rules from 41 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3} Section 5.1.3} 42 and {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4} Section 5.1.4}: 43 - Domain matching uses suffix matching for hostnames (e.g., "example.com" 44 matches "sub.example.com") 45 - IP addresses require exact match only 46 - Path matching requires exact match or prefix with "/" separator 47 48 @see <https://datatracker.ietf.org/doc/html/rfc6265> RFC 6265 - HTTP State Management Mechanism 49 50 {2 Standards and References} 51 52 This library implements and references the following IETF specifications: 53 54 {ul 55 {- {{:https://datatracker.ietf.org/doc/html/rfc6265}RFC 6265} - 56 HTTP State Management Mechanism (April 2011) - Primary specification} 57 {- {{:https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis}RFC 6265bis} - 58 Cookies: HTTP State Management Mechanism (Draft) - SameSite attribute and modern updates} 59 {- {{:https://datatracker.ietf.org/doc/html/rfc1034#section-3.5}RFC 1034 Section 3.5} - 60 Domain Names - Preferred Name Syntax for domain validation} 61 {- {{:https://datatracker.ietf.org/doc/html/rfc2616#section-2.2}RFC 2616 Section 2.2} - 62 HTTP/1.1 - Token syntax definition} 63 {- {{:https://datatracker.ietf.org/doc/html/rfc1123#section-5.2.14}RFC 1123 Section 5.2.14} - 64 Internet Host Requirements - Date format (rfc1123-date)}} 65 66 Additional standards: 67 {ul 68 {- {{:https://publicsuffix.org/}Mozilla Public Suffix List} - Registry 69 of public suffixes for cookie domain validation per RFC 6265 Section 5.3 Step 5}} *) 70 71(** {1 Types} *) 72 73module SameSite : sig 74 type t = [ `Strict | `Lax | `None ] 75 (** Cookie same-site policy for controlling cross-site request behavior. 76 77 Defined in RFC 6265bis draft. 78 79 - [`Strict]: Cookie only sent for same-site requests, providing maximum 80 protection 81 - [`Lax]: Cookie sent for same-site requests and top-level navigation 82 (default for modern browsers) 83 - [`None]: Cookie sent for all cross-site requests (requires [secure] 84 flag per RFC 6265bis) 85 86 @see <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7> RFC 6265bis Section 5.4.7 - The SameSite Attribute *) 87 88 val equal : t -> t -> bool 89 (** Equality function for same-site values. *) 90 91 val pp : Format.formatter -> t -> unit 92 (** Pretty printer for same-site values. *) 93end 94 95module Expiration : sig 96 type t = [ `Session | `DateTime of Ptime.t ] 97 (** Cookie expiration strategy. 98 99 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}: 100 - [`Session]: Session cookie that expires when user agent session ends 101 (persistent-flag = false) 102 - [`DateTime time]: Persistent cookie that expires at specific time 103 (persistent-flag = true) 104 105 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *) 106 107 val equal : t -> t -> bool 108 (** Equality function for expiration values. *) 109 110 val pp : Format.formatter -> t -> unit 111 (** Pretty printer for expiration values. *) 112end 113 114type t 115(** HTTP Cookie representation with all standard attributes. 116 117 A cookie represents a name-value pair with associated metadata that controls 118 its scope, security, and lifetime. Per 119 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}, 120 cookies with the same [name], [domain], and [path] will overwrite each other 121 when stored. 122 123 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *) 124 125(** {1 Cookie Accessors} *) 126 127val domain : t -> string 128(** Get the domain of a cookie. 129 130 The domain is normalized per 131 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3} RFC 6265 Section 5.2.3} 132 (leading dots removed). *) 133 134val path : t -> string 135(** Get the path of a cookie. 136 137 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4> RFC 6265 Section 5.2.4 - The Path Attribute *) 138 139val name : t -> string 140(** Get the name of a cookie. *) 141 142val value : t -> string 143(** Get the value of a cookie. *) 144 145val value_trimmed : t -> string 146(** Get cookie value with surrounding double-quotes removed if they form a 147 matching pair. 148 149 Only removes quotes when both opening and closing quotes are present. The 150 raw value is always preserved in {!value}. This is useful for handling 151 quoted cookie values. 152 153 Examples: 154 - ["value"] → ["value"] 155 - ["\"value\""] → ["value"] 156 - ["\"value"] → ["\"value"] (no matching pair) 157 - ["\"val\"\""] → ["val\""] (removes outer pair only) *) 158 159val secure : t -> bool 160(** Check if cookie has the Secure flag. 161 162 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5} RFC 6265 Section 5.2.5}, 163 Secure cookies are only sent over HTTPS connections. 164 165 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5> RFC 6265 Section 5.2.5 - The Secure Attribute *) 166 167val http_only : t -> bool 168(** Check if cookie has the HttpOnly flag. 169 170 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6} RFC 6265 Section 5.2.6}, 171 HttpOnly cookies are not accessible to client-side scripts. 172 173 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6> RFC 6265 Section 5.2.6 - The HttpOnly Attribute *) 174 175val partitioned : t -> bool 176(** Check if cookie has the Partitioned attribute. 177 178 Partitioned cookies are part of CHIPS (Cookies Having Independent 179 Partitioned State) and are stored separately per top-level site, enabling 180 privacy-preserving third-party cookie functionality. Partitioned cookies 181 must always be Secure. 182 183 @see <https://developer.chrome.com/docs/privacy-sandbox/chips/> CHIPS - Cookies Having Independent Partitioned State *) 184 185val host_only : t -> bool 186(** Check if cookie has the host-only flag set. 187 188 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3 Step 6}: 189 - If the Set-Cookie header included a Domain attribute, host-only-flag is 190 false and the cookie matches the domain and all subdomains. 191 - If no Domain attribute was present, host-only-flag is true and the cookie 192 only matches the exact request host. 193 194 Example: 195 - Cookie set on "example.com" with Domain=example.com: host_only=false, 196 matches example.com and sub.example.com 197 - Cookie set on "example.com" without Domain attribute: host_only=true, 198 matches only example.com, not sub.example.com 199 200 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *) 201 202val expires : t -> Expiration.t option 203(** Get the expiration attribute if set. 204 205 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1} RFC 6265 Section 5.2.1}: 206 - [None]: No expiration specified (session cookie) 207 - [Some `Session]: Session cookie (expires when user agent session ends) 208 - [Some (`DateTime t)]: Expires at specific time [t] 209 210 Both [max_age] and [expires] can be present simultaneously. This library 211 stores both independently. 212 213 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1> RFC 6265 Section 5.2.1 - The Expires Attribute *) 214 215val max_age : t -> Ptime.Span.t option 216(** Get the max-age attribute if set. 217 218 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2} RFC 6265 Section 5.2.2}, 219 Max-Age specifies the cookie lifetime in seconds. Both [max_age] and 220 [expires] can be present simultaneously. When both are present in a 221 Set-Cookie header, browsers prioritize [max_age] per 222 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} Section 5.3 Step 3}. 223 224 This library stores both independently and serializes both when present. 225 226 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2> RFC 6265 Section 5.2.2 - The Max-Age Attribute *) 227 228val same_site : t -> SameSite.t option 229(** Get the same-site policy of a cookie. 230 231 @see <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7> RFC 6265bis Section 5.4.7 - The SameSite Attribute *) 232 233val creation_time : t -> Ptime.t 234(** Get the creation time of a cookie. 235 236 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}, 237 this is set when the cookie is first received. *) 238 239val last_access : t -> Ptime.t 240(** Get the last access time of a cookie. 241 242 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}, 243 this is updated each time the cookie is retrieved for a request. *) 244 245val make : 246 domain:string -> 247 path:string -> 248 name:string -> 249 value:string -> 250 ?secure:bool -> 251 ?http_only:bool -> 252 ?expires:Expiration.t -> 253 ?max_age:Ptime.Span.t -> 254 ?same_site:SameSite.t -> 255 ?partitioned:bool -> 256 ?host_only:bool -> 257 creation_time:Ptime.t -> 258 last_access:Ptime.t -> 259 unit -> 260 t 261(** Create a new cookie with the given attributes. 262 263 @param domain The cookie domain (will be normalized) 264 @param path The cookie path 265 @param name The cookie name 266 @param value The cookie value 267 @param secure If true, cookie only sent over HTTPS (default: false) 268 @param http_only If true, cookie not accessible to scripts (default: false) 269 @param expires Expiration time 270 @param max_age Lifetime in seconds 271 @param same_site Cross-site request policy 272 @param partitioned CHIPS partitioned storage (default: false) 273 @param host_only If true, exact domain match only (default: false). Per 274 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3}, 275 this should be true when no Domain attribute was present in the 276 Set-Cookie header. 277 @param creation_time When the cookie was created 278 @param last_access Last time the cookie was accessed 279 280 Note: If [partitioned] is [true], the cookie must also be [secure]. Invalid 281 combinations will result in validation errors. 282 283 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model *) 284 285(** {1 RFC 6265 Validation} 286 287 Validation functions for cookie names, values, and attributes per 288 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1} RFC 6265 Section 4.1.1}. 289 290 These functions implement the syntactic requirements from RFC 6265 to ensure 291 cookies conform to the specification before being sent in HTTP headers. 292 All validation failures return detailed error messages citing the specific 293 RFC requirement that was violated. 294 295 {2 Validation Philosophy} 296 297 Per RFC 6265 Section 4, there is an important distinction between: 298 - {b Server requirements} (Section 4.1): Strict syntax for generating Set-Cookie headers 299 - {b User agent requirements} (Section 5): Lenient parsing for receiving Set-Cookie headers 300 301 These validation functions enforce the {b server requirements}, ensuring that 302 cookies generated by this library conform to RFC 6265 syntax. When parsing 303 cookies from HTTP headers, the library may be more lenient to maximize 304 interoperability with non-compliant servers. 305 306 {2 Character Set Requirements} 307 308 RFC 6265 restricts cookies to US-ASCII characters with specific exclusions: 309 - Cookie names: RFC 2616 tokens (no CTLs, no separators) 310 - Cookie values: cookie-octet characters (0x21, 0x23-0x2B, 0x2D-0x3A, 0x3C-0x5B, 0x5D-0x7E) 311 - Domain values: RFC 1034 domain name syntax or IP addresses 312 - Path values: Any character except CTLs and semicolon 313 314 These functions return [Ok value] on success or [Error msg] with a detailed 315 explanation of why validation failed. 316 317 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1> RFC 6265 Section 4.1.1 - Syntax *) 318 319module Validate : sig 320 val cookie_name : string -> (string, string) result 321 (** Validate a cookie name per RFC 6265. 322 323 Cookie names must be valid RFC 2616 tokens: one or more characters 324 excluding control characters and separators. 325 326 Per {{:https://datatracker.ietf.org/doc/html/rfc2616#section-2.2}RFC 2616 Section 2.2}, 327 a token is defined as: one or more characters excluding control characters 328 and the following 19 separator characters: parentheses, angle brackets, at-sign, 329 comma, semicolon, colon, backslash, double-quote, forward slash, square brackets, 330 question mark, equals, curly braces, space, and horizontal tab. 331 332 This means tokens consist of visible ASCII characters (33-126) excluding 333 control characters (0-31, 127) and the separator characters listed above. 334 335 @param name The cookie name to validate 336 @return [Ok name] if valid, [Error message] with explanation if invalid 337 338 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1> RFC 6265 Section 4.1.1 339 @see <https://datatracker.ietf.org/doc/html/rfc2616#section-2.2> RFC 2616 Section 2.2 - Basic Rules *) 340 341 val cookie_value : string -> (string, string) result 342 (** Validate a cookie value per RFC 6265. 343 344 Cookie values must contain only cookie-octets, optionally wrapped in 345 double quotes. Invalid characters include: control characters, space, 346 double quote (except as wrapper), comma, semicolon, and backslash. 347 348 Per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1}RFC 6265 Section 4.1.1}, 349 cookie-value may be: 350 - Zero or more cookie-octet characters, or 351 - Double-quoted string containing cookie-octet characters 352 353 Where cookie-octet excludes: CTLs (0x00-0x1F, 0x7F), space (0x20), 354 double-quote (0x22), comma (0x2C), semicolon (0x3B), and backslash (0x5C). 355 356 Valid cookie-octet characters: 0x21, 0x23-0x2B, 0x2D-0x3A, 0x3C-0x5B, 0x5D-0x7E 357 358 @param value The cookie value to validate 359 @return [Ok value] if valid, [Error message] with explanation if invalid 360 361 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1> RFC 6265 Section 4.1.1 *) 362 363 val domain_value : string -> (string, string) result 364 (** Validate a domain attribute value. 365 366 Domain values must be either: 367 - A valid domain name per RFC 1034 Section 3.5 368 - A valid IPv4 address 369 - A valid IPv6 address 370 371 Per {{:https://datatracker.ietf.org/doc/html/rfc1034#section-3.5}RFC 1034 Section 3.5}, 372 preferred domain name syntax requires: 373 - Labels separated by dots 374 - Labels must start with a letter 375 - Labels must end with a letter or digit 376 - Labels may contain letters, digits, and hyphens 377 - Labels are case-insensitive 378 - Total length limited to 255 octets 379 380 Leading dots are stripped per RFC 6265 Section 5.2.3 before validation. 381 382 @param domain The domain value to validate (leading dot is stripped first) 383 @return [Ok domain] if valid, [Error message] with explanation if invalid 384 385 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3> RFC 6265 Section 4.1.2.3 386 @see <https://datatracker.ietf.org/doc/html/rfc1034#section-3.5> RFC 1034 Section 3.5 *) 387 388 val path_value : string -> (string, string) result 389 (** Validate a path attribute value. 390 391 Per RFC 6265 Section 4.1.1, path-value may contain any CHAR except 392 control characters and semicolon. 393 394 @param path The path value to validate 395 @return [Ok path] if valid, [Error message] with explanation if invalid 396 397 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1> RFC 6265 Section 4.1.1 *) 398 399 val max_age : int -> (int, string) result 400 (** Validate a Max-Age attribute value. 401 402 Per RFC 6265 Section 4.1.1, max-age-av uses non-zero-digit *DIGIT. 403 However, per Section 5.2.2, user agents should treat values <= 0 as 404 "delete immediately". This function returns [Ok] for any integer since 405 the parsing code handles negative values by converting to 0. 406 407 @param seconds The Max-Age value in seconds 408 @return [Ok seconds] always (negative values are handled in parsing) 409 410 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1> RFC 6265 Section 4.1.1 411 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2> RFC 6265 Section 5.2.2 *) 412end 413 414(** {1 Cookie Creation and Parsing} *) 415 416val of_set_cookie_header : 417 now:(unit -> Ptime.t) -> 418 domain:string -> 419 path:string -> 420 string -> 421 (t, string) result 422(** Parse Set-Cookie response header value into a cookie. 423 424 Parses a Set-Cookie header following 425 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.2} RFC 6265 Section 5.2}: 426 - Basic format: [NAME=VALUE; attribute1; attribute2=value2] 427 - Supports all standard attributes: [expires], [max-age], [domain], [path], 428 [secure], [httponly], [samesite], [partitioned] 429 - Returns [Error msg] if parsing fails or cookie validation fails, with 430 a detailed explanation of what was invalid 431 - The [domain] and [path] parameters provide the request context for default 432 values 433 - The [now] parameter is used for calculating expiry times from [max-age] 434 attributes and setting creation/access times 435 436 Validation rules applied: 437 - Cookie name must be a valid RFC 2616 token (no CTLs or separators) 438 - Cookie value must contain only valid cookie-octets 439 - Domain must be a valid domain name (RFC 1034) or IP address 440 - Path must not contain control characters or semicolons 441 - Max-Age must be non-negative 442 - [SameSite=None] requires the [Secure] flag to be set (RFC 6265bis) 443 - [Partitioned] requires the [Secure] flag to be set (CHIPS) 444 - Domain must not be a public suffix per 445 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-5.3} RFC 6265 Section 5.3 Step 5} 446 (unless the request host exactly matches the domain). This uses the 447 {{:https://publicsuffix.org/list/} Mozilla Public Suffix List} to prevent 448 domain-wide cookie attacks. 449 450 {3 Public Suffix Validation} 451 452 Cookies with Domain attributes that are public suffixes (e.g., [.com], [.co.uk], 453 [.github.io]) are rejected to prevent a malicious site from setting cookies 454 that would affect all sites under that TLD. 455 456 Examples: 457 - Request from [www.example.com], Domain=[.com] → rejected (public suffix) 458 - Request from [www.example.com], Domain=[.example.com] → allowed 459 - Request from [blogspot.com], Domain=[.blogspot.com] → allowed (request matches) 460 461 Example: 462 {[of_set_cookie_header ~now:(fun () -> Ptime_clock.now ()) 463 ~domain:"example.com" ~path:"/" "session=abc123; Secure; HttpOnly"]} 464 465 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.2> RFC 6265 Section 5.2 - The Set-Cookie Header 466 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-5.3> RFC 6265 Section 5.3 - Storage Model (public suffix check) 467 @see <https://publicsuffix.org/list/> Public Suffix List *) 468 469val of_cookie_header : 470 now:(unit -> Ptime.t) -> 471 domain:string -> 472 path:string -> 473 string -> 474 (t list, string) result 475(** Parse Cookie request header containing semicolon-separated name=value pairs. 476 477 Parses a Cookie header following 478 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.2} RFC 6265 Section 4.2}. 479 Cookie headers contain only name=value pairs without attributes: 480 ["name1=value1; name2=value2; name3=value3"] 481 482 Validates each cookie name and value per RFC 6265 and detects duplicate 483 cookie names (which is forbidden per Section 4.2.1). 484 485 Creates cookies with: 486 - Provided [domain] and [path] from request context 487 - All security flags set to [false] (defaults) 488 - All optional attributes set to [None] 489 - [host_only = true] (since we cannot determine from the header alone 490 whether cookies originally had a Domain attribute) 491 - [creation_time] and [last_access] set to current time from [now] 492 493 Returns [Ok cookies] if all cookies parse successfully with no duplicates, 494 or [Error msg] if any validation fails. 495 496 Example: 497 {[of_cookie_header ~now:(fun () -> Ptime_clock.now ()) ~domain:"example.com" 498 ~path:"/" "session=abc; theme=dark"]} 499 500 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.2> RFC 6265 Section 4.2 - The Cookie Header *) 501 502val make_cookie_header : t list -> string 503(** Create Cookie header value from cookies. 504 505 Formats a list of cookies into a Cookie header value suitable for HTTP 506 requests per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.2} RFC 6265 Section 4.2}. 507 - Format: [name1=value1; name2=value2; name3=value3] 508 - Only includes cookie names and values, not attributes 509 - Cookies should already be filtered for the target domain/path 510 511 Example: [make_cookie_header cookies] might return 512 ["session=abc123; theme=dark"] 513 514 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.2> RFC 6265 Section 4.2 - The Cookie Header *) 515 516val make_set_cookie_header : t -> string 517(** Create Set-Cookie header value from a cookie. 518 519 Formats a cookie into a Set-Cookie header value suitable for HTTP responses 520 per {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1} RFC 6265 Section 4.1}. 521 Includes all cookie attributes: Max-Age, Expires, Domain, Path, Secure, 522 HttpOnly, Partitioned, and SameSite. 523 524 Note: The Expires attribute is currently formatted using RFC 3339 format, 525 which differs from the RFC-recommended rfc1123-date format specified in 526 {{:https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1} Section 4.1.1}. 527 528 @see <https://datatracker.ietf.org/doc/html/rfc6265#section-4.1> RFC 6265 Section 4.1 - The Set-Cookie Header *) 529 530(** {1 Pretty Printing} *) 531 532val pp : Format.formatter -> t -> unit 533(** Pretty print a cookie. *)