OCaml HTTP cookie handling library with support for Eio-based storage jars

break eio dep

+58 -66
+2 -3
cookeio.opam
··· 1 1 # This file is generated by dune, edit dune-project instead 2 2 opam-version: "2.0" 3 - synopsis: "Cookie parsing and management library using Eio" 3 + synopsis: "Cookie parsing and management library" 4 4 description: 5 - "Cookeio provides cookie management functionality for OCaml applications, including parsing Set-Cookie headers, managing cookie jars, and supporting the Mozilla cookies.txt format for persistence." 5 + "Cookeio provides cookie parsing and serialization for OCaml applications. It handles parsing Set-Cookie and Cookie headers with full support for all cookie attributes." 6 6 maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 7 7 authors: ["Anil Madhavapeddy"] 8 8 license: "ISC" ··· 11 11 depends: [ 12 12 "ocaml" {>= "5.2.0"} 13 13 "dune" {>= "3.20"} 14 - "eio" {>= "1.0"} 15 14 "logs" {>= "0.9.0"} 16 15 "ptime" {>= "1.1.0"} 17 16 "eio_main" {with-test}
+2 -3
dune-project
··· 15 15 16 16 (package 17 17 (name cookeio) 18 - (synopsis "Cookie parsing and management library using Eio") 19 - (description "Cookeio provides cookie management functionality for OCaml applications, including parsing Set-Cookie headers, managing cookie jars, and supporting the Mozilla cookies.txt format for persistence.") 18 + (synopsis "Cookie parsing and management library") 19 + (description "Cookeio provides cookie parsing and serialization for OCaml applications. It handles parsing Set-Cookie and Cookie headers with full support for all cookie attributes.") 20 20 (depends 21 21 (ocaml (>= 5.2.0)) 22 22 dune 23 - (eio (>= 1.0)) 24 23 (logs (>= 0.9.0)) 25 24 (ptime (>= 1.1.0)) 26 25 (eio_main :with-test)
+12 -18
lib/core/cookeio.ml
··· 220 220 } 221 221 222 222 (** Parse a single attribute and update the accumulator in-place *) 223 - let parse_attribute clock attrs attr_name attr_value = 223 + let parse_attribute now attrs attr_name attr_value = 224 224 let attr_lower = String.lowercase_ascii attr_name in 225 225 match attr_lower with 226 226 | "domain" -> attrs.domain <- Some (normalize_domain attr_value) ··· 244 244 | Some seconds -> 245 245 (* Handle negative values as 0 per RFC 6265 *) 246 246 let seconds = max 0 seconds in 247 - let now = Eio.Time.now clock in 247 + let current_time = now () in 248 248 (* Store the max-age as a Ptime.Span *) 249 249 attrs.max_age <- Some (Ptime.Span.of_int_s seconds); 250 250 (* Also compute and store expires as DateTime *) 251 - let expires = Ptime.of_float_s (now +. float_of_int seconds) in 251 + let expires = Ptime.add_span current_time (Ptime.Span.of_int_s seconds) in 252 252 (match expires with 253 253 | Some time -> attrs.expires <- Some (`DateTime time) 254 254 | None -> ()); ··· 323 323 324 324 (** {1 Cookie Parsing} *) 325 325 326 - let of_set_cookie_header ~clock ~domain:request_domain ~path:request_path 326 + let of_set_cookie_header ~now ~domain:request_domain ~path:request_path 327 327 header_value = 328 328 Log.debug (fun m -> m "Parsing Set-Cookie: %s" header_value); 329 329 ··· 344 344 |> String.trim 345 345 in 346 346 347 - let now = 348 - Ptime.of_float_s (Eio.Time.now clock) 349 - |> Option.value ~default:Ptime.epoch 350 - in 347 + let current_time = now () in 351 348 352 349 (* Parse all attributes into mutable accumulator *) 353 350 let accumulated_attrs = empty_attributes () in ··· 356 353 match String.index_opt attr '=' with 357 354 | None -> 358 355 (* Attribute without value (e.g., Secure, HttpOnly) *) 359 - parse_attribute clock accumulated_attrs attr "" 356 + parse_attribute now accumulated_attrs attr "" 360 357 | Some eq -> 361 358 let attr_name = String.sub attr 0 eq |> String.trim in 362 359 let attr_value = 363 360 String.sub attr (eq + 1) (String.length attr - eq - 1) 364 361 |> String.trim 365 362 in 366 - parse_attribute clock accumulated_attrs attr_name attr_value) 363 + parse_attribute now accumulated_attrs attr_name attr_value) 367 364 attrs; 368 365 369 366 (* Validate attributes *) ··· 373 370 else 374 371 let cookie = 375 372 build_cookie ~request_domain ~request_path ~name 376 - ~value:cookie_value accumulated_attrs ~now 373 + ~value:cookie_value accumulated_attrs ~now:current_time 377 374 in 378 375 Log.debug (fun m -> m "Parsed cookie: %a" pp cookie); 379 376 Some cookie) 380 377 381 - let of_cookie_header ~clock ~domain ~path header_value = 378 + let of_cookie_header ~now ~domain ~path header_value = 382 379 Log.debug (fun m -> m "Parsing Cookie header: %s" header_value); 383 380 384 381 (* Split on semicolons *) ··· 403 400 (String.length name_value - eq_pos - 1) 404 401 |> String.trim 405 402 in 406 - let now = 407 - Ptime.of_float_s (Eio.Time.now clock) 408 - |> Option.value ~default:Ptime.epoch 409 - in 403 + let current_time = now () in 410 404 (* Create cookie with defaults from Cookie header context *) 411 405 let cookie = 412 406 make ~domain ~path ~name:cookie_name ~value:cookie_value 413 - ~secure:false ~http_only:false ~partitioned:false ~creation_time:now 414 - ~last_access:now () 407 + ~secure:false ~http_only:false ~partitioned:false ~creation_time:current_time 408 + ~last_access:current_time () 415 409 in 416 410 Ok cookie) 417 411 parts
+7 -7
lib/core/cookeio.mli
··· 158 158 (** {1 Cookie Creation and Parsing} *) 159 159 160 160 val of_set_cookie_header : 161 - clock:_ Eio.Time.clock -> domain:string -> path:string -> string -> t option 161 + now:(unit -> Ptime.t) -> domain:string -> path:string -> string -> t option 162 162 (** Parse Set-Cookie response header value into a cookie. 163 163 164 164 Set-Cookie headers are sent from server to client and contain the cookie ··· 171 171 - Returns [None] if parsing fails or cookie validation fails 172 172 - The [domain] and [path] parameters provide the request context for default 173 173 values 174 - - The [clock] parameter is used for calculating expiry times from [max-age] 175 - attributes 174 + - The [now] parameter is used for calculating expiry times from [max-age] 175 + attributes and setting creation/access times 176 176 177 177 Cookie validation rules: 178 178 - [SameSite=None] requires the [Secure] flag to be set 179 179 - [Partitioned] requires the [Secure] flag to be set 180 180 181 181 Example: 182 - [of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" "session=abc123; 182 + [of_set_cookie_header ~now:(fun () -> Ptime_clock.now ()) ~domain:"example.com" ~path:"/" "session=abc123; 183 183 Secure; HttpOnly"] *) 184 184 185 185 val of_cookie_header : 186 - clock:_ Eio.Time.clock -> 186 + now:(unit -> Ptime.t) -> 187 187 domain:string -> 188 188 path:string -> 189 189 string -> ··· 197 197 - Provided [domain] and [path] from request context 198 198 - All security flags set to [false] (defaults) 199 199 - All optional attributes set to [None] 200 - - [creation_time] and [last_access] set to current time from [clock] 200 + - [creation_time] and [last_access] set to current time from [now] 201 201 202 202 Returns a list of parse results, one per cookie. Parse errors for individual 203 203 cookies are returned as [Error msg] without failing the entire parse. Empty 204 204 values and excess whitespace are ignored. 205 205 206 206 Example: 207 - [of_cookie_header ~clock ~domain:"example.com" ~path:"/" 207 + [of_cookie_header ~now:(fun () -> Ptime_clock.now ()) ~domain:"example.com" ~path:"/" 208 208 "session=abc; theme=dark"] *) 209 209 210 210 val make_cookie_header : t list -> string
+1 -1
lib/core/dune
··· 1 1 (library 2 2 (name cookeio) 3 3 (public_name cookeio) 4 - (libraries eio logs ptime unix)) 4 + (libraries logs ptime))
+34 -34
test/test_cookeio.ml
··· 376 376 (* Parse a Set-Cookie header with Max-Age *) 377 377 let header = "session=abc123; Max-Age=3600; Secure; HttpOnly" in 378 378 let cookie_opt = 379 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 379 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 380 380 in 381 381 382 382 Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt); ··· 455 455 "id=xyz789; Expires=2025-10-21T07:28:00Z; Path=/; Domain=.example.com" 456 456 in 457 457 let cookie_opt = 458 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 458 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 459 459 in 460 460 461 461 Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt); ··· 490 490 (* This should be rejected: SameSite=None without Secure *) 491 491 let invalid_header = "token=abc; SameSite=None" in 492 492 let cookie_opt = 493 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" invalid_header 493 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" invalid_header 494 494 in 495 495 496 496 Alcotest.(check bool) ··· 500 500 (* This should be accepted: SameSite=None with Secure *) 501 501 let valid_header = "token=abc; SameSite=None; Secure" in 502 502 let cookie_opt2 = 503 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" valid_header 503 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" valid_header 504 504 in 505 505 506 506 Alcotest.(check bool) ··· 528 528 (* Test parsing ".example.com" stores as "example.com" *) 529 529 let header = "test=value; Domain=.example.com" in 530 530 let cookie_opt = 531 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 531 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 532 532 in 533 533 Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt); 534 534 let cookie = Option.get cookie_opt in ··· 562 562 (* Parse a Set-Cookie header with Max-Age *) 563 563 let header = "session=abc123; Max-Age=3600" in 564 564 let cookie_opt = 565 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 565 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 566 566 in 567 567 Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt); 568 568 ··· 595 595 (* Parse a Set-Cookie header with negative Max-Age *) 596 596 let header = "session=abc123; Max-Age=-100" in 597 597 let cookie_opt = 598 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 598 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 599 599 in 600 600 Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt); 601 601 ··· 679 679 (* Parse a cookie with Max-Age *) 680 680 let header = "session=xyz; Max-Age=7200; Secure; HttpOnly" in 681 681 let cookie_opt = 682 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 682 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 683 683 in 684 684 Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt); 685 685 let cookie = Option.get cookie_opt in ··· 691 691 Eio_mock.Clock.set_time clock 5000.0; 692 692 (* Reset clock to same time *) 693 693 let cookie2_opt = 694 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" set_cookie_header 694 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" set_cookie_header 695 695 in 696 696 Alcotest.(check bool) "cookie re-parsed" true (Option.is_some cookie2_opt); 697 697 let cookie2 = Option.get cookie2_opt in ··· 760 760 (* Test FMT1: "Wed, 21 Oct 2015 07:28:00 GMT" (RFC 1123) *) 761 761 let header = "session=abc; Expires=Wed, 21 Oct 2015 07:28:00 GMT" in 762 762 let cookie_opt = 763 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 763 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 764 764 in 765 765 Alcotest.(check bool) "FMT1 cookie parsed" true (Option.is_some cookie_opt); 766 766 ··· 786 786 (* Test FMT2: "Wednesday, 21-Oct-15 07:28:00 GMT" (RFC 850 with abbreviated year) *) 787 787 let header = "session=abc; Expires=Wednesday, 21-Oct-15 07:28:00 GMT" in 788 788 let cookie_opt = 789 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 789 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 790 790 in 791 791 Alcotest.(check bool) "FMT2 cookie parsed" true (Option.is_some cookie_opt); 792 792 ··· 813 813 (* Test FMT3: "Wed Oct 21 07:28:00 2015" (asctime) *) 814 814 let header = "session=abc; Expires=Wed Oct 21 07:28:00 2015" in 815 815 let cookie_opt = 816 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 816 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 817 817 in 818 818 Alcotest.(check bool) "FMT3 cookie parsed" true (Option.is_some cookie_opt); 819 819 ··· 838 838 (* Test FMT4: "Wed, 21-Oct-2015 07:28:00 GMT" (variant) *) 839 839 let header = "session=abc; Expires=Wed, 21-Oct-2015 07:28:00 GMT" in 840 840 let cookie_opt = 841 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 841 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 842 842 in 843 843 Alcotest.(check bool) "FMT4 cookie parsed" true (Option.is_some cookie_opt); 844 844 ··· 863 863 (* Year 95 should become 1995 *) 864 864 let header = "session=abc; Expires=Wed, 21-Oct-95 07:28:00 GMT" in 865 865 let cookie_opt = 866 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 866 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 867 867 in 868 868 let cookie = Option.get cookie_opt in 869 869 let expected = Ptime.of_date_time ((1995, 10, 21), ((07, 28, 00), 0)) in ··· 877 877 (* Year 69 should become 1969 *) 878 878 let header2 = "session=abc; Expires=Wed, 10-Sep-69 20:00:00 GMT" in 879 879 let cookie_opt2 = 880 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header2 880 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header2 881 881 in 882 882 let cookie2 = Option.get cookie_opt2 in 883 883 let expected2 = Ptime.of_date_time ((1969, 9, 10), ((20, 0, 0), 0)) in ··· 891 891 (* Year 99 should become 1999 *) 892 892 let header3 = "session=abc; Expires=Thu, 10-Sep-99 20:00:00 GMT" in 893 893 let cookie_opt3 = 894 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header3 894 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header3 895 895 in 896 896 let cookie3 = Option.get cookie_opt3 in 897 897 let expected3 = Ptime.of_date_time ((1999, 9, 10), ((20, 0, 0), 0)) in ··· 910 910 (* Year 25 should become 2025 *) 911 911 let header = "session=abc; Expires=Wed, 21-Oct-25 07:28:00 GMT" in 912 912 let cookie_opt = 913 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 913 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 914 914 in 915 915 let cookie = Option.get cookie_opt in 916 916 let expected = Ptime.of_date_time ((2025, 10, 21), ((07, 28, 00), 0)) in ··· 924 924 (* Year 0 should become 2000 *) 925 925 let header2 = "session=abc; Expires=Fri, 01-Jan-00 00:00:00 GMT" in 926 926 let cookie_opt2 = 927 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header2 927 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header2 928 928 in 929 929 let cookie2 = Option.get cookie_opt2 in 930 930 let expected2 = Ptime.of_date_time ((2000, 1, 1), ((0, 0, 0), 0)) in ··· 938 938 (* Year 68 should become 2068 *) 939 939 let header3 = "session=abc; Expires=Thu, 10-Sep-68 20:00:00 GMT" in 940 940 let cookie_opt3 = 941 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header3 941 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header3 942 942 in 943 943 let cookie3 = Option.get cookie_opt3 in 944 944 let expected3 = Ptime.of_date_time ((2068, 9, 10), ((20, 0, 0), 0)) in ··· 957 957 (* Ensure RFC 3339 format still works for backward compatibility *) 958 958 let header = "session=abc; Expires=2025-10-21T07:28:00Z" in 959 959 let cookie_opt = 960 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 960 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 961 961 in 962 962 Alcotest.(check bool) 963 963 "RFC 3339 cookie parsed" true ··· 984 984 (* Invalid date format should log a warning but still parse the cookie *) 985 985 let header = "session=abc; Expires=InvalidDate" in 986 986 let cookie_opt = 987 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 987 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 988 988 in 989 989 990 990 (* Cookie should still be parsed, just without expires *) ··· 1016 1016 List.iter 1017 1017 (fun (header, description) -> 1018 1018 let cookie_opt = 1019 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 1019 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 1020 1020 in 1021 1021 Alcotest.(check bool) 1022 1022 (description ^ " parsed") true ··· 1058 1058 List.iter 1059 1059 (fun (header, description) -> 1060 1060 let cookie_opt = 1061 - of_set_cookie_header ~clock ~domain:"example.com" ~path:"/" header 1061 + of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/" header 1062 1062 in 1063 1063 Alcotest.(check bool) 1064 1064 (description ^ " parsed") true ··· 1384 1384 let test_partitioned_parsing env = 1385 1385 let clock = Eio.Stdenv.clock env in 1386 1386 1387 - match of_set_cookie_header ~clock ~domain:"widget.com" ~path:"/" 1387 + match of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"widget.com" ~path:"/" 1388 1388 "id=123; Partitioned; Secure" with 1389 1389 | Some c -> 1390 1390 Alcotest.(check bool) "partitioned flag" true (partitioned c); ··· 1415 1415 let clock = Eio.Stdenv.clock env in 1416 1416 1417 1417 (* Partitioned without Secure should be rejected *) 1418 - match of_set_cookie_header ~clock ~domain:"widget.com" ~path:"/" 1418 + match of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"widget.com" ~path:"/" 1419 1419 "id=123; Partitioned" with 1420 1420 | None -> () (* Expected *) 1421 1421 | Some _ -> Alcotest.fail "Should reject Partitioned without Secure" ··· 1451 1451 let clock = Eio.Stdenv.clock env in 1452 1452 1453 1453 (* Expires=0 should parse as Session *) 1454 - match of_set_cookie_header ~clock ~domain:"ex.com" ~path:"/" 1454 + match of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" 1455 1455 "id=123; Expires=0" with 1456 1456 | Some c -> 1457 1457 Alcotest.(check (option expiration_testable)) "expires=0 is session" ··· 1497 1497 ] in 1498 1498 1499 1499 List.iter (fun (input, expected_raw, expected_trimmed) -> 1500 - match of_set_cookie_header ~clock ~domain:"ex.com" ~path:"/" input with 1500 + match of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" input with 1501 1501 | Some c -> 1502 1502 Alcotest.(check string) 1503 1503 (Printf.sprintf "raw value for %s" input) expected_raw (value c); ··· 1510 1510 let test_trimmed_value_not_used_for_equality env = 1511 1511 let clock = Eio.Stdenv.clock env in 1512 1512 1513 - match of_set_cookie_header ~clock ~domain:"ex.com" ~path:"/" 1513 + match of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" 1514 1514 "name=\"value\"" with 1515 1515 | Some c1 -> 1516 - begin match of_set_cookie_header ~clock ~domain:"ex.com" ~path:"/" 1516 + begin match of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" 1517 1517 "name=value" with 1518 1518 | Some c2 -> 1519 1519 (* Different raw values *) ··· 1530 1530 1531 1531 let test_cookie_header_parsing_basic env = 1532 1532 let clock = Eio.Stdenv.clock env in 1533 - let results = of_cookie_header ~clock ~domain:"ex.com" ~path:"/" 1533 + let results = of_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" 1534 1534 "session=abc123; theme=dark; lang=en" in 1535 1535 1536 1536 let cookies = List.filter_map Result.to_option results in ··· 1544 1544 let test_cookie_header_defaults env = 1545 1545 let clock = Eio.Stdenv.clock env in 1546 1546 1547 - match of_cookie_header ~clock ~domain:"example.com" ~path:"/app" 1547 + match of_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"example.com" ~path:"/app" 1548 1548 "session=xyz" with 1549 1549 | [Ok c] -> 1550 1550 (* Domain and path from request context *) ··· 1569 1569 let clock = Eio.Stdenv.clock env in 1570 1570 1571 1571 let test input expected_count description = 1572 - let results = of_cookie_header ~clock ~domain:"ex.com" ~path:"/" input in 1572 + let results = of_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" input in 1573 1573 let cookies = List.filter_map Result.to_option results in 1574 1574 Alcotest.(check int) description expected_count (List.length cookies) 1575 1575 in ··· 1584 1584 let clock = Eio.Stdenv.clock env in 1585 1585 1586 1586 (* Mix of valid and invalid cookies *) 1587 - let results = of_cookie_header ~clock ~domain:"ex.com" ~path:"/" 1587 + let results = of_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" 1588 1588 "valid=1;=noname;valid2=2" in 1589 1589 1590 1590 Alcotest.(check int) "total results" 3 (List.length results); ··· 1657 1657 let clock = Eio.Stdenv.clock env in 1658 1658 1659 1659 (* Parse Set-Cookie with both attributes *) 1660 - match of_set_cookie_header ~clock ~domain:"ex.com" ~path:"/" 1660 + match of_set_cookie_header ~now:(fun () -> Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch) ~domain:"ex.com" ~path:"/" 1661 1661 "id=123; Max-Age=3600; Expires=Wed, 21 Oct 2025 07:28:00 GMT" with 1662 1662 | Some c -> 1663 1663 (* Both should be stored *)