let src = Logs.Src.create "requests.headers" ~doc:"HTTP Headers" module Log = (val Logs.src_log src : Logs.LOG) (* Use a map with lowercase keys for case-insensitive lookup *) module StringMap = Map.Make(String) type t = (string * string list) StringMap.t let empty = StringMap.empty let normalize_key k = String.lowercase_ascii k let add key value t = let nkey = normalize_key key in Log.debug (fun m -> m "Adding header %s: %s" key value); let existing = match StringMap.find_opt nkey t with | Some (_, values) -> values | None -> [] in StringMap.add nkey (key, value :: existing) t let set key value t = let nkey = normalize_key key in StringMap.add nkey (key, [value]) t let get key t = let nkey = normalize_key key in match StringMap.find_opt nkey t with | Some (_, values) -> List.nth_opt values 0 | None -> None let get_all key t = let nkey = normalize_key key in match StringMap.find_opt nkey t with | Some (_, values) -> List.rev values | None -> [] let remove key t = let nkey = normalize_key key in StringMap.remove nkey t let mem key t = let nkey = normalize_key key in StringMap.mem nkey t let of_list lst = List.fold_left (fun acc (k, v) -> add k v acc) empty lst let to_list t = StringMap.fold (fun _ (orig_key, values) acc -> List.fold_left (fun acc v -> (orig_key, v) :: acc) acc (List.rev values) ) t [] let merge t1 t2 = StringMap.union (fun _ _ v2 -> Some v2) t1 t2 (* Common header builders *) let content_type mime t = set "Content-Type" (Mime.to_string mime) t let content_length len t = set "Content-Length" (Int64.to_string len) t let accept mime t = set "Accept" (Mime.to_string mime) t let authorization value t = set "Authorization" value t let bearer token t = set "Authorization" (Printf.sprintf "Bearer %s" token) t let basic ~username ~password t = let credentials = Printf.sprintf "%s:%s" username password in let encoded = Base64.encode_exn credentials in set "Authorization" (Printf.sprintf "Basic %s" encoded) t let user_agent ua t = set "User-Agent" ua t let host h t = set "Host" h t let cookie name value t = add "Cookie" (Printf.sprintf "%s=%s" name value) t let range ~start ?end_ () t = let range_value = match end_ with | None -> Printf.sprintf "bytes=%Ld-" start | Some e -> Printf.sprintf "bytes=%Ld-%Ld" start e in set "Range" range_value t (* Additional helper for getting multiple header values *) let get_multi key t = get_all key t (* Pretty printer for headers *) let pp ppf t = Format.fprintf ppf "@[Headers:@,"; let headers = to_list t in List.iter (fun (k, v) -> Format.fprintf ppf " %s: %s@," k v ) headers; Format.fprintf ppf "@]" let pp_brief ppf t = let headers = to_list t in let count = List.length headers in Format.fprintf ppf "Headers(%d entries)" count