(* application/x-www-form-urlencoded bodies and Cookie headers. *) let hex c = match c with | '0' .. '9' -> Some (Char.code c - 48) | 'a' .. 'f' -> Some (Char.code c - 87) | 'A' .. 'F' -> Some (Char.code c - 55) | _ -> None (* Decodes '+' as a space and %XX as a byte. A malformed escape is kept as it is, the way browsers treat it. *) let decode s = let n = String.length s in let b = Buffer.create n in let rec go i = if i < n then match s.[i] with | '+' -> Buffer.add_char b ' '; go (i + 1) | '%' when i + 2 < n -> ( match (hex s.[i + 1], hex s.[i + 2]) with | Some h, Some l -> Buffer.add_char b (Char.chr ((h lsl 4) lor l)); go (i + 3) | _ -> Buffer.add_char b '%'; go (i + 1)) | c -> Buffer.add_char b c; go (i + 1) in go 0; Buffer.contents b let parse body = String.split_on_char '&' body |> List.filter_map (fun pair -> if pair = "" then None else match String.index_opt pair '=' with | Some i -> Some (decode (String.sub pair 0 i), decode (String.sub pair (i + 1) (String.length pair - i - 1))) | None -> Some (decode pair, "")) (* The value of the first cookie called [name] in a Cookie header. *) let cookie header name = String.split_on_char ';' header |> List.find_map (fun part -> match String.index_opt part '=' with | Some i when String.trim (String.sub part 0 i) = name -> Some (String.trim (String.sub part (i + 1) (String.length part - i - 1))) | _ -> None) (* Unicode White_Space, plus U+FEFF (byte order mark), the set JavaScript's String.prototype.trim removes. *) let is_space u = match Uchar.to_int u with | 0x09 | 0x0A | 0x0B | 0x0C | 0x0D | 0x20 | 0x85 | 0xA0 | 0x1680 | 0x2028 | 0x2029 | 0x202F | 0x205F | 0x3000 | 0xFEFF -> true | n -> n >= 0x2000 && n <= 0x200A (* Removes leading and trailing whitespace by that definition, so a title of only non-breaking spaces trims to nothing. [s] is valid UTF-8. *) let trim s = let n = String.length s in let rec go i first last = if i >= n then (first, last) else let d = String.get_utf_8_uchar s i in let next = i + Uchar.utf_decode_length d in if is_space (Uchar.utf_decode_uchar d) then go next first last else go next (if first < 0 then i else first) next in match go 0 (-1) 0 with | -1, _ -> "" | first, last -> String.sub s first (last - first)