Eio HTTP server with static file serving and route handlers
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Irmin CID-native MST, SCITT spec-compliant receipts, offline MST proofs

Irmin:
- MST codec keyed by Atp.Cid.t (removed Hash↔CID conversion layer)
- Backend.{Memory,Disk}.create_cid — CID-native backends
- Proof.encode_cbor / decode_cbor — CBOR serialization for COSE receipts
- pds_interop: trivial passthrough (no conversion needed)
- 72 tests pass

SCITT:
- Receipt vds (395) in protected header per COSE Receipts spec
- Receipt vdp (396) in unprotected header for proof data
- RFC 9162 VDS: O(1) amortized append, RFC-compliant verify_inclusion
with test vectors from the spec
- MST VDS: Irmin.Proof.Mst.produce at registration, encode_cbor into
receipt, decode + Irmin.Proof.Mst.verify for fully offline verification
- Leaf hash authentication binds proof to specific statement
- 34 main + 17 ATP = 51 tests, all pass

Sigstore:
- Certificate chain validation against Fulcio root CA
- Rekor entry binding (body/log_index/integrated_time comparison)
- Hash algorithm from bundle (not hardcoded SHA256)
- 58 tests pass

Auth:
- Per-provider callback URLs (/auth/<slug>/callback)
- provider_name (raw, for DB) vs provider_slug (URL-safe, for routes)
- Token exchange includes grant_type=authorization_code
- No credential leakage in logs or error responses

Respond:
- HEAD responses suppress body per RFC 9110 §9.3.2

OAuth:
- Provider variant type (Github | Google | Gitlab | Custom)
- Per-provider userinfo JSON schemas (no field guessing)

+18 -13
+18 -13
lib/respond.ml
··· 110 110 with Exit -> ()); 111 111 !h 112 112 113 - let send_response flow (r : Response.t) = 113 + let send_response ?(head = false) flow (r : Response.t) = 114 114 let extra = 115 115 List.map (fun (k, v) -> Fmt.str "%s: %s\r\n" k v) r.headers 116 116 |> String.concat "" ··· 125 125 \r\n" 126 126 (status_line r.status) r.content_type (String.length r.body) extra 127 127 in 128 - Eio.Flow.copy_string (h ^ r.body) flow 128 + (* RFC 9110 §9.3.2: HEAD response MUST NOT contain a body. 129 + Content-Length is still set to what GET would return. *) 130 + if head then Eio.Flow.copy_string h flow 131 + else Eio.Flow.copy_string (h ^ r.body) flow 129 132 130 133 let send_cors flow = 131 134 Eio.Flow.copy_string ··· 204 207 205 208 let generate_etag ~size = Fmt.str "W/\"%x\"" size 206 209 207 - let try_serve_path ~root flow rel = 210 + let try_serve_path ?(head = false) ~root flow rel = 208 211 let full = Eio.Path.(root / rel) in 209 212 match Eio.Path.load full with 210 213 | body -> ··· 223 226 \r\n" 224 227 ct (String.length body) etag 225 228 in 226 - Eio.Flow.copy_string (h ^ body) flow; 229 + if head then Eio.Flow.copy_string h flow 230 + else Eio.Flow.copy_string (h ^ body) flow; 227 231 true 228 232 | exception Eio.Io _ -> false 229 233 | exception Sys_error _ -> false 230 234 231 - let serve_file ~root flow path = 235 + let serve_file ?(head = false) ~root flow path = 232 236 let clean = normalize_path path in 233 - if String.length clean > 1024 then send_response flow Response.not_found 234 - else if try_serve_path ~root flow clean then () 235 - else if try_serve_path ~root flow (clean ^ "/index.html") then () 236 - else if clean = "" && try_serve_path ~root flow "index.html" then () 237 - else send_response flow Response.not_found 237 + if String.length clean > 1024 then send_response ~head flow Response.not_found 238 + else if try_serve_path ~head ~root flow clean then () 239 + else if try_serve_path ~head ~root flow (clean ^ "/index.html") then () 240 + else if clean = "" && try_serve_path ~head ~root flow "index.html" then () 241 + else send_response ~head flow Response.not_found 238 242 239 243 (* ── Request handling ─────────────────────────────────────────────── *) 240 244 ··· 281 285 Log.warn (fun m -> m "POST %s 404" path); 282 286 send_response flow Response.not_found) 283 287 | ("GET" | "HEAD"), url :: _ -> ( 288 + let is_head = meth_str = "HEAD" in 284 289 let path, params = parse_url url in 285 290 match match_route routes path with 286 291 | Some { handler = Get handler; _ } -> ( ··· 288 293 let r = handler { params; headers } in 289 294 Log.info (fun m -> 290 295 m "%s %s %s" meth_str path (status_line r.Response.status)); 291 - send_response flow r 296 + send_response ~head:is_head flow r 292 297 with exn -> 293 298 Log.err (fun m -> 294 299 m "%s %s 500 %s" meth_str path (Printexc.to_string exn)); 295 - send_response flow 300 + send_response ~head:is_head flow 296 301 (Response.internal_server_error (Printexc.to_string exn))) 297 302 | _ -> 298 303 Log.info (fun m -> m "%s %s (static)" meth_str path); 299 - serve_file ~root flow path) 304 + serve_file ~head:is_head ~root flow path) 300 305 | _ -> 301 306 Log.warn (fun m -> m "%s 405" meth_str); 302 307 send_response flow Response.method_not_allowed)