| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module http |
| 5 | |
| 6 | // This file implements a minimal server-side HTTP/2 connection driver on top |
| 7 | // of the existing framing (h2_frame.v) and HPACK (h2_hpack.v) layers. It is |
| 8 | // intentionally serial: SETTINGS_MAX_CONCURRENT_STREAMS is advertised as 1, |
| 9 | // so the peer sends one request at a time. Concurrent stream handling and |
| 10 | // background writers are a planned follow-up. |
| 11 | |
| 12 | // h2_server_max_concurrent_streams is the value advertised in our SETTINGS; |
| 13 | // the peer may not open more streams than this at once. |
| 14 | const h2_server_max_concurrent_streams = u32(1) |
| 15 | |
| 16 | // h2_server_default_window is the default initial flow-control window |
| 17 | // (RFC 7540 Section 6.9.2). |
| 18 | const h2_server_default_window = u32(65535) |
| 19 | |
| 20 | // h2_server_max_request_body caps the in-memory request body the server will |
| 21 | // accept before responding with a stream error. Bodies larger than this are |
| 22 | // rejected with RST_STREAM(REFUSED_STREAM). |
| 23 | const h2_server_max_request_body = 8 * 1024 * 1024 |
| 24 | |
| 25 | // The connection-specific header fields RFC 9113 §8.2.2 forbids in HTTP/2 (a |
| 26 | // request carrying one is malformed; the server must never echo one in a |
| 27 | // response) are the module-level `h2_conn_specific_headers` const defined in |
| 28 | // h2_mux_conn.v — shared with the client path rather than redefined here. |
| 29 | |
| 30 | // h2_all_digits reports whether s is a non-empty run of ASCII digits. A value |
| 31 | // that parses with `.int()` is NOT proof of a valid number — `'12x'.int()` is 12 |
| 32 | // — so wire-derived numerics (content-length) must be charset-checked first. |
| 33 | fn h2_all_digits(s string) bool { |
| 34 | if s.len == 0 { |
| 35 | return false |
| 36 | } |
| 37 | for ch in s { |
| 38 | if ch < `0` || ch > `9` { |
| 39 | return false |
| 40 | } |
| 41 | } |
| 42 | return true |
| 43 | } |
| 44 | |
| 45 | // h2_field_value_has_forbidden_octet reports whether a field value contains an |
| 46 | // octet RFC 9113 §8.2.1 forbids: NUL (0x00), LF (0x0a), or CR (0x0d). Such a |
| 47 | // value makes the request malformed and, if the request were forwarded to an |
| 48 | // HTTP/1.x peer, would be a header-injection / request-smuggling vector. |
| 49 | fn h2_field_value_has_forbidden_octet(value string) bool { |
| 50 | for ch in value { |
| 51 | if ch == 0 || ch == 0x0a || ch == 0x0d { |
| 52 | return true |
| 53 | } |
| 54 | } |
| 55 | return false |
| 56 | } |
| 57 | |
| 58 | // h2_request_field_error returns a non-empty reason when a regular (non-pseudo) |
| 59 | // request header field is malformed per RFC 9113 §8.2: names must be lowercase |
| 60 | // and non-empty, values must not contain NUL/CR/LF, connection-specific fields |
| 61 | // are forbidden, and TE may only carry the value "trailers". An empty return |
| 62 | // means the field is valid. |
| 63 | fn h2_request_field_error(name string, value string) string { |
| 64 | if name.len == 0 { |
| 65 | return 'empty header field name' |
| 66 | } |
| 67 | if name != name.to_lower() { |
| 68 | return 'uppercase header field name "${name}"' |
| 69 | } |
| 70 | if h2_field_value_has_forbidden_octet(value) { |
| 71 | return 'forbidden NUL/CR/LF octet in value of "${name}"' |
| 72 | } |
| 73 | if name in h2_conn_specific_headers { |
| 74 | return 'connection-specific header field "${name}"' |
| 75 | } |
| 76 | if name == 'te' && value.trim_space().to_lower() != 'trailers' { |
| 77 | return 'TE header field with a value other than "trailers"' |
| 78 | } |
| 79 | return '' |
| 80 | } |
| 81 | |
| 82 | // h2_validate_request_pseudo enforces the RFC 9113 §8.1.2/§8.3 rules a request |
| 83 | // header list must satisfy: only the request pseudo-headers, each at most once, |
| 84 | // all appearing before any regular field, with :method, :path and :scheme |
| 85 | // present; every regular field must pass h2_request_field_error. A non-ok return |
| 86 | // means the request is malformed (a stream error at the call site). |
| 87 | fn h2_validate_request_pseudo(headers []H2HeaderField) ! { |
| 88 | mut seen_regular := false |
| 89 | mut has_method := false |
| 90 | mut has_path := false |
| 91 | mut has_scheme := false |
| 92 | mut has_authority := false |
| 93 | for f in headers { |
| 94 | if f.name.starts_with(':') { |
| 95 | if seen_regular { |
| 96 | return error('pseudo-header "${f.name}" after a regular field') |
| 97 | } |
| 98 | if h2_field_value_has_forbidden_octet(f.value) { |
| 99 | return error('forbidden NUL/CR/LF octet in pseudo-header "${f.name}"') |
| 100 | } |
| 101 | match f.name { |
| 102 | ':method' { |
| 103 | if has_method { |
| 104 | return error('duplicate :method pseudo-header') |
| 105 | } |
| 106 | if f.value == '' { |
| 107 | return error('empty :method pseudo-header') |
| 108 | } |
| 109 | has_method = true |
| 110 | } |
| 111 | ':path' { |
| 112 | if has_path { |
| 113 | return error('duplicate :path pseudo-header') |
| 114 | } |
| 115 | // RFC 9113 §8.3.1: :path must not be empty for http/https. |
| 116 | if f.value == '' { |
| 117 | return error('empty :path pseudo-header') |
| 118 | } |
| 119 | has_path = true |
| 120 | } |
| 121 | ':scheme' { |
| 122 | if has_scheme { |
| 123 | return error('duplicate :scheme pseudo-header') |
| 124 | } |
| 125 | if f.value == '' { |
| 126 | return error('empty :scheme pseudo-header') |
| 127 | } |
| 128 | has_scheme = true |
| 129 | } |
| 130 | ':authority' { |
| 131 | if has_authority { |
| 132 | return error('duplicate :authority pseudo-header') |
| 133 | } |
| 134 | has_authority = true |
| 135 | } |
| 136 | else { |
| 137 | return error('unknown request pseudo-header "${f.name}"') |
| 138 | } |
| 139 | } |
| 140 | } else { |
| 141 | seen_regular = true |
| 142 | reason := h2_request_field_error(f.name, f.value) |
| 143 | if reason != '' { |
| 144 | return error(reason) |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | if !has_method || !has_path || !has_scheme { |
| 149 | return error('request omits a mandatory pseudo-header (:method/:path/:scheme)') |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | // H2ServerStream holds the state of one in-flight server-side stream. |
| 154 | struct H2ServerStream { |
| 155 | mut: |
| 156 | id u32 |
| 157 | hpack_block []u8 // assembled HEADERS + CONTINUATION fragments |
| 158 | headers []H2HeaderField |
| 159 | body []u8 |
| 160 | end_headers bool |
| 161 | end_stream bool |
| 162 | headers_done bool // set once we've decoded the HPACK block |
| 163 | send_window i64 |
| 164 | trailer_block []u8 // assembled trailer HEADERS + CONTINUATION fragments |
| 165 | in_trailers bool // set once a trailer section (a 2nd HEADERS block) begins |
| 166 | } |
| 167 | |
| 168 | // H2ServerConn drives one server-side HTTP/2 connection over a transport. |
| 169 | struct H2ServerConn { |
| 170 | mut: |
| 171 | transport H2Transport |
| 172 | encoder H2HpackEncoder |
| 173 | decoder H2HpackDecoder |
| 174 | peer H2PeerSettings |
| 175 | rbuf []u8 |
| 176 | send_window i64 = i64(h2_server_default_window) |
| 177 | streams map[u32]&H2ServerStream |
| 178 | last_stream_id u32 |
| 179 | awaiting_cont u32 // non-zero when mid-CONTINUATION on this stream |
| 180 | closing bool |
| 181 | idle_conns &TlsIdleConnTracker = unsafe { nil } |
| 182 | idle_handle int |
| 183 | } |
| 184 | |
| 185 | // serve_h2_conn drives a single HTTP/2 server-side connection until the |
| 186 | // transport closes or a protocol error forces a GOAWAY. `handler` is invoked |
| 187 | // once per fully-received request stream. |
| 188 | fn serve_h2_conn(mut transport H2Transport, mut handler Handler) ! { |
| 189 | serve_h2_conn_with_idle_tracker(mut transport, mut handler, unsafe { nil }, 0)! |
| 190 | } |
| 191 | |
| 192 | fn serve_h2_conn_with_idle_tracker(mut transport H2Transport, mut handler Handler, idle_conns &TlsIdleConnTracker, idle_handle int) ! { |
| 193 | mut c := &H2ServerConn{ |
| 194 | transport: transport |
| 195 | idle_conns: idle_conns |
| 196 | idle_handle: idle_handle |
| 197 | } |
| 198 | c.serve(mut handler) or { |
| 199 | // Best-effort GOAWAY before bailing. Skip if one was already sent by |
| 200 | // the error path (e.g. apply_settings sends FLOW_CONTROL_ERROR). |
| 201 | if !c.closing { |
| 202 | c.send_goaway(.protocol_error, err.msg()) or {} |
| 203 | } |
| 204 | return err |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | fn (mut c H2ServerConn) serve(mut handler Handler) ! { |
| 209 | // Register the connection with the idle tracker once for its whole |
| 210 | // lifetime, instead of around every frame read. The reader thread spends |
| 211 | // nearly all of its time blocked in a frame read, so per-frame mark/unmark |
| 212 | // only added shared-lock contention (and an O(n) list scan) on the hot |
| 213 | // path. On shutdown, close_idle still interrupts the reader by shutting the |
| 214 | // fd down; an h2 request in flight when the server stops is interrupted — |
| 215 | // including response writes, which may be truncated mid-DATA-frame. This |
| 216 | // is acceptable at shutdown and is not relied on by any caller (the |
| 217 | // graceful "wait for active request" guarantee is HTTP/1.1-only). |
| 218 | tracked := c.should_track_idle_read() |
| 219 | if tracked && !c.idle_conns.mark_idle(c.idle_handle) { |
| 220 | // The server is already shutting down; do not start serving. |
| 221 | return |
| 222 | } |
| 223 | defer { |
| 224 | if tracked { |
| 225 | c.idle_conns.unmark_idle(c.idle_handle) |
| 226 | } |
| 227 | } |
| 228 | c.read_client_preface()! |
| 229 | c.send_initial_settings()! |
| 230 | for !c.closing { |
| 231 | frame := c.read_frame() or { |
| 232 | // Treat a clean transport close as end of session. |
| 233 | return |
| 234 | } |
| 235 | c.dispatch_frame(frame, mut handler)! |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | fn (mut c H2ServerConn) should_track_idle_read() bool { |
| 240 | return c.idle_handle > 0 && c.idle_conns != unsafe { nil } |
| 241 | } |
| 242 | |
| 243 | fn (mut c H2ServerConn) read_client_preface() ! { |
| 244 | c.fill_at_least(h2_client_preface.len)! |
| 245 | got := c.rbuf[..h2_client_preface.len].bytestr() |
| 246 | if got != h2_client_preface { |
| 247 | return error('h2 server: invalid connection preface') |
| 248 | } |
| 249 | c.rbuf = c.rbuf[h2_client_preface.len..].clone() |
| 250 | } |
| 251 | |
| 252 | fn (mut c H2ServerConn) send_initial_settings() ! { |
| 253 | c.send_frame(H2SettingsFrame{ |
| 254 | settings: [ |
| 255 | H2Setting{h2_settings_enable_push, 0}, |
| 256 | H2Setting{h2_settings_max_concurrent_streams, h2_server_max_concurrent_streams}, |
| 257 | H2Setting{h2_settings_initial_window_size, h2_server_default_window}, |
| 258 | H2Setting{h2_settings_max_frame_size, h2_default_max_frame_size}, |
| 259 | ] |
| 260 | })! |
| 261 | } |
| 262 | |
| 263 | fn (mut c H2ServerConn) dispatch_frame(frame H2Frame, mut handler Handler) ! { |
| 264 | // RFC 7540 §6.10: once a HEADERS or PUSH_PROMISE without END_HEADERS is |
| 265 | // seen, the next frame must be a CONTINUATION on the same stream. |
| 266 | if c.awaiting_cont != 0 { |
| 267 | if frame is H2ContinuationFrame { |
| 268 | if frame.stream_id != c.awaiting_cont { |
| 269 | return error('h2 server: CONTINUATION on the wrong stream') |
| 270 | } |
| 271 | } else { |
| 272 | return error('h2 server: expected CONTINUATION after HEADERS without END_HEADERS') |
| 273 | } |
| 274 | } |
| 275 | match frame { |
| 276 | H2SettingsFrame, H2PingFrame, H2WindowUpdateFrame { |
| 277 | c.handle_control_frame(frame)! |
| 278 | } |
| 279 | H2GoawayFrame { |
| 280 | c.closing = true |
| 281 | } |
| 282 | H2RstStreamFrame { |
| 283 | c.streams.delete(frame.stream_id) |
| 284 | } |
| 285 | H2PriorityFrame { |
| 286 | // Priority is advisory; ignore. |
| 287 | } |
| 288 | H2HeadersFrame { |
| 289 | c.on_headers(frame, mut handler)! |
| 290 | } |
| 291 | H2ContinuationFrame { |
| 292 | c.on_continuation(frame, mut handler)! |
| 293 | } |
| 294 | H2DataFrame { |
| 295 | c.on_data(frame, mut handler)! |
| 296 | } |
| 297 | H2PushPromiseFrame { |
| 298 | // Clients should not push. Treat as a protocol error. |
| 299 | return error('h2 server: PUSH_PROMISE from client') |
| 300 | } |
| 301 | H2UnknownFrame { |
| 302 | // RFC 7540 §4.1: ignore unknown frame types. |
| 303 | } |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | // handle_control_frame services SETTINGS, PING, and WINDOW_UPDATE frames that |
| 308 | // may arrive at any point in the session, including while a response write is |
| 309 | // blocked in send_body waiting for flow-control credit. Both dispatch_frame and |
| 310 | // pump_for_window delegate to this function so the logic — and any validation |
| 311 | // errors — exist in exactly one place. |
| 312 | fn (mut c H2ServerConn) handle_control_frame(frame H2Frame) ! { |
| 313 | match frame { |
| 314 | H2SettingsFrame { |
| 315 | if !frame.ack { |
| 316 | c.apply_settings(frame.settings)! |
| 317 | c.send_frame(H2SettingsFrame{ |
| 318 | ack: true |
| 319 | })! |
| 320 | } |
| 321 | } |
| 322 | H2PingFrame { |
| 323 | if !frame.ack { |
| 324 | c.send_frame(H2PingFrame{ |
| 325 | ack: true |
| 326 | data: frame.data |
| 327 | })! |
| 328 | } |
| 329 | } |
| 330 | H2WindowUpdateFrame { |
| 331 | if frame.stream_id == 0 { |
| 332 | c.send_window += i64(frame.window_size_increment) |
| 333 | } else if mut s := c.streams[frame.stream_id] { |
| 334 | s.send_window += i64(frame.window_size_increment) |
| 335 | } |
| 336 | } |
| 337 | else {} |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | fn (mut c H2ServerConn) apply_settings(settings []H2Setting) ! { |
| 342 | for s in settings { |
| 343 | match s.id { |
| 344 | h2_settings_header_table_size { |
| 345 | // Constrains our *encoder* (the server's response-header encoder |
| 346 | // whose output the client decodes with its advertised table size). |
| 347 | c.peer.header_table_size = s.value |
| 348 | c.encoder.dyn_table.set_max_size(int(s.value)) |
| 349 | c.encoder.pending_max_table_size = int(s.value) |
| 350 | } |
| 351 | h2_settings_enable_push { |
| 352 | c.peer.enable_push = s.value != 0 |
| 353 | } |
| 354 | h2_settings_max_concurrent_streams { |
| 355 | c.peer.max_concurrent_streams = s.value |
| 356 | } |
| 357 | h2_settings_initial_window_size { |
| 358 | // RFC 7540 §6.5.3: values above 2^31-1 are a FLOW_CONTROL_ERROR, |
| 359 | // not a PROTOCOL_ERROR; send the correct code before unwinding. |
| 360 | if s.value > u32(0x7fff_ffff) { |
| 361 | c.send_goaway(.flow_control_error, |
| 362 | 'SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1') or {} |
| 363 | return error('h2 server: SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1 (FLOW_CONTROL_ERROR)') |
| 364 | } |
| 365 | // RFC 7540 §6.9.2: validate ALL stream windows before mutating any |
| 366 | // so that a delta overflow on stream N does not leave streams 1..N-1 |
| 367 | // partially updated. Send FLOW_CONTROL_ERROR (not PROTOCOL_ERROR). |
| 368 | delta := i64(s.value) - i64(c.peer.initial_window_size) |
| 369 | for _, st in c.streams { |
| 370 | if st.send_window + delta > i64(0x7fff_ffff) { |
| 371 | c.send_goaway(.flow_control_error, |
| 372 | 'SETTINGS_INITIAL_WINDOW_SIZE delta overflows stream ${st.id} send window') or {} |
| 373 | return error('h2 server: SETTINGS_INITIAL_WINDOW_SIZE delta overflows stream ${st.id} send window (RFC 7540 §6.9.2 FLOW_CONTROL_ERROR)') |
| 374 | } |
| 375 | } |
| 376 | c.peer.initial_window_size = s.value |
| 377 | for _, mut st in c.streams { |
| 378 | st.send_window += delta |
| 379 | } |
| 380 | } |
| 381 | h2_settings_max_frame_size { |
| 382 | // RFC 7540 §6.5.2: valid range is 2^14 (16384)..2^24-1 inclusive. |
| 383 | if s.value < h2_default_max_frame_size || s.value > h2_max_max_frame_size { |
| 384 | return error('h2 server: SETTINGS_MAX_FRAME_SIZE ${s.value} out of range [16384, 16777215]') |
| 385 | } |
| 386 | c.peer.max_frame_size = s.value |
| 387 | } |
| 388 | h2_settings_max_header_list_size { |
| 389 | c.peer.max_header_list_size = s.value |
| 390 | } |
| 391 | else {} |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | fn (mut c H2ServerConn) on_headers(frame H2HeadersFrame, mut handler Handler) ! { |
| 397 | // A HEADERS block for a stream that is already open is a trailer section |
| 398 | // (RFC 9113 §8.1), not a new request. |
| 399 | if mut existing := c.streams[frame.stream_id] { |
| 400 | c.on_trailers(mut existing, frame, mut handler)! |
| 401 | return |
| 402 | } |
| 403 | // Stream ids from the client must be odd and strictly increasing. |
| 404 | if frame.stream_id & 1 == 0 || frame.stream_id <= c.last_stream_id { |
| 405 | return error('h2 server: invalid client stream id ${frame.stream_id}') |
| 406 | } |
| 407 | c.last_stream_id = frame.stream_id |
| 408 | mut s := &H2ServerStream{ |
| 409 | id: frame.stream_id |
| 410 | hpack_block: frame.fragment.clone() |
| 411 | end_headers: frame.end_headers |
| 412 | end_stream: frame.end_stream |
| 413 | send_window: i64(c.peer.initial_window_size) |
| 414 | } |
| 415 | c.streams[frame.stream_id] = s |
| 416 | if !frame.end_headers { |
| 417 | c.awaiting_cont = frame.stream_id |
| 418 | return |
| 419 | } |
| 420 | c.finalize_headers(mut s, mut handler)! |
| 421 | } |
| 422 | |
| 423 | fn (mut c H2ServerConn) on_continuation(frame H2ContinuationFrame, mut handler Handler) ! { |
| 424 | mut s := c.streams[frame.stream_id] or { |
| 425 | return error('h2 server: CONTINUATION for unknown stream ${frame.stream_id}') |
| 426 | } |
| 427 | if s.in_trailers { |
| 428 | if s.trailer_block.len + frame.fragment.len > h2_max_recv_header_block { |
| 429 | return error('h2 server: trailer block exceeds ${h2_max_recv_header_block} bytes') |
| 430 | } |
| 431 | s.trailer_block << frame.fragment |
| 432 | if frame.end_headers { |
| 433 | c.awaiting_cont = 0 |
| 434 | c.finalize_trailers(mut s, mut handler)! |
| 435 | } |
| 436 | return |
| 437 | } |
| 438 | if s.hpack_block.len + frame.fragment.len > h2_max_recv_header_block { |
| 439 | return error('h2 server: request header block exceeds ${h2_max_recv_header_block} bytes') |
| 440 | } |
| 441 | s.hpack_block << frame.fragment |
| 442 | if frame.end_headers { |
| 443 | s.end_headers = true |
| 444 | c.awaiting_cont = 0 |
| 445 | c.finalize_headers(mut s, mut handler)! |
| 446 | } |
| 447 | } |
| 448 | |
| 449 | fn (mut c H2ServerConn) finalize_headers(mut s H2ServerStream, mut handler Handler) ! { |
| 450 | // An HPACK decoding failure is a CONNECTION error (RFC 9113 §4.3): the decoder |
| 451 | // mutates the dynamic table as it runs, so a failure leaves our table out of |
| 452 | // sync with the peer and no later header block on this connection can be decoded |
| 453 | // reliably. Close the connection with COMPRESSION_ERROR rather than RST-ing one |
| 454 | // stream. (send_goaway sets c.closing, so serve()'s catch won't re-GOAWAY.) |
| 455 | s.headers = c.decoder.decode(s.hpack_block) or { |
| 456 | c.send_goaway(.compression_error, 'HPACK decode error') or {} |
| 457 | return error('h2 server: HPACK decode error (COMPRESSION_ERROR)') |
| 458 | } |
| 459 | s.headers_done = true |
| 460 | // A well-decoded but malformed request is a STREAM error (RFC 9113 §8.1.1) — |
| 461 | // reset just this stream and keep the connection alive. |
| 462 | h2_validate_request_pseudo(s.headers) or { |
| 463 | c.send_rst_stream(s.id, .protocol_error)! |
| 464 | c.streams.delete(s.id) |
| 465 | return |
| 466 | } |
| 467 | if s.end_stream { |
| 468 | c.run_request(mut s, mut handler)! |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | // on_trailers handles a second HEADERS block on an already-open stream: an |
| 473 | // HTTP/2 trailer section (RFC 9113 §8.1). The block is always assembled and |
| 474 | // decoded; whether it is well-formed — it must carry END_STREAM and no |
| 475 | // pseudo-headers — is decided in finalize_trailers. A well-decoded but malformed |
| 476 | // trailer section is reset as a stream error; an HPACK decode failure is a |
| 477 | // connection error (§4.3) since it desyncs the decoder. |
| 478 | fn (mut c H2ServerConn) on_trailers(mut s H2ServerStream, frame H2HeadersFrame, mut handler Handler) ! { |
| 479 | s.in_trailers = true |
| 480 | s.end_stream = frame.end_stream |
| 481 | s.trailer_block << frame.fragment |
| 482 | if !frame.end_headers { |
| 483 | c.awaiting_cont = frame.stream_id |
| 484 | return |
| 485 | } |
| 486 | c.finalize_trailers(mut s, mut handler)! |
| 487 | } |
| 488 | |
| 489 | fn (mut c H2ServerConn) finalize_trailers(mut s H2ServerStream, mut handler Handler) ! { |
| 490 | // An HPACK decoding failure is a CONNECTION error (RFC 9113 §4.3): the decoder's |
| 491 | // dynamic table is now out of sync with the peer. Close the connection rather |
| 492 | // than RST one stream. |
| 493 | trailers := c.decoder.decode(s.trailer_block) or { |
| 494 | c.send_goaway(.compression_error, 'HPACK decode error in trailers') or {} |
| 495 | return error('h2 server: HPACK decode error in trailers (COMPRESSION_ERROR)') |
| 496 | } |
| 497 | // A well-decoded but malformed trailer section is a STREAM error (§8.1.1). |
| 498 | mut reason := if !s.end_stream { |
| 499 | // A trailer section MUST terminate the stream (RFC 9113 §8.1). |
| 500 | 'trailing HEADERS without END_STREAM' |
| 501 | } else { |
| 502 | '' |
| 503 | } |
| 504 | if reason == '' { |
| 505 | for f in trailers { |
| 506 | // Trailers carry no pseudo-headers and follow the §8.2 field rules. |
| 507 | reason = if f.name.starts_with(':') { |
| 508 | 'pseudo-header "${f.name}" in trailers' |
| 509 | } else { |
| 510 | h2_request_field_error(f.name, f.value) |
| 511 | } |
| 512 | if reason != '' { |
| 513 | break |
| 514 | } |
| 515 | } |
| 516 | } |
| 517 | if reason != '' { |
| 518 | c.send_rst_stream(s.id, .protocol_error)! |
| 519 | c.streams.delete(s.id) |
| 520 | return |
| 521 | } |
| 522 | c.run_request(mut s, mut handler)! |
| 523 | } |
| 524 | |
| 525 | fn (mut c H2ServerConn) on_data(frame H2DataFrame, mut handler Handler) ! { |
| 526 | mut s := c.streams[frame.stream_id] or { |
| 527 | // DATA for an unknown stream (likely already RST'd); just drop and |
| 528 | // keep flow control consistent. Credit flow_size (full wire bytes |
| 529 | // including padding) per RFC 7540 §6.9.1. |
| 530 | if frame.flow_size > 0 { |
| 531 | c.send_window_update(0, u32(frame.flow_size))! |
| 532 | } |
| 533 | return |
| 534 | } |
| 535 | if !s.headers_done { |
| 536 | return error('h2 server: DATA before END_HEADERS') |
| 537 | } |
| 538 | if frame.data.len > 0 { |
| 539 | if s.body.len + frame.data.len > h2_server_max_request_body { |
| 540 | // Credit the connection window before resetting so the peer is |
| 541 | // not penalised for bytes it legitimately sent within its window. |
| 542 | c.send_window_update(0, u32(frame.flow_size)) or {} |
| 543 | c.send_rst_stream(s.id, .refused_stream)! |
| 544 | c.streams.delete(s.id) |
| 545 | return |
| 546 | } |
| 547 | s.body << frame.data |
| 548 | } |
| 549 | // Replenish flow control using flow_size (full wire payload including padding), |
| 550 | // per RFC 7540 §6.9.1. Credit unconditionally when flow_size>0: a padding-only |
| 551 | // DATA frame (data.len==0, flow_size>0) still consumes the peer's send window |
| 552 | // and must be credited back. (Formerly inside the data.len>0 block — padding-only |
| 553 | // frames leaked window silently.) |
| 554 | if frame.flow_size > 0 { |
| 555 | c.send_window_update(0, u32(frame.flow_size))! |
| 556 | c.send_window_update(s.id, u32(frame.flow_size))! |
| 557 | } |
| 558 | if frame.end_stream { |
| 559 | s.end_stream = true |
| 560 | c.run_request(mut s, mut handler)! |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | fn (mut c H2ServerConn) run_request(mut s H2ServerStream, mut handler Handler) ! { |
| 565 | req := c.build_request(s) or { |
| 566 | c.send_rst_stream(s.id, .protocol_error)! |
| 567 | c.streams.delete(s.id) |
| 568 | return |
| 569 | } |
| 570 | resp := handler.handle(req) |
| 571 | c.send_response(s.id, resp)! |
| 572 | c.streams.delete(s.id) |
| 573 | } |
| 574 | |
| 575 | fn (mut c H2ServerConn) build_request(s &H2ServerStream) !Request { |
| 576 | mut req := Request{ |
| 577 | version: .v2_0 |
| 578 | header: new_header() |
| 579 | } |
| 580 | // Pseudo-headers were already validated in h2_validate_request_pseudo |
| 581 | // (presence, no duplicates, ordering, allowed set); here we only extract. |
| 582 | mut method := '' |
| 583 | mut path := '' |
| 584 | mut authority := '' |
| 585 | mut content_length := -1 |
| 586 | for f in s.headers { |
| 587 | match f.name { |
| 588 | ':method' { |
| 589 | method = f.value |
| 590 | } |
| 591 | ':path' { |
| 592 | path = f.value |
| 593 | } |
| 594 | ':authority' { |
| 595 | authority = f.value |
| 596 | } |
| 597 | ':scheme' { |
| 598 | // Validated already; handlers infer the scheme from Host, matching |
| 599 | // the HTTP/1.1 path. |
| 600 | } |
| 601 | else { |
| 602 | if f.name.starts_with(':') { |
| 603 | continue |
| 604 | } |
| 605 | if f.name == 'content-length' { |
| 606 | if !h2_all_digits(f.value) { |
| 607 | return error('h2 server: malformed content-length "${f.value}"') |
| 608 | } |
| 609 | cl := f.value.int() |
| 610 | // Multiple content-length fields with differing values are a |
| 611 | // malformed request (RFC 9110 §8.6); validate every occurrence, |
| 612 | // not just the last. |
| 613 | if content_length >= 0 && content_length != cl { |
| 614 | return error('h2 server: conflicting content-length values ${content_length} and ${cl}') |
| 615 | } |
| 616 | content_length = cl |
| 617 | } |
| 618 | req.header.add_custom(f.name, f.value) or {} |
| 619 | } |
| 620 | } |
| 621 | } |
| 622 | // RFC 9113 §8.1.2.6: a declared content-length MUST equal the sum of the DATA |
| 623 | // payload lengths; a mismatch is a malformed request (stream error). |
| 624 | if content_length >= 0 && content_length != s.body.len { |
| 625 | return error('h2 server: content-length ${content_length} != DATA length ${s.body.len}') |
| 626 | } |
| 627 | req.method = method_from_str(method) |
| 628 | if authority != '' && !req.header.contains(.host) { |
| 629 | req.header.add(.host, authority) |
| 630 | } |
| 631 | // Match the HTTP/1.1 path: req.url is the request-target (the :path |
| 632 | // pseudo-header), so handlers see the same shape on both transports. |
| 633 | req.url = path |
| 634 | req.data = s.body.bytestr() |
| 635 | req.host = authority |
| 636 | return req |
| 637 | } |
| 638 | |
| 639 | fn (mut c H2ServerConn) send_response(stream_id u32, resp Response) ! { |
| 640 | status := if resp.status_code == 0 { 200 } else { resp.status_code } |
| 641 | mut fields := [H2HeaderField{':status', status.str()}] |
| 642 | for key in resp.header.keys() { |
| 643 | lkey := key.to_lower() |
| 644 | // Drop hop-by-hop headers; HTTP/2 forbids them (RFC 9113 §8.2.2). |
| 645 | if lkey in h2_conn_specific_headers { |
| 646 | continue |
| 647 | } |
| 648 | for val in resp.header.custom_values(key) { |
| 649 | fields << H2HeaderField{lkey, val} |
| 650 | } |
| 651 | } |
| 652 | body := resp.body.bytes() |
| 653 | has_body := body.len > 0 |
| 654 | block := c.encoder.encode(fields) |
| 655 | c.send_header_block(stream_id, block, !has_body)! |
| 656 | if has_body { |
| 657 | c.send_body(stream_id, body)! |
| 658 | } |
| 659 | } |
| 660 | |
| 661 | fn (mut c H2ServerConn) send_header_block(stream_id u32, block []u8, end_stream bool) ! { |
| 662 | max := int(c.peer.max_frame_size) |
| 663 | if block.len <= max { |
| 664 | c.send_frame(H2HeadersFrame{ |
| 665 | stream_id: stream_id |
| 666 | fragment: block |
| 667 | end_headers: true |
| 668 | end_stream: end_stream |
| 669 | })! |
| 670 | return |
| 671 | } |
| 672 | c.send_frame(H2HeadersFrame{ |
| 673 | stream_id: stream_id |
| 674 | fragment: block[..max] |
| 675 | end_headers: false |
| 676 | end_stream: end_stream |
| 677 | })! |
| 678 | mut off := max |
| 679 | for off < block.len { |
| 680 | mut next := off + max |
| 681 | if next > block.len { |
| 682 | next = block.len |
| 683 | } |
| 684 | c.send_frame(H2ContinuationFrame{ |
| 685 | stream_id: stream_id |
| 686 | fragment: block[off..next] |
| 687 | end_headers: next == block.len |
| 688 | })! |
| 689 | off = next |
| 690 | } |
| 691 | } |
| 692 | |
| 693 | fn (mut c H2ServerConn) send_body(stream_id u32, body []u8) ! { |
| 694 | max := int(c.peer.max_frame_size) |
| 695 | mut off := 0 |
| 696 | for off < body.len { |
| 697 | // Respect both the connection and per-stream send windows |
| 698 | // (RFC 7540 Section 6.9). When either is exhausted, read frames until |
| 699 | // the peer grows a window with WINDOW_UPDATE. |
| 700 | for c.send_window <= 0 || c.stream_send_window(stream_id) <= 0 { |
| 701 | c.pump_for_window(stream_id)! |
| 702 | } |
| 703 | avail := if c.send_window < c.stream_send_window(stream_id) { |
| 704 | c.send_window |
| 705 | } else { |
| 706 | c.stream_send_window(stream_id) |
| 707 | } |
| 708 | mut chunk := body.len - off |
| 709 | if chunk > max { |
| 710 | chunk = max |
| 711 | } |
| 712 | if i64(chunk) > avail { |
| 713 | chunk = int(avail) |
| 714 | } |
| 715 | next := off + chunk |
| 716 | c.send_frame(H2DataFrame{ |
| 717 | stream_id: stream_id |
| 718 | data: body[off..next] |
| 719 | end_stream: next == body.len |
| 720 | })! |
| 721 | c.send_window -= i64(chunk) |
| 722 | if mut s := c.streams[stream_id] { |
| 723 | s.send_window -= i64(chunk) |
| 724 | } |
| 725 | off = next |
| 726 | } |
| 727 | } |
| 728 | |
| 729 | // stream_send_window returns the current per-stream send window, or 0 if the |
| 730 | // stream is gone. |
| 731 | fn (c &H2ServerConn) stream_send_window(stream_id u32) i64 { |
| 732 | if s := c.streams[stream_id] { |
| 733 | return s.send_window |
| 734 | } |
| 735 | return 0 |
| 736 | } |
| 737 | |
| 738 | // pump_for_window reads one frame while a response is blocked on flow control, |
| 739 | // servicing control frames (SETTINGS / PING / WINDOW_UPDATE) via |
| 740 | // handle_control_frame and aborting on RST_STREAM for the active stream. |
| 741 | fn (mut c H2ServerConn) pump_for_window(stream_id u32) ! { |
| 742 | frame := c.read_frame()! |
| 743 | c.handle_control_frame(frame)! |
| 744 | if frame is H2RstStreamFrame { |
| 745 | if frame.stream_id == stream_id { |
| 746 | return error('h2 server: stream reset by peer while writing response') |
| 747 | } |
| 748 | } |
| 749 | // With SETTINGS_MAX_CONCURRENT_STREAMS=1 no other stream frames are |
| 750 | // expected mid-response; ignore anything else defensively. |
| 751 | } |
| 752 | |
| 753 | fn (mut c H2ServerConn) send_window_update(stream_id u32, inc u32) ! { |
| 754 | if inc == 0 { |
| 755 | return |
| 756 | } |
| 757 | c.send_frame(H2WindowUpdateFrame{ |
| 758 | stream_id: stream_id |
| 759 | window_size_increment: inc |
| 760 | })! |
| 761 | } |
| 762 | |
| 763 | fn (mut c H2ServerConn) send_rst_stream(stream_id u32, code H2ErrorCode) ! { |
| 764 | c.send_frame(H2RstStreamFrame{ |
| 765 | stream_id: stream_id |
| 766 | error_code: u32(code) |
| 767 | })! |
| 768 | } |
| 769 | |
| 770 | fn (mut c H2ServerConn) send_goaway(code H2ErrorCode, msg string) ! { |
| 771 | c.send_frame(H2GoawayFrame{ |
| 772 | last_stream_id: c.last_stream_id |
| 773 | error_code: u32(code) |
| 774 | debug_data: msg.bytes() |
| 775 | })! |
| 776 | c.closing = true |
| 777 | } |
| 778 | |
| 779 | fn (mut c H2ServerConn) read_frame() !H2Frame { |
| 780 | c.fill_at_least(h2_frame_header_len)! |
| 781 | header := h2_parse_frame_header(c.rbuf)! |
| 782 | // Check against our own advertised receive limit (sent in send_initial_settings). |
| 783 | // H2ServerConn always advertises h2_default_max_frame_size and never renegotiates |
| 784 | // it, so the constant is correct here. c.peer.max_frame_size is the client's |
| 785 | // receive limit (our outbound cap) and must not be used for inbound checking. |
| 786 | if header.length > h2_default_max_frame_size { |
| 787 | return error('h2 server: frame larger than SETTINGS_MAX_FRAME_SIZE (${header.length})') |
| 788 | } |
| 789 | total := h2_frame_header_len + int(header.length) |
| 790 | c.fill_at_least(total)! |
| 791 | frame := h2_parse_frame(header, c.rbuf[h2_frame_header_len..total])! |
| 792 | c.rbuf = c.rbuf[total..].clone() |
| 793 | return frame |
| 794 | } |
| 795 | |
| 796 | fn (mut c H2ServerConn) fill_at_least(n int) ! { |
| 797 | for c.rbuf.len < n { |
| 798 | mut tmp := []u8{len: h2_conn_read_chunk} |
| 799 | got := c.transport.read(mut tmp)! |
| 800 | if got <= 0 { |
| 801 | return error('h2 server: connection closed by peer') |
| 802 | } |
| 803 | c.rbuf << tmp[..got] |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | fn (mut c H2ServerConn) send_frame(f H2Frame) ! { |
| 808 | c.write_all(f.encode())! |
| 809 | } |
| 810 | |
| 811 | fn (mut c H2ServerConn) write_all(data []u8) ! { |
| 812 | mut sent := 0 |
| 813 | for sent < data.len { |
| 814 | n := c.transport.write(data[sent..])! |
| 815 | if n <= 0 { |
| 816 | return error('h2 server: transport write returned ${n}') |
| 817 | } |
| 818 | sent += n |
| 819 | } |
| 820 | } |
| 821 | |