this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more

+51 -507
-382
stack/requests/lib/cookie_jar.ml
··· 1 - let src = Logs.Src.create "requests.cookie_jar" ~doc:"HTTP Cookie Jar" 2 - module Log = (val Logs.src_log src : Logs.LOG) 3 - 4 - (** Cookie same-site policy *) 5 - type same_site = [`Strict | `Lax | `None] 6 - 7 - (** HTTP Cookie *) 8 - type cookie = { 9 - domain : string; 10 - path : string; 11 - name : string; 12 - value : string; 13 - secure : bool; 14 - http_only : bool; 15 - expires : Ptime.t option; 16 - same_site : same_site option; 17 - creation_time : Ptime.t; 18 - last_access : Ptime.t; 19 - } 20 - 21 - (** Cookie jar for storing and managing cookies *) 22 - type t = { 23 - mutable cookies : cookie list; 24 - mutex : Eio.Mutex.t; 25 - } 26 - 27 - (** {1 Creation} *) 28 - 29 - let create () = 30 - Log.debug (fun m -> m "Creating new empty cookie jar"); 31 - { cookies = []; mutex = Eio.Mutex.create () } 32 - 33 - (** {1 Cookie Matching Helpers} *) 34 - 35 - let domain_matches cookie_domain request_domain = 36 - (* Cookie domain .example.com matches example.com and sub.example.com *) 37 - if String.starts_with ~prefix:"." cookie_domain then 38 - let domain = String.sub cookie_domain 1 (String.length cookie_domain - 1) in 39 - request_domain = domain || 40 - String.ends_with ~suffix:("." ^ domain) request_domain 41 - else 42 - cookie_domain = request_domain 43 - 44 - let path_matches cookie_path request_path = 45 - (* Cookie path /foo matches /foo, /foo/, /foo/bar *) 46 - String.starts_with ~prefix:cookie_path request_path 47 - 48 - let is_expired cookie clock = 49 - match cookie.expires with 50 - | None -> false (* Session cookie *) 51 - | Some exp_time -> 52 - let now = Ptime.of_float_s (Eio.Time.now clock) |> Option.value ~default:(Ptime.epoch) in 53 - Ptime.compare now exp_time > 0 54 - 55 - (** {1 Cookie Parsing} *) 56 - 57 - let parse_cookie_attribute ~url:_ attr value cookie = 58 - let attr_lower = String.lowercase_ascii attr in 59 - match attr_lower with 60 - | "domain" -> { cookie with domain = value } 61 - | "path" -> { cookie with path = value } 62 - | "expires" -> 63 - (* Parse various date formats *) 64 - (try 65 - let time, _tz_offset, _tz_string = Ptime.of_rfc3339 value |> Result.get_ok in 66 - { cookie with expires = Some time } 67 - with _ -> 68 - Log.debug (fun m -> m "Failed to parse expires: %s" value); 69 - cookie) 70 - | "max-age" -> 71 - (try 72 - let seconds = int_of_string value in 73 - let now = Unix.time () in 74 - let expires = Ptime.of_float_s (now +. float_of_int seconds) in 75 - { cookie with expires } 76 - with _ -> cookie) 77 - | "secure" -> { cookie with secure = true } 78 - | "httponly" -> { cookie with http_only = true } 79 - | "samesite" -> 80 - let same_site = match String.lowercase_ascii value with 81 - | "strict" -> Some `Strict 82 - | "lax" -> Some `Lax 83 - | "none" -> Some `None 84 - | _ -> None 85 - in 86 - { cookie with same_site } 87 - | _ -> cookie 88 - 89 - let rec parse_set_cookie ~url value = 90 - Log.debug (fun m -> m "Parsing Set-Cookie: %s" value); 91 - 92 - let uri = Uri.of_string url in 93 - let default_domain = Uri.host_with_default ~default:"localhost" uri in 94 - let default_path = 95 - let p = Uri.path uri in 96 - if p = "" then "/" 97 - else 98 - let last_slash = String.rindex_opt p '/' in 99 - match last_slash with 100 - | None -> "/" 101 - | Some i -> String.sub p 0 (i + 1) 102 - in 103 - 104 - (* Split into attributes *) 105 - let parts = String.split_on_char ';' value |> List.map String.trim in 106 - 107 - match parts with 108 - | [] -> None 109 - | name_value :: attrs -> 110 - (* Parse name=value *) 111 - (match String.index_opt name_value '=' with 112 - | None -> None 113 - | Some eq_pos -> 114 - let name = String.sub name_value 0 eq_pos |> String.trim in 115 - let value = String.sub name_value (eq_pos + 1) 116 - (String.length name_value - eq_pos - 1) |> String.trim in 117 - 118 - let now = Ptime.of_float_s (Unix.time ()) |> Option.value ~default:Ptime.epoch in 119 - let base_cookie = { 120 - name; 121 - value; 122 - domain = default_domain; 123 - path = default_path; 124 - secure = false; 125 - http_only = false; 126 - expires = None; 127 - same_site = None; 128 - creation_time = now; 129 - last_access = now; 130 - } in 131 - 132 - (* Parse attributes *) 133 - let cookie = List.fold_left (fun cookie attr -> 134 - match String.index_opt attr '=' with 135 - | None -> parse_cookie_attribute ~url attr "" cookie 136 - | Some eq -> 137 - let attr_name = String.sub attr 0 eq |> String.trim in 138 - let attr_value = String.sub attr (eq + 1) 139 - (String.length attr - eq - 1) |> String.trim in 140 - parse_cookie_attribute ~url attr_name attr_value cookie 141 - ) base_cookie attrs in 142 - 143 - Log.debug (fun m -> m "Parsed cookie: %a" pp_cookie cookie); 144 - Some cookie) 145 - 146 - and make_cookie_header cookies = 147 - cookies 148 - |> List.map (fun c -> Printf.sprintf "%s=%s" c.name c.value) 149 - |> String.concat "; " 150 - 151 - (** {1 Pretty Printing} *) 152 - 153 - and pp_same_site ppf = function 154 - | `Strict -> Format.pp_print_string ppf "Strict" 155 - | `Lax -> Format.pp_print_string ppf "Lax" 156 - | `None -> Format.pp_print_string ppf "None" 157 - 158 - and pp_cookie ppf cookie = 159 - Format.fprintf ppf "@[<hov 2>{ name=%S;@ value=%S;@ domain=%S;@ path=%S;@ \ 160 - secure=%b;@ http_only=%b;@ expires=%a;@ same_site=%a }@]" 161 - cookie.name 162 - cookie.value 163 - cookie.domain 164 - cookie.path 165 - cookie.secure 166 - cookie.http_only 167 - (Format.pp_print_option Ptime.pp) cookie.expires 168 - (Format.pp_print_option pp_same_site) cookie.same_site 169 - 170 - let pp ppf t = 171 - Eio.Mutex.lock t.mutex; 172 - let cookies = t.cookies in 173 - Eio.Mutex.unlock t.mutex; 174 - 175 - Format.fprintf ppf "@[<v>CookieJar with %d cookies:@," (List.length cookies); 176 - List.iter (fun cookie -> 177 - Format.fprintf ppf " %a@," pp_cookie cookie 178 - ) cookies; 179 - Format.fprintf ppf "@]" 180 - 181 - (** {1 Cookie Management} *) 182 - 183 - let add_cookie t cookie = 184 - Log.debug (fun m -> m "Adding cookie: %s=%s for domain %s" 185 - cookie.name cookie.value cookie.domain); 186 - 187 - Eio.Mutex.lock t.mutex; 188 - (* Remove existing cookie with same name, domain, and path *) 189 - t.cookies <- List.filter (fun c -> 190 - not (c.name = cookie.name && c.domain = cookie.domain && c.path = cookie.path) 191 - ) t.cookies; 192 - t.cookies <- cookie :: t.cookies; 193 - Eio.Mutex.unlock t.mutex 194 - 195 - let extract_from_headers t ~url headers = 196 - Log.debug (fun m -> m "Extracting cookies from headers for URL: %s" url); 197 - 198 - let set_cookie_values = Headers.get_multi "set-cookie" headers in 199 - List.iter (fun value -> 200 - match parse_set_cookie ~url value with 201 - | Some cookie -> add_cookie t cookie 202 - | None -> Log.warn (fun m -> m "Failed to parse Set-Cookie header: %s" value) 203 - ) set_cookie_values 204 - 205 - let get_cookies t ~url = 206 - let uri = Uri.of_string url in 207 - let domain = Uri.host_with_default ~default:"localhost" uri in 208 - let path = Uri.path uri in 209 - let is_secure = Uri.scheme uri = Some "https" in 210 - 211 - Log.debug (fun m -> m "Getting cookies for domain=%s path=%s secure=%b" 212 - domain path is_secure); 213 - 214 - Eio.Mutex.lock t.mutex; 215 - let applicable = List.filter (fun cookie -> 216 - domain_matches cookie.domain domain && 217 - path_matches cookie.path path && 218 - (not cookie.secure || is_secure) 219 - ) t.cookies in 220 - 221 - (* Update last access time *) 222 - let now = Ptime.of_float_s (Unix.time ()) |> Option.value ~default:Ptime.epoch in 223 - let updated = List.map (fun c -> 224 - if List.memq c applicable then 225 - { c with last_access = now } 226 - else c 227 - ) t.cookies in 228 - t.cookies <- updated; 229 - Eio.Mutex.unlock t.mutex; 230 - 231 - Log.debug (fun m -> m "Found %d applicable cookies" (List.length applicable)); 232 - applicable 233 - 234 - let add_to_headers t ~url headers = 235 - let cookies = get_cookies t ~url in 236 - if cookies = [] then headers 237 - else 238 - let cookie_header = make_cookie_header cookies in 239 - Log.debug (fun m -> m "Adding Cookie header: %s" cookie_header); 240 - Headers.add "cookie" cookie_header headers 241 - 242 - let clear t = 243 - Log.info (fun m -> m "Clearing all cookies"); 244 - Eio.Mutex.lock t.mutex; 245 - t.cookies <- []; 246 - Eio.Mutex.unlock t.mutex 247 - 248 - let clear_expired t ~clock = 249 - Eio.Mutex.lock t.mutex; 250 - let before_count = List.length t.cookies in 251 - t.cookies <- List.filter (fun c -> not (is_expired c clock)) t.cookies; 252 - let removed = before_count - List.length t.cookies in 253 - Eio.Mutex.unlock t.mutex; 254 - Log.info (fun m -> m "Cleared %d expired cookies" removed) 255 - 256 - let clear_session_cookies t = 257 - Eio.Mutex.lock t.mutex; 258 - let before_count = List.length t.cookies in 259 - t.cookies <- List.filter (fun c -> c.expires <> None) t.cookies; 260 - let removed = before_count - List.length t.cookies in 261 - Eio.Mutex.unlock t.mutex; 262 - Log.info (fun m -> m "Cleared %d session cookies" removed) 263 - 264 - let count t = 265 - Eio.Mutex.lock t.mutex; 266 - let n = List.length t.cookies in 267 - Eio.Mutex.unlock t.mutex; 268 - n 269 - 270 - (** {1 Mozilla Format} *) 271 - 272 - let to_mozilla_format_internal t = 273 - let buffer = Buffer.create 1024 in 274 - Buffer.add_string buffer "# Netscape HTTP Cookie File\n"; 275 - Buffer.add_string buffer "# This is a generated file! Do not edit.\n\n"; 276 - 277 - List.iter (fun cookie -> 278 - let include_subdomains = 279 - if String.starts_with ~prefix:"." cookie.domain then "TRUE" else "FALSE" in 280 - let secure = if cookie.secure then "TRUE" else "FALSE" in 281 - let expires = match cookie.expires with 282 - | None -> "0" (* Session cookie *) 283 - | Some t -> 284 - let epoch = Ptime.to_float_s t |> int_of_float |> string_of_int in 285 - epoch 286 - in 287 - 288 - Buffer.add_string buffer (Printf.sprintf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" 289 - cookie.domain 290 - include_subdomains 291 - cookie.path 292 - secure 293 - expires 294 - cookie.name 295 - cookie.value) 296 - ) t.cookies; 297 - 298 - Buffer.contents buffer 299 - 300 - let to_mozilla_format t = 301 - Eio.Mutex.lock t.mutex; 302 - let result = to_mozilla_format_internal t in 303 - Eio.Mutex.unlock t.mutex; 304 - result 305 - 306 - let from_mozilla_format content = 307 - Log.debug (fun m -> m "Parsing Mozilla format cookies"); 308 - let jar = create () in 309 - 310 - let lines = String.split_on_char '\n' content in 311 - List.iter (fun line -> 312 - let line = String.trim line in 313 - if line <> "" && not (String.starts_with ~prefix:"#" line) then 314 - match String.split_on_char '\t' line with 315 - | [domain; _include_subdomains; path; secure; expires; name; value] -> 316 - let now = Ptime.of_float_s (Unix.time ()) |> Option.value ~default:Ptime.epoch in 317 - let expires = 318 - let exp_int = try int_of_string expires with _ -> 0 in 319 - if exp_int = 0 then None 320 - else Ptime.of_float_s (float_of_int exp_int) 321 - in 322 - 323 - let cookie = { 324 - domain; 325 - path; 326 - name; 327 - value; 328 - secure = (secure = "TRUE"); 329 - http_only = false; (* Not stored in Mozilla format *) 330 - expires; 331 - same_site = None; (* Not stored in Mozilla format *) 332 - creation_time = now; 333 - last_access = now; 334 - } in 335 - add_cookie jar cookie; 336 - Log.debug (fun m -> m "Loaded cookie: %s=%s" name value) 337 - | _ -> 338 - Log.warn (fun m -> m "Invalid cookie line: %s" line) 339 - ) lines; 340 - 341 - Log.info (fun m -> m "Loaded %d cookies" (List.length jar.cookies)); 342 - jar 343 - 344 - (** {1 File Operations} *) 345 - 346 - (** Get cookie file path - uses XDG data directory or provided path *) 347 - let get_cookie_file ?xdg ?path () = 348 - match xdg, path with 349 - | Some xdg_ctx, _ -> 350 - (* Use XDG data directory for cookies *) 351 - let data_dir = Xdge.data_dir xdg_ctx in 352 - Eio.Path.(data_dir / "cookies.txt") 353 - | None, Some p -> p 354 - | None, None -> 355 - failwith "Cookie_jar: either xdg or path must be provided" 356 - 357 - let load ?xdg ?path () = 358 - let cookie_file = get_cookie_file ?xdg ?path () in 359 - Log.info (fun m -> m "Loading cookies from %a" Eio.Path.pp cookie_file); 360 - 361 - try 362 - let content = Eio.Path.load cookie_file in 363 - from_mozilla_format content 364 - with 365 - | Eio.Io _ -> 366 - Log.info (fun m -> m "Cookie file not found, creating empty jar"); 367 - create () 368 - | exn -> 369 - Log.err (fun m -> m "Failed to load cookies: %s" (Printexc.to_string exn)); 370 - create () 371 - 372 - let save ?xdg ?path t = 373 - let cookie_file = get_cookie_file ?xdg ?path () in 374 - Log.info (fun m -> m "Saving %d cookies to %a" (List.length t.cookies) Eio.Path.pp cookie_file); 375 - 376 - let content = to_mozilla_format t in 377 - 378 - try 379 - Eio.Path.save ~create:(`Or_truncate 0o600) cookie_file content; 380 - Log.debug (fun m -> m "Cookies saved successfully") 381 - with exn -> 382 - Log.err (fun m -> m "Failed to save cookies: %s" (Printexc.to_string exn))
-104
stack/requests/lib/cookie_jar.mli
··· 1 - (** HTTP Cookie Jar with Mozilla format persistence support *) 2 - 3 - open Eio 4 - 5 - (** Cookie same-site policy *) 6 - type same_site = [`Strict | `Lax | `None] 7 - 8 - (** HTTP Cookie *) 9 - type cookie = { 10 - domain : string; (** Domain that set the cookie *) 11 - path : string; (** Path scope for the cookie *) 12 - name : string; (** Cookie name *) 13 - value : string; (** Cookie value *) 14 - secure : bool; (** Only send over HTTPS *) 15 - http_only : bool; (** Not accessible to JavaScript *) 16 - expires : Ptime.t option; (** Expiry time, None for session cookies *) 17 - same_site : same_site option; (** Same-site policy *) 18 - creation_time : Ptime.t; (** When cookie was created *) 19 - last_access : Ptime.t; (** Last time cookie was accessed *) 20 - } 21 - 22 - (** Cookie jar for storing and managing cookies *) 23 - type t 24 - 25 - (** {1 Creation and Loading} *) 26 - 27 - (** Create an empty cookie jar *) 28 - val create : unit -> t 29 - 30 - (** Load cookies from Mozilla format file. 31 - If xdg is provided, uses XDG data directory, otherwise uses provided path. *) 32 - val load : ?xdg:Xdge.t -> ?path:Eio.Fs.dir_ty Path.t -> unit -> t 33 - 34 - (** Save cookies to Mozilla format file. 35 - If xdg is provided, uses XDG data directory, otherwise uses provided path. *) 36 - val save : ?xdg:Xdge.t -> ?path:Eio.Fs.dir_ty Path.t -> t -> unit 37 - 38 - (** {1 Cookie Management} *) 39 - 40 - (** Add a cookie to the jar *) 41 - val add_cookie : t -> cookie -> unit 42 - 43 - (** Extract cookies from Set-Cookie headers *) 44 - val extract_from_headers : t -> url:string -> Headers.t -> unit 45 - 46 - (** Get cookies applicable for a URL *) 47 - val get_cookies : t -> url:string -> cookie list 48 - 49 - (** Add Cookie header for a request *) 50 - val add_to_headers : t -> url:string -> Headers.t -> Headers.t 51 - 52 - (** Clear all cookies *) 53 - val clear : t -> unit 54 - 55 - (** Clear expired cookies *) 56 - val clear_expired : t -> clock:_ Time.clock -> unit 57 - 58 - (** Clear session cookies (those without expiry) *) 59 - val clear_session_cookies : t -> unit 60 - 61 - (** Get the number of cookies in the jar *) 62 - val count : t -> int 63 - 64 - (** {1 Cookie Creation} *) 65 - 66 - (** Parse Set-Cookie header value into a cookie *) 67 - val parse_set_cookie : url:string -> string -> cookie option 68 - 69 - (** Create cookie header value from cookies *) 70 - val make_cookie_header : cookie list -> string 71 - 72 - (** {1 Pretty Printing} *) 73 - 74 - (** Pretty print a cookie *) 75 - val pp_cookie : Format.formatter -> cookie -> unit 76 - 77 - (** Pretty print a cookie jar *) 78 - val pp : Format.formatter -> t -> unit 79 - 80 - (** {1 Mozilla Format} *) 81 - 82 - (** Mozilla cookies.txt format: 83 - # Netscape HTTP Cookie File 84 - # This is a generated file! Do not edit. 85 - 86 - domain include_subdomains path secure expires name value 87 - 88 - Where: 89 - - domain: The domain that created the cookie 90 - - include_subdomains: TRUE if cookie applies to subdomains, FALSE otherwise 91 - - path: The path the cookie is valid for 92 - - secure: TRUE if cookie requires secure connection 93 - - expires: Unix timestamp when cookie expires (0 for session cookies) 94 - - name: Cookie name 95 - - value: Cookie value 96 - 97 - Example: 98 - .github.com TRUE / TRUE 1735689600 _gh_sess abc123... *) 99 - 100 - (** Write cookies in Mozilla format *) 101 - val to_mozilla_format : t -> string 102 - 103 - (** Parse Mozilla format cookies *) 104 - val from_mozilla_format : string -> t
+1
stack/requests/lib/dune
··· 12 12 yojson 13 13 base64 14 14 cacheio 15 + cookeio 15 16 xdge 16 17 logs 17 18 ptime
-1
stack/requests/lib/requests.ml
··· 12 12 module Status = Status 13 13 module Error = Error 14 14 module Session = Session 15 - module Cookie_jar = Cookie_jar 16 15 module Retry = Retry
+1 -4
stack/requests/lib/requests.mli
··· 135 135 (** Stateful HTTP sessions with cookies and configuration persistence *) 136 136 module Session = Session 137 137 138 - (** Cookie storage and management *) 139 - module Cookie_jar = Cookie_jar 140 - 141 138 (** Retry policies and backoff strategies *) 142 139 module Retry = Retry 143 140 ··· 183 180 module Mime = Mime 184 181 185 182 (** Timeout configuration for requests *) 186 - module Timeout = Timeout 183 + module Timeout = Timeout
+47 -14
stack/requests/lib/session.ml
··· 33 33 sw : Eio.Switch.t; 34 34 client : ('clock, 'net) Client.t; 35 35 clock : 'clock; 36 - cookie_jar : Cookie_jar.t; 36 + cookie_jar : Cookeio.jar; 37 37 mutable default_headers : Headers.t; 38 38 mutable auth : Auth.t option; 39 39 mutable timeout : Timeout.t; ··· 91 91 | Some jar, _, _ -> jar 92 92 | None, true, Some xdg_ctx -> 93 93 Log.debug (fun m -> m "Loading persistent cookie jar from XDG data dir"); 94 - Cookie_jar.load ~xdg:xdg_ctx () 94 + let data_dir = Xdge.data_dir xdg_ctx in 95 + let cookie_file = Eio.Path.(data_dir / "cookies.txt") in 96 + Cookeio.load cookie_file 95 97 | None, _, _ -> 96 - Cookie_jar.create () 98 + Cookeio.create () 97 99 in 98 100 99 101 let session = { ··· 120 122 Log.info (fun m -> m "Closing session after %d requests" session.requests_made); 121 123 if persist_cookies && Option.is_some xdg then begin 122 124 Log.info (fun m -> m "Saving cookies on session close"); 123 - Cookie_jar.save ?xdg session.cookie_jar 125 + let data_dir = Xdge.data_dir (Option.get xdg) in 126 + let cookie_file = Eio.Path.(data_dir / "cookies.txt") in 127 + Cookeio.save cookie_file session.cookie_jar 124 128 end 125 129 ); 126 130 ··· 128 132 129 133 let save_cookies : ('a, 'b) t -> unit = fun t -> 130 134 if t.persist_cookies && Option.is_some t.xdg then 131 - Cookie_jar.save ?xdg:t.xdg t.cookie_jar 135 + let data_dir = Xdge.data_dir (Option.get t.xdg) in 136 + let cookie_file = Eio.Path.(data_dir / "cookies.txt") in 137 + Cookeio.save cookie_file t.cookie_jar 132 138 133 139 let load_cookies : ('a, 'b) t -> unit = fun t -> 134 140 if t.persist_cookies && Option.is_some t.xdg then 135 - let loaded = Cookie_jar.load ?xdg:t.xdg () in 141 + let data_dir = Xdge.data_dir (Option.get t.xdg) in 142 + let cookie_file = Eio.Path.(data_dir / "cookies.txt") in 143 + let loaded = Cookeio.load cookie_file in 136 144 (* Copy loaded cookies into our jar *) 137 - Cookie_jar.clear t.cookie_jar; 138 - let cookies_from_loaded = Cookie_jar.to_mozilla_format loaded in 139 - let _reloaded = Cookie_jar.from_mozilla_format cookies_from_loaded in 145 + Cookeio.clear t.cookie_jar; 146 + let cookies_from_loaded = Cookeio.to_mozilla_format loaded in 147 + let _reloaded = Cookeio.from_mozilla_format cookies_from_loaded in 140 148 (* This is a bit convoluted but maintains the same jar reference *) 141 149 () 142 150 ··· 172 180 let cookies t = t.cookie_jar 173 181 174 182 let clear_cookies t = 175 - Cookie_jar.clear t.cookie_jar 183 + Cookeio.clear t.cookie_jar 176 184 177 185 (** {1 Internal Request Function} *) 178 186 ··· 183 191 let headers = 184 192 t.default_headers 185 193 |> Headers.merge (Option.value headers ~default:Headers.empty) 186 - |> Cookie_jar.add_to_headers t.cookie_jar ~url 194 + |> (fun headers -> 195 + let uri = Uri.of_string url in 196 + let domain = Uri.host_with_default ~default:"localhost" uri in 197 + let path = Uri.path uri in 198 + let is_secure = Uri.scheme uri = Some "https" in 199 + let cookies = Cookeio.get_cookies t.cookie_jar ~domain ~path ~is_secure in 200 + if cookies = [] then headers 201 + else 202 + let cookie_header = Cookeio.make_cookie_header cookies in 203 + Headers.add "cookie" cookie_header headers) 187 204 in 188 205 189 206 (* Use provided auth or session default *) ··· 224 241 in 225 242 226 243 (* Extract cookies from response *) 227 - Cookie_jar.extract_from_headers t.cookie_jar ~url (Response.headers response); 244 + let uri = Uri.of_string url in 245 + let domain = Uri.host_with_default ~default:"localhost" uri in 246 + let path = 247 + let p = Uri.path uri in 248 + if p = "" then "/" 249 + else 250 + let last_slash = String.rindex_opt p '/' in 251 + match last_slash with 252 + | None -> "/" 253 + | Some i -> String.sub p 0 (i + 1) 254 + in 255 + let set_cookie_values = Headers.get_multi "set-cookie" (Response.headers response) in 256 + List.iter (fun value -> 257 + match Cookeio.parse_set_cookie ~domain ~path value with 258 + | Some cookie -> Cookeio.add_cookie t.cookie_jar cookie 259 + | None -> Log.warn (fun m -> m "Failed to parse Set-Cookie header: %s" value) 260 + ) set_cookie_values; 228 261 229 262 (* Update statistics *) 230 263 Mutex.lock t.mutex; ··· 293 326 let pp ppf t = 294 327 Mutex.lock t.mutex; 295 328 let stats = t.requests_made, t.total_time, 296 - Cookie_jar.count t.cookie_jar in 329 + Cookeio.count t.cookie_jar in 297 330 Mutex.unlock t.mutex; 298 331 let requests, time, cookies = stats in 299 332 Format.fprintf ppf "@[<v>Session:@,\ ··· 321 354 let result = Stats.{ 322 355 requests_made = t.requests_made; 323 356 total_time = t.total_time; 324 - cookies_count = Cookie_jar.count t.cookie_jar; 357 + cookies_count = Cookeio.count t.cookie_jar; 325 358 retries_count = t.retries_count; 326 359 } in 327 360 Mutex.unlock t.mutex;
+2 -2
stack/requests/lib/session.mli
··· 60 60 val create : 61 61 sw:Eio.Switch.t -> 62 62 ?client:('clock Eio.Time.clock,'net Eio.Net.t) Client.t -> 63 - ?cookie_jar:Cookie_jar.t -> 63 + ?cookie_jar:Cookeio.jar -> 64 64 ?default_headers:Headers.t -> 65 65 ?auth:Auth.t -> 66 66 ?timeout:Timeout.t -> ··· 113 113 114 114 (** {1 Cookie Management} *) 115 115 116 - val cookies : ('clock, 'net) t -> Cookie_jar.t 116 + val cookies : ('clock, 'net) t -> Cookeio.jar 117 117 (** Get the session's cookie jar for direct manipulation *) 118 118 119 119 val clear_cookies : ('clock, 'net) t -> unit