···
partitioned=%b; expires=%a; max_age=%a; same_site=%a }"
(Cookeio.name c) (Cookeio.value c) (Cookeio.domain c) (Cookeio.path c)
(Cookeio.secure c) (Cookeio.http_only c) (Cookeio.partitioned c)
22
-
(Format.pp_print_option
22
+
(Format.pp_print_option (fun ppf e ->
| `Session -> Format.pp_print_string ppf "Session"
| `DateTime t -> Format.fprintf ppf "DateTime(%a)" Ptime.pp t))
(Format.pp_print_option Ptime.Span.pp)
30
-
(Format.pp_print_option
31
-
(fun ppf -> function
32
-
| `Strict -> Format.pp_print_string ppf "Strict"
33
-
| `Lax -> Format.pp_print_string ppf "Lax"
34
-
| `None -> Format.pp_print_string ppf "None"))
29
+
(Format.pp_print_option (fun ppf -> function
30
+
| `Strict -> Format.pp_print_string ppf "Strict"
31
+
| `Lax -> Format.pp_print_string ppf "Lax"
32
+
| `None -> Format.pp_print_string ppf "None"))
let expires_equal e1 e2 =
···
begin match expected_expiry with
Alcotest.(check (option expiration_testable))
129
-
"cookie-3 expires" (Some (`DateTime t)) (Cookeio.expires cookie3)
128
+
(Some (`DateTime t))
129
+
(Cookeio.expires cookie3)
| None -> Alcotest.fail "Expected expiry time for cookie-3"
···
begin match expected_expiry with
Alcotest.(check (option expiration_testable))
145
-
"cookie-4 expires" (Some (`DateTime t)) (Cookeio.expires cookie4)
146
+
(Some (`DateTime t))
147
+
(Cookeio.expires cookie4)
| None -> Alcotest.fail "Expected expiry time for cookie-4"
···
begin match expected_expiry with
Alcotest.(check (option expiration_testable))
161
-
"cookie-5 expires" (Some (`DateTime t)) (Cookeio.expires cookie5)
164
+
(Some (`DateTime t))
165
+
(Cookeio.expires cookie5)
| None -> Alcotest.fail "Expected expiry time for cookie-5"
···
begin match expected_expiry with
Alcotest.(check (option expiration_testable))
194
-
"file cookie-5 expires" (Some (`DateTime t)) (Cookeio.expires cookie5)
198
+
"file cookie-5 expires"
199
+
(Some (`DateTime t))
200
+
(Cookeio.expires cookie5)
| None -> Alcotest.fail "Expected expiry time for cookie-5"
···
begin match Ptime.of_float_s 1257894000.0 with
Alcotest.(check (option expiration_testable))
295
-
"round trip expires" (Some (`DateTime t)) (Cookeio.expires cookie2)
301
+
"round trip expires"
302
+
(Some (`DateTime t))
303
+
(Cookeio.expires cookie2)
| None -> Alcotest.fail "Expected expiry time"
···
(* Parse a Set-Cookie header with Max-Age *)
let header = "session=abc123; Max-Age=3600; Secure; HttpOnly" in
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
387
+
of_set_cookie_header
389
+
Ptime.of_float_s (Eio.Time.now clock)
390
+
|> Option.value ~default:Ptime.epoch)
391
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt);
···
begin match expected_expiry with
Alcotest.(check (option expiration_testable))
395
-
"expires set from max-age" (Some (`DateTime t)) (Cookeio.expires cookie)
407
+
"expires set from max-age"
408
+
(Some (`DateTime t))
409
+
(Cookeio.expires cookie)
| None -> Alcotest.fail "Expected expiry time"
···
"id=xyz789; Expires=2025-10-21T07:28:00Z; Path=/; Domain=.example.com"
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
472
+
of_set_cookie_header
474
+
Ptime.of_float_s (Eio.Time.now clock)
475
+
|> Option.value ~default:Ptime.epoch)
476
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt);
···
match expected_expiry with
Alcotest.(check (option expiration_testable))
479
-
"expires matches parsed value" (Some (`DateTime time))
497
+
"expires matches parsed value"
498
+
(Some (`DateTime time))
| Error _ -> Alcotest.fail "Failed to parse expected expiry time"
···
(* This should be rejected: SameSite=None without Secure *)
let invalid_header = "token=abc; SameSite=None" in
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
512
+
of_set_cookie_header
514
+
Ptime.of_float_s (Eio.Time.now clock)
515
+
|> Option.value ~default:Ptime.epoch)
516
+
~domain:"example.com" ~path:"/" invalid_header
···
(* This should be accepted: SameSite=None with Secure *)
let valid_header = "token=abc; SameSite=None; Secure" in
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
526
+
of_set_cookie_header
528
+
Ptime.of_float_s (Eio.Time.now clock)
529
+
|> Option.value ~default:Ptime.epoch)
530
+
~domain:"example.com" ~path:"/" valid_header
···
(* Test parsing ".example.com" stores as "example.com" *)
let header = "test=value; Domain=.example.com" in
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
558
+
of_set_cookie_header
560
+
Ptime.of_float_s (Eio.Time.now clock)
561
+
|> Option.value ~default:Ptime.epoch)
562
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt);
let cookie = Option.get cookie_opt in
···
(* Parse a Set-Cookie header with Max-Age *)
let header = "session=abc123; Max-Age=3600" in
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
596
+
of_set_cookie_header
598
+
Ptime.of_float_s (Eio.Time.now clock)
599
+
|> Option.value ~default:Ptime.epoch)
600
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt);
···
begin match expected_expiry with
Alcotest.(check (option expiration_testable))
585
-
"expires computed from max-age" (Some (`DateTime t))
620
+
"expires computed from max-age"
621
+
(Some (`DateTime t))
| None -> Alcotest.fail "Expected expiry time"
···
(* Parse a Set-Cookie header with negative Max-Age *)
let header = "session=abc123; Max-Age=-100" in
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
634
+
of_set_cookie_header
636
+
Ptime.of_float_s (Eio.Time.now clock)
637
+
|> Option.value ~default:Ptime.epoch)
638
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt);
···
begin match expected_expiry with
Alcotest.(check (option expiration_testable))
618
-
"expires computed with 0 seconds" (Some (`DateTime t))
658
+
"expires computed with 0 seconds"
659
+
(Some (`DateTime t))
| None -> Alcotest.fail "Expected expiry time"
···
let expires_time = Ptime.of_float_s 8600.0 |> Option.get in
Cookeio.make ~domain:"example.com" ~path:"/" ~name:"session" ~value:"abc123"
644
-
~secure:true ~http_only:true ?expires:(Some (`DateTime expires_time))
685
+
~secure:true ~http_only:true
686
+
?expires:(Some (`DateTime expires_time))
?max_age:(Some max_age_span) ?same_site:(Some `Strict)
~creation_time:(Ptime.of_float_s 5000.0 |> Option.get)
~last_access:(Ptime.of_float_s 5000.0 |> Option.get)
···
(* Parse a cookie with Max-Age *)
let header = "session=xyz; Max-Age=7200; Secure; HttpOnly" in
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
724
+
of_set_cookie_header
726
+
Ptime.of_float_s (Eio.Time.now clock)
727
+
|> Option.value ~default:Ptime.epoch)
728
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "cookie parsed" true (Option.is_some cookie_opt);
let cookie = Option.get cookie_opt in
···
Eio_mock.Clock.set_time clock 5000.0;
(* Reset clock to same time *)
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
740
+
of_set_cookie_header
742
+
Ptime.of_float_s (Eio.Time.now clock)
743
+
|> Option.value ~default:Ptime.epoch)
744
+
~domain:"example.com" ~path:"/" set_cookie_header
Alcotest.(check bool) "cookie re-parsed" true (Option.is_some cookie2_opt);
let cookie2 = Option.get cookie2_opt in
···
(* Test FMT1: "Wed, 21 Oct 2015 07:28:00 GMT" (RFC 1123) *)
let header = "session=abc; Expires=Wed, 21 Oct 2015 07:28:00 GMT" in
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
813
+
of_set_cookie_header
815
+
Ptime.of_float_s (Eio.Time.now clock)
816
+
|> Option.value ~default:Ptime.epoch)
817
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "FMT1 cookie parsed" true (Option.is_some cookie_opt);
···
begin match expected with
Alcotest.(check (option expiration_testable))
777
-
"FMT1 expiry correct" (Some (`DateTime t)) (Cookeio.expires cookie)
831
+
"FMT1 expiry correct"
832
+
(Some (`DateTime t))
833
+
(Cookeio.expires cookie)
| None -> Alcotest.fail "Expected expiry time for FMT1"
···
(* Test FMT2: "Wednesday, 21-Oct-15 07:28:00 GMT" (RFC 850 with abbreviated year) *)
let header = "session=abc; Expires=Wednesday, 21-Oct-15 07:28:00 GMT" in
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
845
+
of_set_cookie_header
847
+
Ptime.of_float_s (Eio.Time.now clock)
848
+
|> Option.value ~default:Ptime.epoch)
849
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "FMT2 cookie parsed" true (Option.is_some cookie_opt);
···
begin match expected with
Alcotest.(check (option expiration_testable))
803
-
"FMT2 expiry correct with year normalization" (Some (`DateTime t))
863
+
"FMT2 expiry correct with year normalization"
864
+
(Some (`DateTime t))
| None -> Alcotest.fail "Expected expiry time for FMT2"
···
(* Test FMT3: "Wed Oct 21 07:28:00 2015" (asctime) *)
let header = "session=abc; Expires=Wed Oct 21 07:28:00 2015" in
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
877
+
of_set_cookie_header
879
+
Ptime.of_float_s (Eio.Time.now clock)
880
+
|> Option.value ~default:Ptime.epoch)
881
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "FMT3 cookie parsed" true (Option.is_some cookie_opt);
···
begin match expected with
Alcotest.(check (option expiration_testable))
829
-
"FMT3 expiry correct" (Some (`DateTime t)) (Cookeio.expires cookie)
894
+
"FMT3 expiry correct"
895
+
(Some (`DateTime t))
896
+
(Cookeio.expires cookie)
| None -> Alcotest.fail "Expected expiry time for FMT3"
···
(* Test FMT4: "Wed, 21-Oct-2015 07:28:00 GMT" (variant) *)
let header = "session=abc; Expires=Wed, 21-Oct-2015 07:28:00 GMT" in
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
908
+
of_set_cookie_header
910
+
Ptime.of_float_s (Eio.Time.now clock)
911
+
|> Option.value ~default:Ptime.epoch)
912
+
~domain:"example.com" ~path:"/" header
Alcotest.(check bool) "FMT4 cookie parsed" true (Option.is_some cookie_opt);
···
begin match expected with
Alcotest.(check (option expiration_testable))
854
-
"FMT4 expiry correct" (Some (`DateTime t)) (Cookeio.expires cookie)
925
+
"FMT4 expiry correct"
926
+
(Some (`DateTime t))
927
+
(Cookeio.expires cookie)
| None -> Alcotest.fail "Expected expiry time for FMT4"
···
(* Year 95 should become 1995 *)
let header = "session=abc; Expires=Wed, 21-Oct-95 07:28:00 GMT" in
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
939
+
of_set_cookie_header
941
+
Ptime.of_float_s (Eio.Time.now clock)
942
+
|> Option.value ~default:Ptime.epoch)
943
+
~domain:"example.com" ~path:"/" header
let cookie = Option.get cookie_opt in
let expected = Ptime.of_date_time ((1995, 10, 21), ((07, 28, 00), 0)) in
begin match expected with
Alcotest.(check (option expiration_testable))
873
-
"year 95 becomes 1995" (Some (`DateTime t)) (Cookeio.expires cookie)
950
+
"year 95 becomes 1995"
951
+
(Some (`DateTime t))
952
+
(Cookeio.expires cookie)
| None -> Alcotest.fail "Expected expiry time for year 95"
(* Year 69 should become 1969 *)
let header2 = "session=abc; Expires=Wed, 10-Sep-69 20:00:00 GMT" in
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
959
+
of_set_cookie_header
961
+
Ptime.of_float_s (Eio.Time.now clock)
962
+
|> Option.value ~default:Ptime.epoch)
963
+
~domain:"example.com" ~path:"/" header2
let cookie2 = Option.get cookie_opt2 in
let expected2 = Ptime.of_date_time ((1969, 9, 10), ((20, 0, 0), 0)) in
begin match expected2 with
Alcotest.(check (option expiration_testable))
887
-
"year 69 becomes 1969" (Some (`DateTime t)) (Cookeio.expires cookie2)
970
+
"year 69 becomes 1969"
971
+
(Some (`DateTime t))
972
+
(Cookeio.expires cookie2)
| None -> Alcotest.fail "Expected expiry time for year 69"
(* Year 99 should become 1999 *)
let header3 = "session=abc; Expires=Thu, 10-Sep-99 20:00:00 GMT" in
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
979
+
of_set_cookie_header
981
+
Ptime.of_float_s (Eio.Time.now clock)
982
+
|> Option.value ~default:Ptime.epoch)
983
+
~domain:"example.com" ~path:"/" header3
let cookie3 = Option.get cookie_opt3 in
let expected3 = Ptime.of_date_time ((1999, 9, 10), ((20, 0, 0), 0)) in
begin match expected3 with
Alcotest.(check (option expiration_testable))
901
-
"year 99 becomes 1999" (Some (`DateTime t)) (Cookeio.expires cookie3)
990
+
"year 99 becomes 1999"
991
+
(Some (`DateTime t))
992
+
(Cookeio.expires cookie3)
| None -> Alcotest.fail "Expected expiry time for year 99"
···
(* Year 25 should become 2025 *)
let header = "session=abc; Expires=Wed, 21-Oct-25 07:28:00 GMT" in
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
1004
+
of_set_cookie_header
1006
+
Ptime.of_float_s (Eio.Time.now clock)
1007
+
|> Option.value ~default:Ptime.epoch)
1008
+
~domain:"example.com" ~path:"/" header
let cookie = Option.get cookie_opt in
let expected = Ptime.of_date_time ((2025, 10, 21), ((07, 28, 00), 0)) in
begin match expected with
Alcotest.(check (option expiration_testable))
920
-
"year 25 becomes 2025" (Some (`DateTime t)) (Cookeio.expires cookie)
1015
+
"year 25 becomes 2025"
1016
+
(Some (`DateTime t))
1017
+
(Cookeio.expires cookie)
| None -> Alcotest.fail "Expected expiry time for year 25"
(* Year 0 should become 2000 *)
let header2 = "session=abc; Expires=Fri, 01-Jan-00 00:00:00 GMT" in
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
1024
+
of_set_cookie_header
1026
+
Ptime.of_float_s (Eio.Time.now clock)
1027
+
|> Option.value ~default:Ptime.epoch)
1028
+
~domain:"example.com" ~path:"/" header2
let cookie2 = Option.get cookie_opt2 in
let expected2 = Ptime.of_date_time ((2000, 1, 1), ((0, 0, 0), 0)) in
begin match expected2 with
Alcotest.(check (option expiration_testable))
934
-
"year 0 becomes 2000" (Some (`DateTime t)) (Cookeio.expires cookie2)
1035
+
"year 0 becomes 2000"
1036
+
(Some (`DateTime t))
1037
+
(Cookeio.expires cookie2)
| None -> Alcotest.fail "Expected expiry time for year 0"
(* Year 68 should become 2068 *)
let header3 = "session=abc; Expires=Thu, 10-Sep-68 20:00:00 GMT" in
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
1044
+
of_set_cookie_header
1046
+
Ptime.of_float_s (Eio.Time.now clock)
1047
+
|> Option.value ~default:Ptime.epoch)
1048
+
~domain:"example.com" ~path:"/" header3
let cookie3 = Option.get cookie_opt3 in
let expected3 = Ptime.of_date_time ((2068, 9, 10), ((20, 0, 0), 0)) in
begin match expected3 with
Alcotest.(check (option expiration_testable))
948
-
"year 68 becomes 2068" (Some (`DateTime t)) (Cookeio.expires cookie3)
1055
+
"year 68 becomes 2068"
1056
+
(Some (`DateTime t))
1057
+
(Cookeio.expires cookie3)
| None -> Alcotest.fail "Expected expiry time for year 68"
···
(* Ensure RFC 3339 format still works for backward compatibility *)
let header = "session=abc; Expires=2025-10-21T07:28:00Z" in
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
1069
+
of_set_cookie_header
1071
+
Ptime.of_float_s (Eio.Time.now clock)
1072
+
|> Option.value ~default:Ptime.epoch)
1073
+
~domain:"example.com" ~path:"/" header
"RFC 3339 cookie parsed" true
···
Alcotest.(check (option expiration_testable))
976
-
"RFC 3339 expiry correct" (Some (`DateTime time)) (Cookeio.expires cookie)
1089
+
"RFC 3339 expiry correct"
1090
+
(Some (`DateTime time))
1091
+
(Cookeio.expires cookie)
| Error _ -> Alcotest.fail "Failed to parse expected RFC 3339 time"
let test_invalid_date_format_logs_warning () =
···
(* Invalid date format should log a warning but still parse the cookie *)
let header = "session=abc; Expires=InvalidDate" in
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
1102
+
of_set_cookie_header
1104
+
Ptime.of_float_s (Eio.Time.now clock)
1105
+
|> Option.value ~default:Ptime.epoch)
1106
+
~domain:"example.com" ~path:"/" header
(* Cookie should still be parsed, just without expires *)
···
(fun (header, description) ->
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
1138
+
of_set_cookie_header
1140
+
Ptime.of_float_s (Eio.Time.now clock)
1141
+
|> Option.value ~default:Ptime.epoch)
1142
+
~domain:"example.com" ~path:"/" header
(description ^ " parsed") true
···
(fun (header, description) ->
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
1184
+
of_set_cookie_header
1186
+
Ptime.of_float_s (Eio.Time.now clock)
1187
+
|> Option.value ~default:Ptime.epoch)
1188
+
~domain:"example.com" ~path:"/" header
(description ^ " parsed") true
···
let test_partitioned_parsing env =
let clock = Eio.Stdenv.clock env in
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
-
"id=123; Partitioned; Secure" with
1515
+
of_set_cookie_header
1517
+
Ptime.of_float_s (Eio.Time.now clock)
1518
+
|> Option.value ~default:Ptime.epoch)
1519
+
~domain:"widget.com" ~path:"/" "id=123; Partitioned; Secure"
Alcotest.(check bool) "partitioned flag" true (partitioned c);
Alcotest.(check bool) "secure flag" true (secure c)
···
let test_partitioned_serialization env =
let clock = Eio.Stdenv.clock env in
1396
-
let now = Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch in
1529
+
Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch
1398
-
let cookie = make ~domain:"widget.com" ~path:"/" ~name:"id" ~value:"123"
1399
-
~secure:true ~partitioned:true
1400
-
~creation_time:now ~last_access:now () in
1533
+
make ~domain:"widget.com" ~path:"/" ~name:"id" ~value:"123" ~secure:true
1534
+
~partitioned:true ~creation_time:now ~last_access:now ()
let header = make_set_cookie_header cookie in
let contains_substring s sub =
···
let clock = Eio.Stdenv.clock env in
(* Partitioned without Secure should be rejected *)
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
-
"id=123; Partitioned" with
1554
+
of_set_cookie_header
1556
+
Ptime.of_float_s (Eio.Time.now clock)
1557
+
|> Option.value ~default:Ptime.epoch)
1558
+
~domain:"widget.com" ~path:"/" "id=123; Partitioned"
| None -> () (* Expected *)
| Some _ -> Alcotest.fail "Should reject Partitioned without Secure"
···
let test_expiration_variants env =
let clock = Eio.Stdenv.clock env in
1427
-
let now = Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch in
1568
+
Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch
let make_base ~name ?expires () =
1429
-
make ~domain:"ex.com" ~path:"/" ~name ~value:"v"
1430
-
?expires ~creation_time:now ~last_access:now ()
1571
+
make ~domain:"ex.com" ~path:"/" ~name ~value:"v" ?expires ~creation_time:now
1572
+
~last_access:now ()
let c1 = make_base ~name:"no_expiry" () in
1435
-
Alcotest.(check (option expiration_testable)) "no expiration"
1436
-
None (expires c1);
1577
+
Alcotest.(check (option expiration_testable))
1578
+
"no expiration" None (expires c1);
let c2 = make_base ~name:"session" ~expires:`Session () in
1440
-
Alcotest.(check (option expiration_testable)) "session cookie"
1441
-
(Some `Session) (expires c2);
1582
+
Alcotest.(check (option expiration_testable))
1583
+
"session cookie" (Some `Session) (expires c2);
(* Explicit expiration *)
let future = Ptime.add_span now (Ptime.Span.of_int_s 3600) |> Option.get in
···
let clock = Eio.Stdenv.clock env in
(* Expires=0 should parse as Session *)
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
-
"id=123; Expires=0" with
1597
+
of_set_cookie_header
1599
+
Ptime.of_float_s (Eio.Time.now clock)
1600
+
|> Option.value ~default:Ptime.epoch)
1601
+
~domain:"ex.com" ~path:"/" "id=123; Expires=0"
1457
-
Alcotest.(check (option expiration_testable)) "expires=0 is session"
1458
-
(Some `Session) (expires c)
1604
+
Alcotest.(check (option expiration_testable))
1605
+
"expires=0 is session" (Some `Session) (expires c)
| None -> Alcotest.fail "Should parse Expires=0"
let test_serialize_expiration_variants env =
let clock = Eio.Stdenv.clock env in
1463
-
let now = Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch in
1611
+
Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch
let contains_substring s sub =
let _ = Str.search_forward (Str.regexp_string sub) s 0 in
···
(* Session cookie serialization *)
1472
-
let c1 = make ~domain:"ex.com" ~path:"/" ~name:"s" ~value:"v"
1473
-
~expires:`Session ~creation_time:now ~last_access:now () in
1622
+
make ~domain:"ex.com" ~path:"/" ~name:"s" ~value:"v" ~expires:`Session
1623
+
~creation_time:now ~last_access:now ()
let h1 = make_set_cookie_header c1 in
let has_expires = contains_substring h1 "Expires=" in
Alcotest.(check bool) "session has Expires" true has_expires;
(* DateTime serialization *)
let future = Ptime.add_span now (Ptime.Span.of_int_s 3600) |> Option.get in
1480
-
let c2 = make ~domain:"ex.com" ~path:"/" ~name:"p" ~value:"v"
1481
-
~expires:(`DateTime future) ~creation_time:now ~last_access:now () in
1632
+
make ~domain:"ex.com" ~path:"/" ~name:"p" ~value:"v"
1633
+
~expires:(`DateTime future) ~creation_time:now ~last_access:now ()
let h2 = make_set_cookie_header c2 in
let has_expires2 = contains_substring h2 "Expires=" in
Alcotest.(check bool) "datetime has Expires" true has_expires2
···
let test_quoted_cookie_values env =
let clock = Eio.Stdenv.clock env in
1490
-
let test_cases = [
1491
-
("name=value", "value", "value");
1492
-
("name=\"value\"", "\"value\"", "value");
1493
-
("name=\"partial", "\"partial", "\"partial");
1494
-
("name=\"val\"\"", "\"val\"\"", "val\"");
1495
-
("name=val\"", "val\"", "val\"");
1496
-
("name=\"\"", "\"\"", "");
1645
+
("name=value", "value", "value");
1646
+
("name=\"value\"", "\"value\"", "value");
1647
+
("name=\"partial", "\"partial", "\"partial");
1648
+
("name=\"val\"\"", "\"val\"\"", "val\"");
1649
+
("name=val\"", "val\"", "val\"");
1650
+
("name=\"\"", "\"\"", "");
1499
-
List.iter (fun (input, expected_raw, expected_trimmed) ->
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
1502
-
Alcotest.(check string)
1503
-
(Printf.sprintf "raw value for %s" input) expected_raw (value c);
1504
-
Alcotest.(check string)
1505
-
(Printf.sprintf "trimmed value for %s" input) expected_trimmed
1507
-
| None -> Alcotest.fail ("Parse failed: " ^ input)
1655
+
(fun (input, expected_raw, expected_trimmed) ->
1657
+
of_set_cookie_header
1659
+
Ptime.of_float_s (Eio.Time.now clock)
1660
+
|> Option.value ~default:Ptime.epoch)
1661
+
~domain:"ex.com" ~path:"/" input
1664
+
Alcotest.(check string)
1665
+
(Printf.sprintf "raw value for %s" input)
1666
+
expected_raw (value c);
1667
+
Alcotest.(check string)
1668
+
(Printf.sprintf "trimmed value for %s" input)
1669
+
expected_trimmed (value_trimmed c)
1670
+
| None -> Alcotest.fail ("Parse failed: " ^ input))
let test_trimmed_value_not_used_for_equality env =
let clock = Eio.Stdenv.clock env in
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
-
"name=\"value\"" with
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:"/"
1677
+
of_set_cookie_header
1679
+
Ptime.of_float_s (Eio.Time.now clock)
1680
+
|> Option.value ~default:Ptime.epoch)
1681
+
~domain:"ex.com" ~path:"/" "name=\"value\""
1683
+
| Some c1 -> begin
1685
+
of_set_cookie_header
1687
+
Ptime.of_float_s (Eio.Time.now clock)
1688
+
|> Option.value ~default:Ptime.epoch)
1689
+
~domain:"ex.com" ~path:"/" "name=value"
(* Different raw values *)
1520
-
Alcotest.(check bool) "different raw values" false
1693
+
Alcotest.(check bool)
1694
+
"different raw values" false
(* Same trimmed values *)
1523
-
Alcotest.(check string) "same trimmed values"
1524
-
(value_trimmed c1) (value_trimmed c2)
1697
+
Alcotest.(check string)
1698
+
"same trimmed values" (value_trimmed c1) (value_trimmed c2)
| None -> Alcotest.fail "Parse failed for unquoted"
| None -> Alcotest.fail "Parse failed for quoted"
(* Priority 2.4: Cookie Header Parsing *)
let test_cookie_header_parsing_basic env =
let clock = Eio.Stdenv.clock env in
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
-
"session=abc123; theme=dark; lang=en" in
1710
+
Ptime.of_float_s (Eio.Time.now clock)
1711
+
|> Option.value ~default:Ptime.epoch)
1712
+
~domain:"ex.com" ~path:"/" "session=abc123; theme=dark; lang=en"
let cookies = List.filter_map Result.to_option results in
Alcotest.(check int) "parsed 3 cookies" 3 (List.length cookies);
···
let test_cookie_header_defaults env =
let clock = Eio.Stdenv.clock env in
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
-
"session=xyz" with
1729
+
Ptime.of_float_s (Eio.Time.now clock)
1730
+
|> Option.value ~default:Ptime.epoch)
1731
+
~domain:"example.com" ~path:"/app" "session=xyz"
(* Domain and path from request context *)
Alcotest.(check string) "domain from context" "example.com" (domain c);
Alcotest.(check string) "path from context" "/app" (path c);
···
Alcotest.(check bool) "partitioned default" false (partitioned c);
(* Optional attributes default to None *)
1560
-
Alcotest.(check (option expiration_testable)) "no expiration"
1562
-
Alcotest.(check (option span_testable)) "no max_age"
1564
-
Alcotest.(check (option same_site_testable)) "no same_site"
1565
-
None (same_site c)
1744
+
Alcotest.(check (option expiration_testable))
1745
+
"no expiration" None (expires c);
1746
+
Alcotest.(check (option span_testable)) "no max_age" None (max_age c);
1747
+
Alcotest.(check (option same_site_testable))
1748
+
"no same_site" None (same_site c)
| _ -> Alcotest.fail "Should parse single cookie"
let test_cookie_header_edge_cases env =
let clock = Eio.Stdenv.clock env in
let test input expected_count description =
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
1758
+
Ptime.of_float_s (Eio.Time.now clock)
1759
+
|> Option.value ~default:Ptime.epoch)
1760
+
~domain:"ex.com" ~path:"/" input
let cookies = List.filter_map Result.to_option results in
Alcotest.(check int) description expected_count (List.length cookies)
···
let clock = Eio.Stdenv.clock env in
(* Mix of valid and invalid cookies *)
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
-
"valid=1;=noname;valid2=2" in
1779
+
Ptime.of_float_s (Eio.Time.now clock)
1780
+
|> Option.value ~default:Ptime.epoch)
1781
+
~domain:"ex.com" ~path:"/" "valid=1;=noname;valid2=2"
Alcotest.(check int) "total results" 3 (List.length results);
···
let has_name = contains_substring msg "name" in
let has_empty = contains_substring msg "empty" in
1609
-
Alcotest.(check bool) "error mentions name or empty" true
1610
-
(has_name || has_empty)
1803
+
Alcotest.(check bool)
1804
+
"error mentions name or empty" true (has_name || has_empty)
| Ok _ -> Alcotest.fail "Expected error"
···
let test_max_age_and_expires_both_present env =
let clock = Eio.Stdenv.clock env in
1618
-
let now = Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch in
1813
+
Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:Ptime.epoch
let future = Ptime.add_span now (Ptime.Span.of_int_s 7200) |> Option.get in
(* Create cookie with both *)
1622
-
let cookie = make ~domain:"ex.com" ~path:"/" ~name:"dual" ~value:"val"
1623
-
~max_age:(Ptime.Span.of_int_s 3600)
1624
-
~expires:(`DateTime future)
1625
-
~creation_time:now ~last_access:now () in
1819
+
make ~domain:"ex.com" ~path:"/" ~name:"dual" ~value:"val"
1820
+
~max_age:(Ptime.Span.of_int_s 3600) ~expires:(`DateTime future)
1821
+
~creation_time:now ~last_access:now ()
(* Both should be present *)
begin match max_age cookie with
1630
-
begin match Ptime.Span.to_int_s span with
1826
+
| Some span -> begin
1827
+
match Ptime.Span.to_int_s span with
Alcotest.(check int64) "max_age present" 3600L (Int64.of_int s)
| None -> Alcotest.fail "max_age span could not be converted to int"
| None -> Alcotest.fail "max_age should be present"
···
let clock = Eio.Stdenv.clock env in
(* Parse Set-Cookie with both attributes *)
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
-
"id=123; Max-Age=3600; Expires=Wed, 21 Oct 2025 07:28:00 GMT" with
1858
+
of_set_cookie_header
1860
+
Ptime.of_float_s (Eio.Time.now clock)
1861
+
|> Option.value ~default:Ptime.epoch)
1862
+
~domain:"ex.com" ~path:"/"
1863
+
"id=123; Max-Age=3600; Expires=Wed, 21 Oct 2025 07:28:00 GMT"
(* Both should be stored *)
begin match max_age c with
1666
-
begin match Ptime.Span.to_int_s span with
1868
+
| Some span -> begin
1869
+
match Ptime.Span.to_int_s span with
Alcotest.(check int64) "max_age parsed" 3600L (Int64.of_int s)
| None -> Alcotest.fail "max_age span could not be converted to int"
| None -> Alcotest.fail "max_age should be parsed"