ocaml http/1, http/2 and websocket client and server library
at main 6.1 kB view raw
1(** Multipart form data parsing. 2 3 {[ 4 (* Non-streaming *) 5 match Multipart.parse req with 6 | Ok parts -> Multipart.find_part "name" parts 7 | Error e -> Response.bad_request (Multipart.error_to_string e) 8 9 (* Streaming large files *) 10 match Multipart.create_parser req with 11 | Ok parser -> 12 Multipart.iter_parts (fun part -> 13 Stream.Async.iter write_chunk part.body 14 ) parser 15 | Error e -> ... 16 ]} *) 17 18module Hmf = Http_multipart_formdata 19 20type part = { 21 name : string; 22 filename : string option; 23 content_type : string; 24 data : string; 25} 26 27type error = 28 | Missing_content_type 29 | Not_multipart 30 | Missing_boundary 31 | Invalid_boundary of string 32 | Parse_error of string 33 34let error_to_string = function 35 | Missing_content_type -> "Missing Content-Type header" 36 | Not_multipart -> "Content-Type is not multipart/form-data" 37 | Missing_boundary -> "Missing boundary parameter in Content-Type" 38 | Invalid_boundary msg -> "Invalid boundary: " ^ msg 39 | Parse_error msg -> "Parse error: " ^ msg 40 41let is_multipart (req : Request.t) = 42 match Request.content_type req with 43 | None -> false 44 | Some ct -> 45 let ct_lower = String.lowercase_ascii ct in 46 let prefix = "multipart/form-data" in 47 String.length ct_lower >= String.length prefix 48 && String.sub ct_lower 0 (String.length prefix) = prefix 49 50let boundary (req : Request.t) : (Hmf.boundary, error) result = 51 match Request.content_type req with 52 | None -> Error Missing_content_type 53 | Some ct -> ( 54 if not (is_multipart req) then Error Not_multipart 55 else 56 match Hmf.boundary ct with 57 | Ok b -> Ok b 58 | Error msg -> 59 if String.length msg > 0 && msg.[0] = '\'' then 60 Error Missing_boundary 61 else Error (Invalid_boundary msg)) 62 63let parse (req : Request.t) : (part list, error) result = 64 match boundary req with 65 | Error e -> Error e 66 | Ok b -> ( 67 let body = Request.body req in 68 match Hmf.parts b body with 69 | Error msg -> Error (Parse_error msg) 70 | Ok parts -> 71 Ok 72 (List.map 73 (fun (_name, (header, body)) -> 74 { 75 name = Hmf.name header; 76 filename = Hmf.filename header; 77 content_type = Hmf.content_type header; 78 data = body; 79 }) 80 parts)) 81 82let find_part name parts = List.find_opt (fun p -> p.name = name) parts 83 84let find_file name parts = 85 List.find_opt (fun p -> p.name = name && Option.is_some p.filename) parts 86 87let to_assoc parts : (string * part) list = 88 List.map (fun p -> (p.name, p)) parts 89 90type stream_part = { 91 name : string; 92 filename : string option; 93 content_type : string; 94 body : Cstruct.t Stream.Async.t; 95} 96 97type parser = { 98 reader : Hmf.reader; 99 body_stream : unit -> Cstruct.t option; 100 mutable current_state : Hmf.read; 101 mutable finished : bool; 102} 103 104let create_parser (req : Request.t) : (parser, error) result = 105 match boundary req with 106 | Error e -> Error e 107 | Ok b -> 108 let body_stream = 109 Request.body_reader req |> fun r -> r.H1_server.read_stream 110 in 111 let reader = Hmf.reader b `Incremental in 112 let current_state = Hmf.read reader in 113 Ok { reader; body_stream; current_state; finished = false } 114 115let rec advance_state parser = 116 match parser.current_state with 117 | `Awaiting_input continue -> ( 118 match parser.body_stream () with 119 | None -> 120 parser.current_state <- continue `Eof; 121 advance_state parser 122 | Some chunk -> 123 parser.current_state <- continue (`Cstruct chunk); 124 advance_state parser) 125 | other -> other 126 127let make_body_stream parser : Cstruct.t Stream.Async.t = 128 let finished = ref false in 129 fun () -> 130 if !finished then None 131 else 132 let rec read_body () = 133 match advance_state parser with 134 | `Body chunk -> 135 parser.current_state <- Hmf.read parser.reader; 136 Some chunk 137 | `Body_end -> 138 finished := true; 139 parser.current_state <- Hmf.read parser.reader; 140 None 141 | `End -> 142 finished := true; 143 parser.finished <- true; 144 None 145 | `Error _ -> 146 finished := true; 147 parser.finished <- true; 148 None 149 | `Header _ -> 150 finished := true; 151 None 152 | `Awaiting_input _ -> read_body () 153 in 154 read_body () 155 156let next_part parser : stream_part option = 157 if parser.finished then None 158 else 159 let rec find_header () = 160 match advance_state parser with 161 | `Header header -> 162 parser.current_state <- Hmf.read parser.reader; 163 let body = make_body_stream parser in 164 Some 165 { 166 name = Hmf.name header; 167 filename = Hmf.filename header; 168 content_type = Hmf.content_type header; 169 body; 170 } 171 | `End -> 172 parser.finished <- true; 173 None 174 | `Error _ -> 175 parser.finished <- true; 176 None 177 | `Body _ | `Body_end -> 178 parser.current_state <- Hmf.read parser.reader; 179 find_header () 180 | `Awaiting_input _ -> find_header () 181 in 182 find_header () 183 184let iter_parts f parser : (unit, error) result = 185 let rec loop () = 186 match next_part parser with 187 | None -> 188 if parser.finished then Ok () else Error (Parse_error "Unexpected end") 189 | Some part -> 190 f part; 191 Stream.Async.drain part.body; 192 loop () 193 in 194 loop () 195 196let fold_parts f init parser = 197 let rec loop acc = 198 match next_part parser with 199 | None -> 200 if parser.finished then Ok acc else Error (Parse_error "Unexpected end") 201 | Some part -> 202 let acc' = f part acc in 203 Stream.Async.drain part.body; 204 loop acc' 205 in 206 loop init 207 208let collect_body (part : stream_part) : string = 209 Stream.Async.cstructs_to_string part.body 210 211let parse_assoc req = 212 match parse req with Error e -> Error e | Ok parts -> Ok (to_assoc parts)