A batteries included HTTP/1.1 client in OCaml
at main 81 lines 2.9 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** HTTP-date parsing per RFC 9110 Section 5.6.7 *) 7 8let src = Logs.Src.create "requests.http_date" ~doc:"HTTP Date Parsing" 9 10module Log = (val Logs.src_log src : Logs.LOG) 11 12(** Parse HTTP-date (RFC 9110 Section 5.6.7) to Ptime.t *) 13let parse s = 14 (* HTTP-date format: "Sun, 06 Nov 1994 08:49:37 GMT" (RFC 1123) *) 15 (* Also supports obsolete formats per RFC 9110 *) 16 let s = String.trim s in 17 18 (* Helper to parse month name *) 19 let parse_month month_str = 20 match String.lowercase_ascii month_str with 21 | "jan" -> 1 22 | "feb" -> 2 23 | "mar" -> 3 24 | "apr" -> 4 25 | "may" -> 5 26 | "jun" -> 6 27 | "jul" -> 7 28 | "aug" -> 8 29 | "sep" -> 9 30 | "oct" -> 10 31 | "nov" -> 11 32 | "dec" -> 12 33 | _ -> failwith "invalid month" 34 in 35 36 (* Validate time components per RFC 9110 Section 5.6.7 ABNF: 37 hour = 2DIGIT (00-23), minute = 2DIGIT (00-59), second = 2DIGIT (00-59). 38 Note: RFC 9110 inherits the 00-60 range from RFC 5234 to allow leap 39 seconds, but HTTP servers MUST NOT generate them and recipients SHOULD 40 treat second=60 as invalid. We reject second=60 for robustness. *) 41 let validate_time hour min sec = 42 hour >= 0 && hour <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59 43 in 44 45 let make_datetime year month day hour min sec = 46 if validate_time hour min sec then 47 Ptime.of_date_time ((year, month, day), ((hour, min, sec), 0)) 48 else None 49 in 50 51 (* Try different date formats in order of preference *) 52 let parsers = 53 [ 54 (* RFC 1123 format: "Sun, 06 Nov 1994 08:49:37 GMT" *) 55 (fun () -> 56 Scanf.sscanf s "%_s %d %s %d %d:%d:%d GMT" 57 (fun day month_str year hour min sec -> 58 let month = parse_month month_str in 59 make_datetime year month day hour min sec)); 60 (* RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT" *) 61 (fun () -> 62 Scanf.sscanf s "%_s %d-%s@-%d %d:%d:%d GMT" 63 (fun day month_str year2 hour min sec -> 64 let year = if year2 >= 70 then 1900 + year2 else 2000 + year2 in 65 let month = parse_month month_str in 66 make_datetime year month day hour min sec)); 67 (* ANSI C asctime() format: "Sun Nov 6 08:49:37 1994" *) 68 (fun () -> 69 Scanf.sscanf s "%_s %s %d %d:%d:%d %d" 70 (fun month_str day hour min sec year -> 71 let month = parse_month month_str in 72 make_datetime year month day hour min sec)); 73 ] 74 in 75 76 (* Try each parser until one succeeds *) 77 List.find_map 78 (fun parser -> 79 try parser () 80 with Scanf.Scan_failure _ | Failure _ | End_of_file -> None) 81 parsers