package ohttp
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Oblivious HTTP (RFC 9458) for OCaml
Install
dune-project
Dependency
Authors
Maintainers
Sources
v0.1.2.tar.gz
md5=6a31dba01d9ee06d1b0115516439d8b0
sha512=c2739c32cf8d44b36ca565052f6b14feaf99a029115e8d94dcd469bd363f7dacd8e401a4ca07c0a3555f0fe5572a37516b489ddd945ee9acae4ceafc29e7dadb
doc/src/ohttp/service.ml.html
Source file service.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274(* The client, relay, and gateway of RFC 9458 Section 5, as steps from HTTP messages to HTTP messages. *) type request = { headers : (string * string) list; body : string } type response = Http_binding.Gateway.error_response = { status : int; headers : (string * string) list; body : string; } let default_max_request_size = 1 lsl 20 let default_max_response_size = 8 lsl 20 let default_max_in_flight = 256 let limit name ~default = function | None -> default | Some n when n > 0 -> n | Some _ -> invalid_arg ("Ohttp.Service: " ^ name ^ " must be positive") (* Only digits: a value that int_of_string would read otherwise, such as "0x10" or "-1", is left to the count of what is read. *) let exceeds ~max_size headers = List.exists (fun (name, value) -> String.lowercase_ascii name = "content-length" && let value = String.trim value in value <> "" && String.for_all (function '0' .. '9' -> true | _ -> false) value && (String.length value > 18 || int_of_string value > max_size)) headers let plain ?(headers = []) status = { status; headers; body = "" } let content_too_large = plain 413 let busy = plain ~headers:[ ("retry-after", "1") ] 503 (* The requests that a party is handling, which may run on several domains. *) module In_flight = struct type t = { max : int; count : int Atomic.t } let create max = { max; count = Atomic.make 0 } let admit t = Atomic.fetch_and_add t.count 1 < t.max || (Atomic.decr t.count; false) let release t = Atomic.decr t.count end module Client = struct let key_configs ~status ~headers content = Result.bind (Http_binding.Client.check_key_config_response ~status ~headers) (fun () -> Key_config.decode_list content) type exchange = { rng : Mirage_crypto_rng.g; preference : Suite.symmetric list option; framing : Bhttp.Framing.t option; padding : int option; config : Key_config.t; request : Bhttp.Request.t; (* Whether the request carried a date, and so may be sent once more with the gateway's. *) may_retry : bool; context : Client.response_context; } let send ~rng ?preference ?framing ?padding ~now ~may_retry config (request : Bhttp.Request.t) = let dated = match now with | None -> request | Some now -> { request with headers = Replay.date_field ~now :: List.filter (fun (name, _) -> not (String.equal (String.lowercase_ascii name) "date")) request.headers; } in Result.map (fun (body, context) -> ( { headers = Http_binding.Client.request_headers; body }, { rng; preference; framing; padding; config; request; may_retry; context; } )) (Http_message.encapsulate_request ~rng ?preference ?framing ?padding config dated) let start ~rng ?preference ?framing ?padding ?now config request = send ~rng ?preference ?framing ?padding ~now ~may_retry:(now <> None) config request type outcome = Response of Bhttp.Response.t | Retry of request * exchange let finish exchange ~status ~headers content = let ( let* ) = Result.bind in let* () = Http_binding.Client.check_response ~status ~headers in let* response = Http_message.decapsulate_response exchange.context content in match Replay.date_of_problem response with | Some time when exchange.may_retry -> (* Encapsulated anew: the same Encapsulated Request would be refused as a replay, and would let the gateway link the two. *) let* retry = send ~rng:exchange.rng ?preference:exchange.preference ?framing:exchange.framing ?padding:exchange.padding ~now:(Some time) ~may_retry:false exchange.config exchange.request in let request, exchange = retry in Ok (Retry (request, exchange)) | _ -> Ok (Response response) end module Relay = struct type t = { max_request_size : int; max_response_size : int; in_flight : In_flight.t; } let create ?max_request_size ?max_response_size ?max_in_flight () = { max_request_size = limit "max_request_size" ~default:default_max_request_size max_request_size; max_response_size = limit "max_response_size" ~default:default_max_response_size max_response_size; in_flight = In_flight.create (limit "max_in_flight" ~default:default_max_in_flight max_in_flight); } let max_request_size t = t.max_request_size let max_response_size t = t.max_response_size let admit t = In_flight.admit t.in_flight let release t = In_flight.release t.in_flight let request ~meth ~headers content = match Http_binding.Gateway.check_request ~meth ~headers with | Ok () -> Ok { headers = Http_binding.Client.request_headers; body = content } | Error (Error.Method_not_allowed _) -> Error (plain ~headers:[ ("allow", "POST") ] 405) | Error _ -> Error (plain 415) let passed = [ "content-type"; "cache-control"; "allow" ] let response ~status ~headers content = let headers = List.filter_map (fun (name, value) -> let name = String.lowercase_ascii name in if List.mem name passed then Some (name, value) else None) headers in { status; headers; body = content } let unreachable = plain 502 end module Gateway = struct type t = { gateway : Gateway.t; rng : Mirage_crypto_rng.g; replay : Replay.t option; checks_replay : Bhttp.Request.t -> bool; framing : Bhttp.Framing.t option; padding : int option; max_request_size : int; in_flight : In_flight.t; } let create ~rng ?replay ?(checks_replay = fun _ -> true) ?framing ?padding ?max_request_size ?max_in_flight gateway = { gateway; rng; replay; checks_replay; framing; padding; max_request_size = limit "max_request_size" ~default:default_max_request_size max_request_size; in_flight = In_flight.create (limit "max_in_flight" ~default:default_max_in_flight max_in_flight); } let max_request_size t = t.max_request_size let admit t = In_flight.admit t.in_flight let release t = In_flight.release t.in_flight let key_configs t = { status = 200; headers = Http_binding.Gateway.key_config_response_headers; body = Gateway.encoded_key_configs t.gateway; } type step = | Respond of response | Forward of Bhttp.Request.t * (Bhttp.Response.t -> response) let unreplayed t ~now context request = match t.replay with | Some replay when t.checks_replay request -> ( match Replay.check replay ~now ~enc:(Gateway.encapsulated_key context) request with | Ok () -> Ok request | Error rejection -> Error (Replay.rejection_response ~now rejection)) | _ -> Ok request let target ~targets (request : Bhttp.Request.t) = match List.assoc_opt request.authority targets with | None -> Error (Bhttp.Response.make ~status:403 ()) | Some _ when String.length request.path = 0 || request.path.[0] <> '/' -> Error (Bhttp.Response.make ~status:400 ()) | Some base -> let base = if String.length base > 0 && base.[String.length base - 1] = '/' then String.sub base 0 (String.length base - 1) else base in Ok (base ^ request.path) let receive t ~now ~meth ~headers content = let received = Result.bind (Http_binding.Gateway.check_request ~meth ~headers) (fun () -> if String.length content > t.max_request_size then Error (Error.Content_too_large t.max_request_size) else Http_message.decapsulate_request t.gateway content) in match received with (* The encapsulation is still on: answer in the clear. *) | Error e -> Respond (Http_binding.Gateway.error_response e) (* It is off: from here on, every answer goes back sealed. *) | Ok (inner, context) -> ( let seal response = match Http_message.encapsulate_response ~rng:t.rng ?framing:t.framing ?padding:t.padding context response with | Ok body -> { status = 200; headers = Http_binding.Gateway.response_headers; body; } | Error e -> Http_binding.Gateway.error_response e in match Result.bind inner (unreplayed t ~now context) with | Ok request -> Forward (request, seal) | Error response -> Respond (seal response)) end
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>