(** Request helper functions. This module provides utility functions for working with HTTP requests, including header access, query parameter parsing, and body handling. *) let string_contains haystack pattern = let plen = String.length pattern in let hlen = String.length haystack in if plen > hlen then false else let rec check i = if i > hlen - plen then false else if String.sub haystack i plen = pattern then true else check (i + 1) in check 0 type t = H1_server.request let meth (req : t) = req.meth let target (req : t) = req.target let headers (req : t) = req.headers let body (req : t) = H1_server.read_body req let body_reader (req : t) = req.body_reader let path (req : t) = match String.index_opt req.target '?' with | Some i -> String.sub req.target 0 i | None -> req.target let query_string (req : t) = match String.index_opt req.target '?' with | Some i -> Some (String.sub req.target (i + 1) (String.length req.target - i - 1)) | None -> None let parse_query_string qs = let pairs = String.split_on_char '&' qs in List.filter_map (fun pair -> match String.index_opt pair '=' with | Some i -> let key = String.sub pair 0 i in let value = String.sub pair (i + 1) (String.length pair - i - 1) in Some (Uri.pct_decode key, Uri.pct_decode value) | None -> Some (Uri.pct_decode pair, "")) pairs let query_params (req : t) = match query_string req with None -> [] | Some qs -> parse_query_string qs let query_params_from_target target = match String.index_opt target '?' with | Some i -> let qs = String.sub target (i + 1) (String.length target - i - 1) in parse_query_string qs | None -> [] let query (req : Server.request) key = List.assoc_opt key (query_params_from_target req.target) let query_or ~default (req : Server.request) key = match query req key with Some v -> v | None -> default let query_all (req : Server.request) key = List.filter_map (fun (k, v) -> if String.equal k key then Some v else None) (query_params_from_target req.target) let query_int (req : Server.request) key = match query req key with Some v -> int_of_string_opt v | None -> None let query_int_or ~default (req : Server.request) key = match query_int req key with Some v -> v | None -> default let query_bool (req : Server.request) key = match query req key with | Some v -> ( match String.lowercase_ascii v with | "true" | "1" | "yes" | "on" -> Some true | "false" | "0" | "no" | "off" -> Some false | _ -> None) | None -> None let query_bool_or ~default (req : Server.request) key = match query_bool req key with Some v -> v | None -> default let query_float (req : Server.request) key = match query req key with Some v -> float_of_string_opt v | None -> None let query_float_or ~default (req : Server.request) key = match query_float req key with Some v -> v | None -> default let header name (req : t) = H1.Headers.get req.headers name let header_all name (req : t) = H1.Headers.get_multi req.headers name let has_header name (req : t) = match H1.Headers.get req.headers name with Some _ -> true | None -> false let content_type req = header "content-type" req let content_length req = match header "content-length" req with | Some s -> Int64.of_string_opt s | None -> None let is_keep_alive (req : t) = match header "connection" req with | Some v -> String.lowercase_ascii v = "keep-alive" | None -> true let host req = header "host" req let accept req = header "accept" req let authorization req = header "authorization" req let accepts_json req = match accept req with | Some v -> string_contains v "application/json" || string_contains v "*/*" | None -> false let accepts_html req = match accept req with | Some v -> string_contains v "text/html" || string_contains v "*/*" | None -> false let is_get (req : t) = req.meth = `GET let is_post (req : t) = req.meth = `POST let is_put (req : t) = req.meth = `PUT let is_delete (req : t) = req.meth = `DELETE let is_patch (req : t) = req.meth = `Other "PATCH" let is_head (req : t) = req.meth = `HEAD let is_options (req : t) = req.meth = `OPTIONS let is_safe (req : t) = match req.meth with `GET | `HEAD | `OPTIONS -> true | _ -> false let is_idempotent (req : t) = match req.meth with | `GET | `HEAD | `PUT | `DELETE | `OPTIONS -> true | _ -> false let body_string (req : t) = body req let body_length (req : t) = String.length (body req) let has_body (req : t) = String.length (body req) > 0 let form_decode s = let s = String.map (fun c -> if c = '+' then ' ' else c) s in Uri.pct_decode s let parse_form_body body = if String.length body = 0 then [] else let pairs = String.split_on_char '&' body in List.filter_map (fun pair -> match String.index_opt pair '=' with | Some i -> let key = String.sub pair 0 i in let value = String.sub pair (i + 1) (String.length pair - i - 1) in Some (form_decode key, form_decode value) | None -> Some (form_decode pair, "")) pairs let form_data (req : t) = parse_form_body (body req) let form_data_from_body body = parse_form_body body let form_field (req : Server.request) key = List.assoc_opt key (form_data_from_body req.body) let form_field_or ~default (req : Server.request) key = match form_field req key with Some v -> v | None -> default let form_int (req : Server.request) key = match form_field req key with Some v -> int_of_string_opt v | None -> None let form_int_or ~default (req : Server.request) key = match form_int req key with Some v -> v | None -> default