package dream
Source file cookie.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139(* This file is part of Dream, released under the MIT license. See LICENSE.md for details, or visit https://github.com/camlworks/dream. Copyright 2021 Anton Bachin *) module Cipher = Dream__cipher.Cipher module Formats = Dream_pure.Formats module Message = Dream_pure.Message (* TODO LATER Optimize by caching the parsed cookies in a local key. *) (* TODO LATER Can decide whether to accept multiple Cookie: headers based on request version. But that would entail an actual middleware - is that worth it? *) (* TODO LATER Also not efficient, at all. Need faster parser + the cache. *) (* TODO DOC Using only raw cookies. *) (* TODO However, is it best to URL-encode cookies by default, and provide a variable for opting out? *) (* TODO DOC We allow multiple headers sent by the client, to support HTTP/2. What is this about? *) let request = Message.headers request "Cookie" |> List.map Formats.from_cookie |> List.flatten let prefix domain path secure = match prefix, domain, path, secure with | Some (Some `Host), _, _, _ -> "__Host-" | Some (Some `Secure), _, _, _ -> "__Secure-" | Some None, _, _, _ -> "" | None, None, Some "/", true -> "__Host-" | None, _, _, true -> "__Secure-" | None, _, _, _ -> "" (* TODO Some actual performance in the implementation. *) let ?prefix: ?decrypt:( = true) ?domain ?path ?secure request name = let path = match path with | Some path -> path | None -> Some (Router.prefix request) in let secure = match secure with | Some secure -> secure | None -> Helpers.tls request in let = infer_cookie_prefix cookie_prefix domain path secure in let name = cookie_prefix ^ name in let test = fun (name', _) -> name = name' in match all_cookies request |> List.find_opt test with | None -> None | Some (_, value) -> if not decrypt_cookie then Some value else match Formats.from_base64url value with | None -> None | Some value -> Cipher.decrypt request value ~associated_data:("dream.cookie-" ^ name) let ?prefix: ?encrypt:( = true) ?expires ?max_age ?domain ?path ?secure ?(http_only = true) ?same_site response request name value = (* TODO Need the site prefix, not the subsite prefix! *) let path = match path with | Some path -> path | None -> Some (Router.prefix request) in let secure = match secure with | Some secure -> secure | None -> Helpers.tls request in let = infer_cookie_prefix cookie_prefix domain path secure in let same_site = match same_site with | None -> Some `Lax | Some None -> None | Some (Some `Strict) -> Some `Strict | Some (Some `Lax) -> Some `Lax | Some (Some `None) -> Some `None in let name = cookie_prefix ^ name in let value = if encrypt_cookie then (* Give each cookie name a different associated data "space," effectively partitioning valid ciphertexts among the cookies. See also https://github.com/camlworks/dream/issues/19#issuecomment-820250853. *) Cipher.encrypt request value ~associated_data:("dream.cookie-" ^ name) |> Formats.to_base64url else value in let = Formats.to_set_cookie ?expires ?max_age ?domain ?path ~secure ~http_only ?same_site name value in Message.add_header response "Set-Cookie" set_cookie let ?prefix ?domain ?path ?secure ?http_only ?same_site response request name = set_cookie ?prefix ~encrypt:false ~expires:0. ?domain ?path ?secure ?http_only ?same_site response request name ""