1 # HTTP/1.1 and HTTP/2 client, with an HTTP/1.1 server. Source: https://github.com/paymog/bend-kit/tree/main/http 2 import Base 3 # By hash: bend-kit-url@0.4.1.0, -encoding@0.3.0.0, -json@0.5.0.1, -bytes@0.3.0.0, -dns@0.4.0.1, -zlib@0.1.5.0. 4 import bend-kit-wire@0.4.2.0/wire.bend as Wire 5 import bend-kit-http2@0.1.2.0/http2.bend as H2 6 import bend-kit-http2@0.1.2.0/hpack.bend as Hpack 7 import bend-kit-bytes@0.3.1.0/bytes.bend as HBytes 8 import bend-kit-time@0.1.0.0/time.bend as Time 9 import bend-kit-int@0.2.0.0/int.bend as Int 10 import 0x1f2d80f53f971b16c6de6a65cb1918ae/url.bend as Url 11 import 0xcfc8be7b076f41f95c8e118383892d55/encoding.bend as Enc 12 import 0x584fc27920487ceab242392391418d7f/json.bend as Json 13 import 0x49814d83de8f70993a43e1002be29ecd/bytes.bend as Bytes 14 import 0x296c14c91cb121bd2f42e9d9f730480d/dns.bend as Dns 15 import 0x58bd522479d133cfe1b2c4451fc1a28f/zlib.bend as Zlib 16 17 # HTTP/1.1 codec and client (RFC 9112) for http:// and https:// (OpenSSL 3 at run time). 18 # parse: start-line → field lines → blank → body length (§6.3); chunked (§7.1). 19 # Bodies are Bytes. The String parsers below are the spec: one Char per octet. 20 # fetch reads until frame() says the response is whole. It returns Result, not Maybe. 21 # Build with -o for big bodies: the `bend file.bend` runner overflows on ~30 KB strings; native binaries do not. 22 # import bend-kit-http@0.20.1.0/http.bend as Http 23 24 type Req is Type: 25 Req{method: String, path: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes} 26 27 type Res is Type: 28 Res{status: U32, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes} 29 30 # The body type and its conversions, so a caller needs no bytes import. 31 def Body() -> Type: 32 Bytes.Bytes 33 34 # A byte string (one Char per octet) to a body, and back. 35 def from_string(+s: String) -> Bytes.Bytes: 36 Bytes.from_string(s) 37 38 def to_string(b: Bytes.Bytes) -> String: 39 Bytes.to_string(b) 40 41 def length(b: Bytes.Bytes) -> Bytes.Bytes & U32: 42 Bytes.length(b) 43 44 # Why fetch failed. Wire errors keep the errno and the text the effect already had. 45 # ponytail: 60 and 110 are ETIMEDOUT on macOS and Linux. Windows is out of scope. 46 type Err is Data: 47 ErrUrl{} 48 ErrDns{} 49 ErrConnect{code: U32, why: String} 50 ErrTls{code: U32, why: String} 51 ErrRead{code: U32, why: String} 52 ErrWrite{code: U32, why: String} 53 ErrTimeout{} 54 ErrRedirect{} 55 ErrBad{} 56 57 def err.late(+code: U32) -> Bool: 58 Bool.or(U32.is_eq(code, 60), U32.is_eq(code, 110)) 59 60 def err.pick(e: Err, late: Bool) -> Err: 61 match late: 62 case True{}: 63 ErrTimeout{} 64 case False{}: 65 e 66 67 def err.or_late(code: U32, e: Err) -> Err: 68 err.pick(e, err.late(code)) 69 70 type Fields is Data: 71 FieldsBad{} 72 FieldsOk{m: Map<&2, List<&2, String>>} 73 74 # One list per field name, in arrival order. header() is the first value. 75 # Set-Cookie is never joined. Encode writes one line per value. 76 def empty() -> Map<&2, List<&2, String>>: 77 Map.new(&2, List<&2, String>) 78 79 def fields.of(r: Map<&2, List<&2, String>> & List<&2, String>) -> List<&2, String>: 80 (h, xs) = r 81 xs 82 83 def fields(+h: Map<&2, List<&2, String>>, k: String) -> List<&2, String>: 84 fields.of(Map.get(List<&2, String>, Nil{}, h, k)) 85 86 def header.first(xs: List<&2, String>) -> String: 87 match xs: 88 case Nil{}: 89 "" 90 case Con{v, t}: 91 v 92 93 def header(+h: Map<&2, List<&2, String>>, k: String) -> String: 94 header.first(fields(h, k)) 95 96 def field.last(xs: List<&2, String>) -> String: 97 match xs: 98 case Nil{}: 99 "" 100 case Con{v, Nil{}}: 101 v 102 case Con{v, t}: 103 field.last(t) 104 105 # Transfer-Encoding is a list; chunked, when present, is the last coding. 106 def header.last(+h: Map<&2, List<&2, String>>, k: String) -> String: 107 field.last(fields(h, k)) 108 109 def snoc(xs: List<&2, String>, v: String) -> List<&2, String>: 110 match xs: 111 case Nil{}: 112 [v] 113 case Con{h, t}: 114 Con{h, snoc(t, v)} 115 116 def set(m: Map<&2, List<&2, String>>, k: String, v: String) -> Map<&2, List<&2, String>>: 117 Map.set(&2, List<&2, String>, m, k, [v]) 118 119 def add(+m: Map<&2, List<&2, String>>, +k: String, v: String) -> Map<&2, List<&2, String>>: 120 Map.set(&2, List<&2, String>, m, k, snoc(fields(m, k), v)) 121 122 def sanitize.cons(ch: Char, rest: String, drop: Bool) -> String: 123 match drop: 124 case True{}: 125 rest 126 case False{}: 127 SCon{ch, rest} 128 129 def sanitize(s: String) -> String: 130 match s: 131 case SNil{}: 132 SNil{} 133 case SCon{Chr{+c}, t}: 134 sanitize.cons(Chr{c}, sanitize(t), Bool.or(U32.is_eq(c, 13), U32.is_eq(c, 10))) 135 136 def drop_cr.last.if(cr: Bool, c: U32) -> String: 137 match cr: 138 case True{}: 139 SNil{} 140 case False{}: 141 SCon{Chr{c}, SNil{}} 142 143 def drop_cr.last(+c: U32) -> String: 144 drop_cr.last.if(U32.is_eq(c, 13), c) 145 146 # prev is the char before s; it is kept unless it is a final CR. 147 def drop_cr.go(s: String, prev: U32) -> String: 148 match s: 149 case SNil{}: 150 drop_cr.last(prev) 151 case SCon{Chr{c}, t}: 152 SCon{Chr{prev}, drop_cr.go(t, c)} 153 154 def drop_cr(s: String) -> String: 155 match s: 156 case SNil{}: 157 SNil{} 158 case SCon{Chr{c}, t}: 159 drop_cr.go(t, c) 160 161 # w holds the last four octets; a char past 255 clears it. 162 def blank_end.go(s: String, w: U32) -> Bool: 163 match s: 164 case SNil{}: 165 U32.is_eq(w, 218762506) 166 case SCon{Chr{+c}, t}: 167 blank_end.go(t, Bool.pick(U32, U32.is_lt(c, 256), (w * 256 + c : U32), 0)) 168 169 # String.ends_with(s, "\r\n\r\n") in one pass, with no reversed copy. 170 def blank_end(s: String) -> Bool: 171 blank_end.go(s, 0) 172 173 def trim_end.cons(h: Char, r: String, space: Bool) -> String: 174 match r: 175 case SNil{}: 176 match space: 177 case True{}: 178 SNil{} 179 case False{}: 180 SCon{h, SNil{}} 181 case SCon{a, t}: 182 SCon{h, SCon{a, t}} 183 184 # String.trim_end without the two reversed copies. 185 def trim_end(s: String) -> String: 186 match s: 187 case SNil{}: 188 SNil{} 189 case SCon{+h, t}: 190 trim_end.cons(h, trim_end(t), Char.is_space(h)) 191 192 def trim(s: String) -> String: 193 trim_end(String.trim_start(s)) 194 195 # Bool.pick evaluates both arms; a split that recursed inside it would reverse acc at every byte. 196 def take.cut(acc: String, rest: String) -> String & String: 197 (String.reverse(String.drop(acc, 1n)), rest) 198 199 # hit: the last char pushed onto acc was the stop char. 200 def take_sp.go(s: String, acc: String, hit: Bool) -> String & String: 201 match s: 202 case SNil{}: 203 match hit: 204 case True{}: 205 take.cut(acc, SNil{}) 206 case False{}: 207 (String.reverse(acc), SNil{}) 208 case SCon{Chr{+c}, t}: 209 match hit: 210 case True{}: 211 take.cut(acc, SCon{Chr{c}, t}) 212 case False{}: 213 take_sp.go(t, SCon{Chr{c}, acc}, U32.is_eq(c, 32)) 214 215 def take_sp(s: String) -> String & String: 216 take_sp.go(s, SNil{}, False{}) 217 218 def start_line.ver(m: String, pv: String & String) -> String & String & String: 219 (p, v) = pv 220 (m, p, drop_cr(v)) 221 222 def start_line.rest(mr: String & String) -> String & String & String: 223 (m, r) = mr 224 start_line.ver(m, take_sp(r)) 225 226 def start_line(s: String) -> String & String & String: 227 start_line.rest(take_sp(s)) 228 229 def split_at_blank.go(s: String, e: Bool, w: U32, acc: String) -> String & String: 230 match s: 231 case SNil{}: 232 (String.reverse(acc), SNil{}) 233 case SCon{Chr{+a}, t}: 234 match e: 235 case True{}: 236 (String.reverse(acc), SCon{Chr{a}, t}) 237 case False{}: 238 +w2 = (w * 256 + a : U32) 239 split_at_blank.go(t, U32.is_eq(w2, 218762506), w2, SCon{Chr{a}, acc}) 240 241 # split_at_blank, plus whether the head ends in the blank line. 242 def split_at_blank.flag.go(+raw: String, s: String, off: Nat, w: U32, e: Bool) -> String & String & Bool: 243 match s: 244 case SNil{}: 245 (String.take(raw, off), SNil{}, e) 246 case SCon{Chr{+a}, t}: 247 match e: 248 case True{}: 249 (String.take(raw, off), SCon{Chr{a}, t}, True{}) 250 case False{}: 251 +w2 = (w * 256 + a : U32) 252 +off1 = (1n + off : Nat) 253 split_at_blank.flag.go(raw, t, off1, w2, U32.is_eq(w2, 218762506)) 254 255 def split_at_blank.pair(r: String & String & Bool) -> String & String: 256 (head, rest, whole) = r 257 (head, rest) 258 259 def split_at_blank(+s: String) -> String & String: 260 split_at_blank.pair(split_at_blank.flag.go(s, s, 0n, 0, False{})) 261 262 def split_colon.go(s: String, acc: String, hit: Bool) -> String & String: 263 match s: 264 case SNil{}: 265 match hit: 266 case True{}: 267 take.cut(acc, SNil{}) 268 case False{}: 269 (String.reverse(acc), SNil{}) 270 case SCon{Chr{+c}, t}: 271 match hit: 272 case True{}: 273 take.cut(acc, SCon{Chr{c}, t}) 274 case False{}: 275 split_colon.go(t, SCon{Char.to_lower(Chr{c}), acc}, U32.is_eq(c, 58)) 276 277 def split_colon(+s: String) -> String & String: 278 split_colon.go(s, SNil{}, False{}) 279 280 def has_header.of(r: Map<&2, List<&2, String>> & Bool) -> Bool: 281 (h, b) = r 282 b 283 284 def has_header(+h: Map<&2, List<&2, String>>, k: String) -> Bool: 285 has_header.of(Map.has(&2, List<&2, String>, h, k)) 286 287 def folded(+line: String) -> Bool: 288 Bool.or(String.starts_with(line, " "), String.starts_with(line, "\t")) 289 290 def tracked(+k: String) -> Bool: 291 Bool.or(String.eq(k, "host"), String.eq(k, "content-length")) 292 293 def fields_put.dup(m: Map<&2, List<&2, String>>, k: String, v: String, bad: Bool) -> Fields: 294 match bad: 295 case True{}: 296 FieldsBad{} 297 case False{}: 298 FieldsOk{add(m, k, v)} 299 300 def fields_put.tracked(+m: Map<&2, List<&2, String>>, +k: String, v: String, t: Bool) -> Fields: 301 match t: 302 case True{}: 303 fields_put.dup(m, k, v, has_header(m, k)) 304 case False{}: 305 FieldsOk{add(m, k, v)} 306 307 def fields_put.key(+m: Map<&2, List<&2, String>>, +k: String, v: String) -> Fields: 308 fields_put.tracked(m, k, v, tracked(k)) 309 310 def fields_put.kv(m: Map<&2, List<&2, String>>, kv: String & String) -> Fields: 311 (k, v) = kv 312 fields_put.key(m, k, trim(v)) 313 314 def fields_put.fold(m: Map<&2, List<&2, String>>, line: String, fold: Bool) -> Fields: 315 match fold: 316 case True{}: 317 FieldsBad{} 318 case False{}: 319 fields_put.kv(m, split_colon(line)) 320 321 def fields_put.empty(m: Map<&2, List<&2, String>>, +line: String, e: Bool) -> Fields: 322 match e: 323 case True{}: 324 FieldsOk{m} 325 case False{}: 326 fields_put.fold(m, line, folded(line)) 327 328 def fields_put.ok(m: Map<&2, List<&2, String>>, +line: String) -> Fields: 329 fields_put.empty(m, line, String.is_empty(line)) 330 331 def fields_put(acc: Fields, line: String) -> Fields: 332 match acc: 333 case FieldsBad{}: 334 FieldsBad{} 335 case FieldsOk{m}: 336 fields_put.ok(m, line) 337 338 def parse.headers.done(acc: Fields) -> Maybe<&2, Map<&2, List<&2, String>>>: 339 match acc: 340 case FieldsBad{}: 341 None{} 342 case FieldsOk{m}: 343 Some{m} 344 345 def parse.headers(xs: List<&2, String>, acc: Fields) -> Maybe<&2, Map<&2, List<&2, String>>>: 346 match xs: 347 case Nil{}: 348 parse.headers.done(acc) 349 case Con{line, rest}: 350 parse.headers(rest, fields_put(acc, drop_cr(line))) 351 352 def parse_u32.digit(+c: U32) -> Bool: 353 Bool.and(U32.is_le(48, c), U32.is_le(c, 57)) 354 355 def parse_u32.add(acc: U32, c: U32) -> U32: 356 (acc * 10 + (c - 48 : U32) : U32) 357 358 def parse_u32.done(acc: U32, bad: Bool) -> Maybe<&2, U32>: 359 match bad: 360 case True{}: 361 None{} 362 case False{}: 363 Some{acc} 364 365 def parse_u32.go(s: String, acc: U32, bad: Bool) -> Maybe<&2, U32>: 366 match s: 367 case SNil{}: 368 parse_u32.done(acc, bad) 369 case SCon{Chr{+c}, t}: 370 parse_u32.go(t, parse_u32.add(acc, c), Bool.or(bad, Bool.not(parse_u32.digit(c)))) 371 372 def parse_u32(s: String) -> Maybe<&2, U32>: 373 match s: 374 case SNil{}: 375 None{} 376 case SCon{Chr{+c}, t}: 377 parse_u32.go(t, parse_u32.add(0, c), Bool.not(parse_u32.digit(c))) 378 379 # More: a valid prefix. Bad: cannot become a body. Body: a whole chunked body. 380 type Hold is Data: 381 HoldMore{} 382 HoldBad{} 383 HoldBody{b: String} 384 HoldUntil{b: String} 385 386 def is_hex(+c: U32) -> Bool: 387 Bool.or(parse_u32.digit(c), Bool.or(Bool.and(U32.is_le(97, c), U32.is_le(c, 102)), Bool.and(U32.is_le(65, c), U32.is_le(c, 70)))) 388 389 def hexv(+c: U32) -> U32: 390 Bool.pick(U32, parse_u32.digit(c), (c - 48 : U32), Bool.pick(U32, U32.is_le(c, 70), (c - 55 : U32), (c - 87 : U32))) 391 392 # A chunked body decoder that reads each byte once, so a body fed in pieces 393 # costs O(bytes) in total. Tr{j}: j bytes of the closing CRLF CRLF matched. 394 type Dc is Data: 395 DcSize{acc: U32, seen: Bool} 396 DcExt{n: U32} 397 DcSizeLf{n: U32} 398 DcData{k: U32} 399 DcDataCr{} 400 DcDataLf{} 401 DcTr{j: U32} 402 DcDone{} 403 DcBad{} 404 405 # st: where the decoder is. racc: the body so far, reversed. rest: bytes after a Done body. 406 type Dco is Data: 407 Dco{st: Dc, racc: String, rest: String} 408 409 def dc.start() -> Dc: 410 DcSize{0, False{}} 411 412 def dc.size.semi(+n: U32, semi: Bool) -> Dc: 413 match semi: 414 case True{}: 415 DcExt{n} 416 case False{}: 417 DcBad{} 418 419 def dc.size.cr(+n: U32, +c: U32, cr: Bool) -> Dc: 420 match cr: 421 case True{}: 422 DcSizeLf{n} 423 case False{}: 424 dc.size.semi(n, U32.is_eq(c, 59)) 425 426 def dc.size.end(+n: U32, +c: U32, seen: Bool) -> Dc: 427 match seen: 428 case False{}: 429 DcBad{} 430 case True{}: 431 dc.size.cr(n, c, U32.is_eq(c, 13)) 432 433 # A size that would overflow 32 bits ends the digits, and size.end then refuses it. 434 def dc.size(+acc: U32, seen: Bool, +c: U32, digit: Bool) -> Dc: 435 match digit: 436 case True{}: 437 DcSize{(acc * 16 + hexv(c) : U32), True{}} 438 case False{}: 439 dc.size.end(acc, c, seen) 440 441 # RFC 9112 §7.1.1: recipients ignore unknown chunk extensions; a bare LF is not CRLF. 442 def dc.ext.lf(+n: U32, lf: Bool) -> Dc: 443 match lf: 444 case True{}: 445 DcBad{} 446 case False{}: 447 DcExt{n} 448 449 def dc.ext(+n: U32, +c: U32, cr: Bool) -> Dc: 450 match cr: 451 case True{}: 452 DcSizeLf{n} 453 case False{}: 454 dc.ext.lf(n, U32.is_eq(c, 10)) 455 456 def dc.sized(+n: U32, zero: Bool) -> Dc: 457 match zero: 458 case True{}: 459 DcTr{2} 460 case False{}: 461 DcData{n} 462 463 def dc.size.lf(+n: U32, lf: Bool) -> Dc: 464 match lf: 465 case True{}: 466 dc.sized(n, U32.is_eq(n, 0)) 467 case False{}: 468 DcBad{} 469 470 # k data bytes are left and m of them were just read. 471 def dc.data(+k: U32, +m: U32) -> Dc: 472 Bool.pick(Dc, U32.is_le(k, m), DcDataCr{}, DcData{(k - m : U32)}) 473 474 def dc.pick(ok: Bool, next: Dc) -> Dc: 475 match ok: 476 case True{}: 477 next 478 case False{}: 479 DcBad{} 480 481 # ponytail: trailer fields are dropped unparsed (§7.1.2 allows discarding) 482 def dc.tr.hit(+j: U32) -> Dc: 483 Bool.pick(Dc, U32.is_eq(j, 3), DcDone{}, DcTr{(j + 1 : U32)}) 484 485 def dc.tr(+j: U32, +c: U32, hit: Bool) -> Dc: 486 match hit: 487 case True{}: 488 dc.tr.hit(j) 489 case False{}: 490 Bool.pick(Dc, U32.is_eq(c, 13), DcTr{1}, DcTr{0}) 491 492 def dc.tr.want(+j: U32) -> U32: 493 Bool.pick(U32, U32.is_eq(U32.mod(j, 2), 0), 13, 10) 494 495 def dc.byte(h: Char) -> U32: 496 Chr{c} = h 497 c 498 499 # One byte c. Done and Bad never look at it, and a data byte is not inspected. 500 def dc.step(st: Dc, +c: U32) -> Dc: 501 match st: 502 case DcBad{}: 503 DcBad{} 504 case DcSize{+acc, seen}: 505 dc.size(acc, seen, c, Bool.and(is_hex(c), U32.is_le(acc, 268435455))) 506 case DcExt{+n}: 507 dc.ext(n, c, U32.is_eq(c, 13)) 508 case DcSizeLf{+n}: 509 dc.size.lf(n, U32.is_eq(c, 10)) 510 case DcDataCr{}: 511 dc.pick(U32.is_eq(c, 13), DcDataLf{}) 512 case DcDataLf{}: 513 dc.pick(U32.is_eq(c, 10), dc.start()) 514 case DcTr{+j}: 515 dc.tr(j, c, U32.is_eq(c, dc.tr.want(j))) 516 case DcData{+k}: 517 dc.data(k, 1) 518 case DcDone{}: 519 DcDone{} 520 521 def dc.live(st: Dc) -> Bool: 522 match st: 523 case DcDone{}: 524 False{} 525 case DcBad{}: 526 False{} 527 case DcSize{a, b}: 528 True{} 529 case DcExt{n}: 530 True{} 531 case DcSizeLf{n}: 532 True{} 533 case DcData{k}: 534 True{} 535 case DcDataCr{}: 536 True{} 537 case DcDataLf{}: 538 True{} 539 case DcTr{j}: 540 True{} 541 542 def dc.feed(s: String, st: Dc, racc: String) -> Dco: 543 match s: 544 case SNil{}: 545 Dco{st, racc, ""} 546 case SCon{h, t}: 547 match st: 548 case DcBad{}: 549 Dco{DcBad{}, racc, ""} 550 case DcDone{}: 551 Dco{DcDone{}, racc, SCon{h, t}} 552 case DcData{+k}: 553 dc.feed(t, dc.data(k, 1), SCon{h, racc}) 554 case DcSize{+acc, seen}: 555 dc.feed(t, dc.step(DcSize{acc, seen}, dc.byte(h)), racc) 556 case DcExt{+n}: 557 dc.feed(t, dc.step(DcExt{n}, dc.byte(h)), racc) 558 case DcSizeLf{+n}: 559 dc.feed(t, dc.step(DcSizeLf{n}, dc.byte(h)), racc) 560 case DcDataCr{}: 561 dc.feed(t, dc.step(DcDataCr{}, dc.byte(h)), racc) 562 case DcDataLf{}: 563 dc.feed(t, dc.step(DcDataLf{}, dc.byte(h)), racc) 564 case DcTr{+j}: 565 dc.feed(t, dc.step(DcTr{j}, dc.byte(h)), racc) 566 567 def chunk.hold(o: Dco) -> Hold: 568 Dco{st, racc, rest} = o 569 match st: 570 case DcDone{}: 571 HoldBody{String.reverse(racc)} 572 case DcBad{}: 573 HoldBad{} 574 case DcSize{a, b}: 575 HoldMore{} 576 case DcExt{n}: 577 HoldMore{} 578 case DcSizeLf{n}: 579 HoldMore{} 580 case DcData{k}: 581 HoldMore{} 582 case DcDataCr{}: 583 HoldMore{} 584 case DcDataLf{}: 585 HoldMore{} 586 case DcTr{j}: 587 HoldMore{} 588 589 def chunk.live(s: String) -> Hold: 590 chunk.hold(dc.feed(s, dc.start(), "")) 591 592 def chunk.rest(o: Dco) -> Maybe<&2, String>: 593 Dco{st, racc, rest} = o 594 match st: 595 case DcDone{}: 596 Some{rest} 597 case DcBad{}: 598 None{} 599 case DcSize{a, b}: 600 None{} 601 case DcExt{n}: 602 None{} 603 case DcSizeLf{n}: 604 None{} 605 case DcData{k}: 606 None{} 607 case DcDataCr{}: 608 None{} 609 case DcDataLf{}: 610 None{} 611 case DcTr{j}: 612 None{} 613 614 def chunk.suffix(s: String) -> Maybe<&2, String>: 615 chunk.rest(dc.feed(s, dc.start(), "")) 616 617 def chunk.decode.hold(h: Hold) -> Maybe<&2, String>: 618 match h: 619 case HoldMore{}: 620 None{} 621 case HoldBad{}: 622 None{} 623 case HoldUntil{b}: 624 None{} 625 case HoldBody{b}: 626 Some{b} 627 628 def chunk.decode(s: String) -> Maybe<&2, String>: 629 chunk.decode.hold(chunk.live(s)) 630 631 def bytes.snd(r: Bytes.Bytes & Bytes.Bytes) -> Bytes.Bytes: 632 (a, b) = r 633 b 634 635 def found.at(+from: U32, m: Maybe<&2, U32>) -> Maybe<&2, U32>: 636 match m: 637 case None{}: 638 None{} 639 case Some{+j}: 640 Some{(from + j : U32)} 641 642 # A chunked body decoded from piece[i..]: a data run is sliced whole, and every other byte steps dc. 643 type Cs is Type: 644 Cs{piece: Bytes.Bytes, i: U32, st: Dc, parts: List<&1, Bytes.Bytes>} 645 646 # st: where the decoder stopped. parts: the data so far, newest first. rest: the bytes after a Done body. 647 type Ck is Type: 648 Ck{st: Dc, parts: List<&1, Bytes.Bytes>, rest: Bytes.Bytes} 649 650 def ck.next(+len: U32, +i: U32, +st: Dc, piece: Bytes.Bytes, parts: List<&1, Bytes.Bytes>) -> Bool & Cs: 651 (Bool.and(U32.is_lt(i, len), dc.live(st)), Cs{piece, i, st, parts}) 652 653 def ck.data(+len: U32, +i: U32, +k: U32, +m: U32, parts: List<&1, Bytes.Bytes>, r: Bytes.Bytes & Bytes.Bytes) -> Bool & Cs: 654 (piece, run) = r 655 ck.next(len, (i + m : U32), dc.data(k, m), piece, Con{run, parts}) 656 657 def ck.byte(+len: U32, +i: U32, st: Dc, parts: List<&1, Bytes.Bytes>, r: Bytes.Bytes & Maybe<&2, U32>) -> Bool & Cs: 658 (piece, m) = r 659 match m: 660 case None{}: 661 ck.next(len, i, DcBad{}, piece, parts) 662 case Some{+c}: 663 ck.next(len, (i + 1 : U32), dc.step(st, c), piece, parts) 664 665 def ck.step(+len: U32, cs: Cs) -> Bool & Cs: 666 Cs{piece, +i, st, parts} = cs 667 match st: 668 case DcData{+k}: 669 +m = U32.min(k, (len - i : U32)) 670 ck.data(len, i, k, m, parts, Bytes.slice(piece, i, m)) 671 case DcDone{}: 672 (False{}, Cs{piece, i, DcDone{}, parts}) 673 case DcBad{}: 674 (False{}, Cs{piece, i, DcBad{}, parts}) 675 case DcSize{+acc, seen}: 676 ck.byte(len, i, DcSize{acc, seen}, parts, Bytes.get(piece, i)) 677 case DcExt{+n}: 678 ck.byte(len, i, DcExt{n}, parts, Bytes.get(piece, i)) 679 case DcSizeLf{+n}: 680 ck.byte(len, i, DcSizeLf{n}, parts, Bytes.get(piece, i)) 681 case DcDataCr{}: 682 ck.byte(len, i, DcDataCr{}, parts, Bytes.get(piece, i)) 683 case DcDataLf{}: 684 ck.byte(len, i, DcDataLf{}, parts, Bytes.get(piece, i)) 685 case DcTr{+j}: 686 ck.byte(len, i, DcTr{j}, parts, Bytes.get(piece, i)) 687 688 def ck.rest(st: Dc, parts: List<&1, Bytes.Bytes>, r: Bytes.Bytes & Bytes.Bytes) -> Ck: 689 (piece, rest) = r 690 Ck{st, parts, rest} 691 692 def ck.end(+len: U32, cs: Cs) -> Ck: 693 Cs{piece, +i, st, parts} = cs 694 ck.rest(st, parts, Bytes.slice(piece, i, (len - i : U32))) 695 696 # Each step reads at least one byte, so len steps are enough. 697 def ck.feed(f: Nat, +len: U32, r: Bool & Cs) -> Ck: 698 match f: 699 case 0n: 700 (go, cs) = r 701 ck.end(len, cs) 702 case 1n+p: 703 (go, cs) = r 704 match go: 705 case False{}: 706 ck.end(len, cs) 707 case True{}: 708 ck.feed(p, len, ck.step(len, cs)) 709 710 def ck(piece: Bytes.Bytes, +st: Dc, parts: List<&1, Bytes.Bytes>) -> Ck: 711 Bytes.Bytes{+len, buf} = piece 712 ck.feed(U32.to_nat(len), len, ck.next(len, 0, st, Bytes.Bytes{len, buf}, parts)) 713 714 def parse.res.cl.have(+rest: String, +k: Nat, short: Bool) -> Hold: 715 match short: 716 case True{}: 717 HoldMore{} 718 case False{}: 719 HoldBody{String.take(rest, k)} 720 721 def parse.res.cl.k(+rest: String, +k: Nat) -> Hold: 722 parse.res.cl.have(rest, k, Nat.is_lt(String.length(rest), k)) 723 724 def parse.res.cl.n(rest: String, n: Maybe<&2, U32>) -> Hold: 725 match n: 726 case None{}: 727 HoldBad{} 728 case Some{k}: 729 parse.res.cl.k(rest, U32.to_nat(k)) 730 731 def parse.body.cl(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold: 732 match hascl: 733 case False{}: 734 HoldBody{""} 735 case True{}: 736 parse.res.cl.n(rest, parse_u32(header(h, "content-length"))) 737 738 # RFC 9112 §6.3 (4): a request coding other than chunked cannot be framed. 739 def parse.body.chunked.eq(rest: String, ok: Bool) -> Hold: 740 match ok: 741 case False{}: 742 HoldBad{} 743 case True{}: 744 chunk.live(rest) 745 746 def parse.body.chunked.val(te: String, rest: String) -> Hold: 747 parse.body.chunked.eq(rest, String.eq(String.to_lower(te), "chunked")) 748 749 def parse.body.chunked(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold: 750 match hascl: 751 case True{}: 752 HoldBad{} 753 case False{}: 754 parse.body.chunked.val(header.last(h, "transfer-encoding"), rest) 755 756 def parse.body.te(+h: Map<&2, List<&2, String>>, rest: String, te: Bool) -> Hold: 757 match te: 758 case True{}: 759 parse.body.chunked(h, rest, has_header(h, "content-length")) 760 case False{}: 761 parse.body.cl(h, rest, has_header(h, "content-length")) 762 763 def parse.body(+h: Map<&2, List<&2, String>>, rest: String) -> Hold: 764 parse.body.te(h, rest, has_header(h, "transfer-encoding")) 765 766 # Bad: never a request. More: the head is not whole; read on. Head: the head is whole 767 # and valid, and the body is still coming (req.body is empty). Req: a whole request. 768 type Got is Type: 769 GotBad{} 770 GotMore{} 771 GotHead{req: Req} 772 GotReq{req: Req} 773 774 def parse.finish(method: String, path: String, h: Map<&2, List<&2, String>>, body: Hold) -> Got: 775 match body: 776 case HoldBad{}: 777 GotBad{} 778 case HoldMore{}: 779 GotHead{Req{method, path, h, Bytes.new(0)}} 780 case HoldUntil{b}: 781 GotBad{} 782 case HoldBody{b}: 783 GotReq{Req{method, path, h, Bytes.from_string(b)}} 784 785 def parse.host(method: String, path: String, +h: Map<&2, List<&2, String>>, rest: String, has: Bool) -> Got: 786 match has: 787 case False{}: 788 GotBad{} 789 case True{}: 790 parse.finish(method, path, h, parse.body(h, rest)) 791 792 # RFC 9112 §3.2: only HTTP/1.1 requires Host. 793 def parse.fields(method: String, path: String, rest: String, v11: Bool, fs: Maybe<&2, Map<&2, List<&2, String>>>) -> Got: 794 match fs: 795 case None{}: 796 GotBad{} 797 case Some{+h}: 798 parse.host(method, path, h, rest, Bool.or(Bool.not(v11), has_header(h, "host"))) 799 800 def target_ok(+p: String) -> Bool: 801 Bool.or(String.starts_with(p, "/"), Bool.or(String.eq(p, "*"), String.starts_with(p, "http://"))) 802 803 def parse.target(method: String, +path: String, headers: List<&2, String>, rest: String, v11: Bool, ok: Bool) -> Got: 804 match ok: 805 case False{}: 806 GotBad{} 807 case True{}: 808 parse.fields(method, path, rest, v11, parse.headers(headers, FieldsOk{empty()})) 809 810 def parse.ver(method: String, +path: String, +v: String, headers: List<&2, String>, rest: String, ok: Bool) -> Got: 811 match ok: 812 case False{}: 813 GotBad{} 814 case True{}: 815 parse.target(method, path, headers, rest, String.eq(v, "HTTP/1.1"), target_ok(path)) 816 817 def parse.empty_method(method: String, path: String, +v: String, headers: List<&2, String>, rest: String, empty: Bool) -> Got: 818 match empty: 819 case True{}: 820 GotBad{} 821 case False{}: 822 parse.ver(method, path, v, headers, rest, Bool.or(String.eq(v, "HTTP/1.1"), String.eq(v, "HTTP/1.0"))) 823 824 def parse.start3(mpv: String & String & String, headers: List<&2, String>, rest: String) -> Got: 825 (+method, path, v) = mpv 826 parse.empty_method(method, path, v, headers, rest, String.is_empty(method)) 827 828 def parse.lines(xs: List<&2, String>, rest: String) -> Got: 829 match xs: 830 case Nil{}: 831 GotBad{} 832 case Con{line, headers}: 833 parse.start3(start_line(line), headers, rest) 834 835 836 def parse.head(+head: String, rest: String, whole: Bool) -> Got: 837 match whole: 838 case False{}: 839 GotMore{} 840 case True{}: 841 parse.lines(String.lines(head), rest) 842 843 def parse.of(hb: String & String & Bool) -> Got: 844 (head, rest, whole) = hb 845 parse.head(head, rest, whole) 846 847 # serve reads until this is not GotMore. 848 def parse.got(+raw: String) -> Got: 849 parse.of(split_at_blank.flag.go(raw, raw, 0n, 0, False{})) 850 851 852 def parse.req(g: Got) -> Maybe<&1, Req>: 853 match g: 854 case GotReq{req}: 855 Some{req} 856 case GotBad{}: 857 None{} 858 case GotMore{}: 859 None{} 860 case GotHead{req}: 861 None{} 862 863 def parse(raw: String) -> Maybe<&1, Req>: 864 parse.req(parse.got(raw)) 865 866 def parse.res.cl(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold: 867 match hascl: 868 case False{}: 869 HoldBody{rest} 870 case True{}: 871 parse.res.cl.n(rest, parse_u32(header(h, "content-length"))) 872 873 def parse.res.chunked.eq(rest: String, ok: Bool) -> Hold: 874 match ok: 875 case False{}: 876 HoldUntil{rest} 877 case True{}: 878 chunk.live(rest) 879 880 def parse.res.chunked.val(te: String, rest: String) -> Hold: 881 parse.res.chunked.eq(rest, String.eq(String.to_lower(te), "chunked")) 882 883 def parse.res.chunked(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold: 884 match hascl: 885 case True{}: 886 HoldBad{} 887 case False{}: 888 parse.res.chunked.val(header.last(h, "transfer-encoding"), rest) 889 890 def parse.res.te(+h: Map<&2, List<&2, String>>, rest: String, te: Bool) -> Hold: 891 match te: 892 case True{}: 893 parse.res.chunked(h, rest, has_header(h, "content-length")) 894 case False{}: 895 parse.res.cl(h, rest, has_header(h, "content-length")) 896 897 def parse.res.body(+h: Map<&2, List<&2, String>>, rest: String) -> Hold: 898 parse.res.te(h, rest, has_header(h, "transfer-encoding")) 899 900 # Bad: never a message. More: a valid prefix; read on. Done: a whole response. 901 type Frame is Type: 902 FrameBad{} 903 FrameMore{} 904 FrameDone{res: Res} 905 906 # RFC 9112 §8: a message cut short by close is incomplete, not valid. 907 def frame.cut(closed: Bool) -> Frame: 908 match closed: 909 case True{}: 910 FrameBad{} 911 case False{}: 912 FrameMore{} 913 914 def frame.wait(res: Res, wait: Bool) -> Frame: 915 match wait: 916 case True{}: 917 FrameMore{} 918 case False{}: 919 FrameDone{res} 920 921 def frame.done(status: U32, h: Map<&2, List<&2, String>>, body: Hold, +closed: Bool, open: Bool) -> Frame: 922 match body: 923 case HoldBad{}: 924 FrameBad{} 925 case HoldMore{}: 926 frame.cut(closed) 927 case HoldUntil{b}: 928 frame.wait(Res{status, h, Bytes.from_string(b)}, Bool.not(closed)) 929 case HoldBody{b}: 930 frame.wait(Res{status, h, Bytes.from_string(b)}, Bool.and(open, Bool.not(closed))) 931 932 # RFC 9112 §6.3 (8): no TE and no CL ⇒ the body runs until the server closes. 933 def frame.open(+h: Map<&2, List<&2, String>>) -> Bool: 934 Bool.not(Bool.or(has_header(h, "transfer-encoding"), has_header(h, "content-length"))) 935 936 # RFC 9112 §6.3 (1): HEAD, 1xx, 204 and 304 responses end at the blank line. 937 def frame.nobody(+status: U32, head: Bool) -> Bool: 938 Bool.or(head, Bool.or(U32.is_lt(status, 200), Bool.or(U32.is_eq(status, 204), U32.is_eq(status, 304)))) 939 940 # 101 is the final response. Other 1xx are interim; a final response follows. 941 def frame.interim(+status: U32) -> Bool: 942 Bool.and(U32.is_lt(status, 200), Bool.not(U32.is_eq(status, 101))) 943 944 def frame.status(status: U32, +h: Map<&2, List<&2, String>>, rest: String, closed: Bool, nobody: Bool) -> Frame: 945 match nobody: 946 case True{}: 947 FrameDone{Res{status, h, Bytes.new(0)}} 948 case False{}: 949 frame.done(status, h, parse.res.body(h, rest), closed, frame.open(h)) 950 951 def parse.res.fields(+status: U32, rest: String, fs: Maybe<&2, Map<&2, List<&2, String>>>, closed: Bool, head: Bool) -> Frame: 952 match fs: 953 case None{}: 954 FrameBad{} 955 case Some{h}: 956 frame.status(status, h, rest, closed, frame.nobody(status, head)) 957 958 def parse.res.num(code: Maybe<&2, U32>, headers: List<&2, String>, rest: String, closed: Bool, head: Bool) -> Frame: 959 match code: 960 case None{}: 961 FrameBad{} 962 case Some{n}: 963 parse.res.fields(n, rest, parse.headers(headers, FieldsOk{empty()}), closed, head) 964 965 def parse.res.ver(code: String, headers: List<&2, String>, rest: String, closed: Bool, head: Bool, ok: Bool) -> Frame: 966 match ok: 967 case False{}: 968 FrameBad{} 969 case True{}: 970 parse.res.num(parse_u32(code), headers, rest, closed, head) 971 972 def parse.res.start3(mpv: String & String & String, headers: List<&2, String>, rest: String, closed: Bool, head: Bool) -> Frame: 973 (+ver, code, reason) = mpv 974 parse.res.ver(code, headers, rest, closed, head, Bool.or(String.eq(ver, "HTTP/1.1"), String.eq(ver, "HTTP/1.0"))) 975 976 def parse.res.lines(xs: List<&2, String>, rest: String, closed: Bool, head: Bool) -> Frame: 977 match xs: 978 case Nil{}: 979 FrameBad{} 980 case Con{line, headers}: 981 parse.res.start3(start_line(line), headers, rest, closed, head) 982 983 def parse.res.blank(hd: String, rest: String, +closed: Bool, head: Bool, ok: Bool) -> Frame: 984 match ok: 985 case False{}: 986 frame.cut(closed) 987 case True{}: 988 parse.res.lines(String.lines(hd), rest, closed, head) 989 990 def parse.res.of(hb: String & String, closed: Bool, head: Bool) -> Frame: 991 (+hd, rest) = hb 992 parse.res.blank(hd, rest, closed, head, blank_end(hd)) 993 994 type Lead is Data: 995 LeadCut{} 996 LeadFinal{} 997 LeadSkip{+rest: String} 998 999 def frame.lead.if(rest: String, interim: Bool) -> Lead: 1000 match interim: 1001 case True{}: 1002 LeadSkip{rest} 1003 case False{}: 1004 LeadFinal{} 1005 1006 def frame.lead.num(n: Maybe<&2, U32>, rest: String) -> Lead: 1007 match n: 1008 case None{}: 1009 LeadFinal{} 1010 case Some{s}: 1011 frame.lead.if(rest, frame.interim(s)) 1012 1013 def frame.lead.code(mpv: String & String & String, rest: String) -> Lead: 1014 (ver, code, reason) = mpv 1015 frame.lead.num(parse_u32(code), rest) 1016 1017 def frame.lead.lines(xs: List<&2, String>, rest: String) -> Lead: 1018 match xs: 1019 case Nil{}: 1020 LeadFinal{} 1021 case Con{line, headers}: 1022 frame.lead.code(start_line(line), rest) 1023 1024 def frame.lead.blank(+hd: String, rest: String, ok: Bool) -> Lead: 1025 match ok: 1026 case False{}: 1027 LeadCut{} 1028 case True{}: 1029 frame.lead.lines(String.lines(hd), rest) 1030 1031 def frame.lead(hb: String & String) -> Lead: 1032 (+hd, rest) = hb 1033 frame.lead.blank(hd, rest, blank_end(hd)) 1034 1035 def frame.at.cont(+raw: String, lead: Lead) -> String: 1036 match lead: 1037 case LeadCut{}: 1038 raw 1039 case LeadFinal{}: 1040 raw 1041 case LeadSkip{rest}: 1042 rest 1043 1044 def frame.at.zero.go(+raw: String, closed: Bool, head: Bool, lead: Lead) -> Frame: 1045 match lead: 1046 case LeadCut{}: 1047 frame.cut(closed) 1048 case LeadFinal{}: 1049 parse.res.of(split_at_blank(raw), closed, head) 1050 case LeadSkip{rest}: 1051 FrameBad{} 1052 1053 def frame.at.zero.of(+raw: String, closed: Bool, head: Bool) -> Frame: 1054 frame.at.zero.go(raw, closed, head, frame.lead(split_at_blank(raw))) 1055 1056 def frame.at.pos.do(f: Nat, +raw: String, closed: Bool, head: Bool, hb: String & String, lead: Lead) -> Frame: 1057 match f: 1058 case 0n: 1059 match lead: 1060 case LeadCut{}: 1061 frame.cut(closed) 1062 case LeadFinal{}: 1063 parse.res.of(hb, closed, head) 1064 case LeadSkip{+rest}: 1065 frame.at.zero.of(rest, closed, head) 1066 case 1n+g: 1067 match lead: 1068 case LeadCut{}: 1069 frame.cut(closed) 1070 case LeadFinal{}: 1071 parse.res.of(hb, closed, head) 1072 case LeadSkip{+rest}: 1073 frame.at.pos.do(g, rest, closed, head, split_at_blank(rest), frame.lead(split_at_blank(rest))) 1074 1075 def frame.at.zero(+raw: String, closed: Bool, head: Bool) -> Frame: 1076 frame.at.zero.of(raw, closed, head) 1077 1078 # ponytail: 8 interim responses; raise the fuel if a server sends more 1079 def frame.at(fuel: Nat, +raw: String, closed: Bool, head: Bool) -> Frame: 1080 match fuel: 1081 case 0n: 1082 frame.at.zero(raw, closed, head) 1083 case 1n+f: 1084 frame.at.pos.do(f, raw, closed, head, split_at_blank(raw), frame.lead(split_at_blank(raw))) 1085 1086 # frame(bytes so far, server closed?, request was HEAD?) 1087 def frame(raw: String, closed: Bool, head: Bool) -> Frame: 1088 frame.at(8n, raw, closed, head) 1089 1090 def frame.res(f: Frame) -> Maybe<&1, Res>: 1091 match f: 1092 case FrameDone{res}: 1093 Some{res} 1094 case FrameBad{}: 1095 None{} 1096 case FrameMore{}: 1097 None{} 1098 1099 def parse_res(raw: String) -> Maybe<&1, Res>: 1100 frame.res(frame(raw, True{}, False{})) 1101 1102 def res_fields.res(res: Res, k: String) -> List<&2, String>: 1103 Res{status, headers, body} = res 1104 fields(headers, k) 1105 1106 def res_fields(m: Maybe<&1, Res>, k: String) -> List<&2, String>: 1107 match m: 1108 case None{}: 1109 Nil{} 1110 case Some{res}: 1111 res_fields.res(res, k) 1112 1113 # The response body as it arrives. Len: bytes left. Chunk: the decoder. Close: until the 1114 # server closes. Done: the body is whole. Bad: it cannot become a body. 1115 type Rb is Data: 1116 RbLen{left: U32} 1117 RbChunk{st: Dc} 1118 RbClose{} 1119 RbDone{} 1120 RbBad{} 1121 1122 def rb.cl(n: Maybe<&2, U32>) -> Maybe<&2, Rb>: 1123 match n: 1124 case None{}: 1125 None{} 1126 case Some{+k}: 1127 Some{Bool.pick(Rb, U32.is_zero(k), RbDone{}, RbLen{k})} 1128 1129 def rb.te(+h: Map<&2, List<&2, String>>, cl: Bool) -> Maybe<&2, Rb>: 1130 match cl: 1131 case True{}: 1132 None{} 1133 case False{}: 1134 Some{Bool.pick(Rb, String.eq(String.to_lower(header.last(h, "transfer-encoding")), "chunked"), RbChunk{dc.start()}, RbClose{})} 1135 1136 def rb.len(+h: Map<&2, List<&2, String>>, te: Bool) -> Maybe<&2, Rb>: 1137 match te: 1138 case True{}: 1139 rb.te(h, has_header(h, "content-length")) 1140 case False{}: 1141 Bool.pick(Maybe<&2, Rb>, has_header(h, "content-length"), rb.cl(parse_u32(header(h, "content-length"))), Some{RbClose{}}) 1142 1143 # RFC 9112 §6.3: how the body of this response ends. None: it cannot be framed. 1144 def rb.of(+h: Map<&2, List<&2, String>>, nobody: Bool) -> Maybe<&2, Rb>: 1145 match nobody: 1146 case True{}: 1147 Some{RbDone{}} 1148 case False{}: 1149 rb.len(h, has_header(h, "transfer-encoding")) 1150 1151 # rb: how the body goes on. parts: body bytes so far, newest first. rest: bytes past a Done body. 1152 type Bf is Type: 1153 Bf{rb: Rb, parts: List<&1, Bytes.Bytes>, rest: Bytes.Bytes} 1154 1155 def rb.len.cut(parts: List<&1, Bytes.Bytes>, +len: U32, +left: U32, r: Bytes.Bytes & Bytes.Bytes) -> Bf: 1156 (piece, data) = r 1157 Bf{RbDone{}, Con{data, parts}, bytes.snd(Bytes.slice(piece, left, (len - left : U32)))} 1158 1159 def rb.len.have(short: Bool, +left: U32, parts: List<&1, Bytes.Bytes>, +len: U32, piece: Bytes.Bytes) -> Bf: 1160 match short: 1161 case True{}: 1162 Bf{RbLen{(left - len : U32)}, Con{piece, parts}, Bytes.new(0)} 1163 case False{}: 1164 rb.len.cut(parts, len, left, Bytes.slice(piece, 0, left)) 1165 1166 def rb.chunk(c: Ck) -> Bf: 1167 Ck{st, parts, rest} = c 1168 match st: 1169 case DcDone{}: 1170 Bf{RbDone{}, parts, rest} 1171 case DcBad{}: 1172 Bf{RbBad{}, parts, rest} 1173 case DcSize{a, b}: 1174 Bf{RbChunk{DcSize{a, b}}, parts, rest} 1175 case DcExt{x}: 1176 Bf{RbChunk{DcExt{x}}, parts, rest} 1177 case DcSizeLf{x}: 1178 Bf{RbChunk{DcSizeLf{x}}, parts, rest} 1179 case DcData{k}: 1180 Bf{RbChunk{DcData{k}}, parts, rest} 1181 case DcDataCr{}: 1182 Bf{RbChunk{DcDataCr{}}, parts, rest} 1183 case DcDataLf{}: 1184 Bf{RbChunk{DcDataLf{}}, parts, rest} 1185 case DcTr{j}: 1186 Bf{RbChunk{DcTr{j}}, parts, rest} 1187 1188 # The body bytes in piece, added to parts. 1189 def rb.feed(rb: Rb, parts: List<&1, Bytes.Bytes>, piece: Bytes.Bytes) -> Bf: 1190 match rb: 1191 case RbLen{+left}: 1192 Bytes.Bytes{+len, buf} = piece 1193 rb.len.have(U32.is_lt(len, left), left, parts, len, Bytes.Bytes{len, buf}) 1194 case RbChunk{st}: 1195 rb.chunk(ck(piece, st, parts)) 1196 case RbClose{}: 1197 Bf{RbClose{}, Con{piece, parts}, Bytes.new(0)} 1198 case RbDone{}: 1199 Bf{RbDone{}, parts, piece} 1200 case RbBad{}: 1201 Bf{RbBad{}, parts, piece} 1202 1203 # RFC 9112 §8: only a close-delimited body may end at close. 1204 def rb.eof(rb: Rb) -> Bool: 1205 match rb: 1206 case RbClose{}: 1207 True{} 1208 case RbDone{}: 1209 True{} 1210 case RbLen{n}: 1211 False{} 1212 case RbChunk{st}: 1213 False{} 1214 case RbBad{}: 1215 False{} 1216 1217 def rb.body(parts: List<&1, Bytes.Bytes>) -> Bytes.Bytes: 1218 Bytes.concat(List.reverse(&1, Bytes.Bytes, parts)) 1219 1220 # The head scan. Seek: look for the blank line from byte from; k interim heads are behind. 1221 # Wait: no blank line yet. Res: the final head, how its body ends, and the bytes after it. 1222 type Hs is Type: 1223 HsSeek{buf: Bytes.Bytes, from: U32, k: U32} 1224 HsWait{buf: Bytes.Bytes, k: U32} 1225 HsBad{} 1226 HsRes{res: Res, rb: Rb, rest: Bytes.Bytes} 1227 1228 def hs.final(+status: U32, +h: Map<&2, List<&2, String>>, rest: Bytes.Bytes, m: Maybe<&2, Rb>) -> Hs: 1229 match m: 1230 case None{}: 1231 HsBad{} 1232 case Some{rb}: 1233 HsRes{Res{status, h, Bytes.new(0)}, rb, rest} 1234 1235 def hs.skip(more: Bool, +k: U32, rest: Bytes.Bytes) -> Hs: 1236 match more: 1237 case True{}: 1238 HsSeek{rest, 0, (k + 1 : U32)} 1239 case False{}: 1240 HsBad{} 1241 1242 def hs.pick(interim: Bool, +head: Bool, +k: U32, +status: U32, +h: Map<&2, List<&2, String>>, rest: Bytes.Bytes) -> Hs: 1243 match interim: 1244 case True{}: 1245 hs.skip(U32.is_lt(k, 8), k, rest) 1246 case False{}: 1247 hs.final(status, h, rest, rb.of(h, frame.nobody(status, head))) 1248 1249 def hs.res(+head: Bool, +k: U32, rest: Bytes.Bytes, res: Res) -> Hs: 1250 Res{+status, +h, body} = res 1251 hs.pick(frame.interim(status), head, k, status, h, rest) 1252 1253 # The head alone, framed as a HEAD response so it stops at the blank line. 1254 def hs.parsed(+head: Bool, +k: U32, rest: Bytes.Bytes, f: Frame) -> Hs: 1255 match f: 1256 case FrameDone{res}: 1257 hs.res(head, k, rest, res) 1258 case FrameBad{}: 1259 HsBad{} 1260 case FrameMore{}: 1261 HsBad{} 1262 1263 def hs.split(+head: Bool, +k: U32, +hlen: U32, +len: U32, r: Bytes.Bytes & Bytes.Bytes) -> Hs: 1264 (buf, hb) = r 1265 hs.parsed(head, k, bytes.snd(Bytes.slice(buf, hlen, (len - hlen : U32))), parse.res.of(split_at_blank(Bytes.to_string(hb)), False{}, True{})) 1266 1267 def hs.hit(+head: Bool, +k: U32, +len: U32, buf: Bytes.Bytes, at: Maybe<&2, U32>) -> Hs: 1268 match at: 1269 case None{}: 1270 HsWait{buf, k} 1271 case Some{+i}: 1272 +hlen = (i + 4 : U32) 1273 hs.split(head, k, hlen, len, Bytes.slice(buf, 0, hlen)) 1274 1275 def hs.seek.of(+head: Bool, +k: U32, +len: U32, +from: U32, buf: Bytes.Bytes, r: Bytes.Bytes & Maybe<&2, U32>) -> Hs: 1276 (tail, m) = r 1277 hs.hit(head, k, len, buf, found.at(from, m)) 1278 1279 def hs.seek(+head: Bool, +k: U32, +len: U32, +from: U32, r: Bytes.Bytes & Bytes.Bytes) -> Hs: 1280 (buf, tail) = r 1281 hs.seek.of(head, k, len, from, buf, Bytes.find(tail, "\r\n\r\n")) 1282 1283 def hs.step(+head: Bool, st: Hs) -> Hs: 1284 match st: 1285 case HsSeek{buf, +from, +k}: 1286 Bytes.Bytes{+len, b} = buf 1287 hs.seek(head, k, len, from, Bytes.slice(Bytes.Bytes{len, b}, from, (len - from : U32))) 1288 case HsWait{buf, k}: 1289 HsWait{buf, k} 1290 case HsBad{}: 1291 HsBad{} 1292 case HsRes{res, rb, rest}: 1293 HsRes{res, rb, rest} 1294 1295 # Each step ends the scan or passes one interim head, and at most 8 interim heads pass. 1296 def hs.scan(fuel: Nat, +head: Bool, st: Hs) -> Hs: 1297 match fuel: 1298 case 0n: 1299 match st: 1300 case HsSeek{buf, from, k}: 1301 HsBad{} 1302 case HsWait{buf, k}: 1303 HsWait{buf, k} 1304 case HsBad{}: 1305 HsBad{} 1306 case HsRes{res, rb, rest}: 1307 HsRes{res, rb, rest} 1308 case 1n+f: 1309 hs.scan(f, head, hs.step(head, st)) 1310 1311 # piece joins buf; only the new bytes, and the 3 before them, can finish the blank line. 1312 def hs.more(+head: Bool, +k: U32, buf: Bytes.Bytes, piece: Bytes.Bytes) -> Hs: 1313 Bytes.Bytes{+ol, ob} = buf 1314 hs.scan(10n, head, HsSeek{Bytes.append(Bytes.Bytes{ol, ob}, piece), Bool.pick(U32, U32.is_lt(ol, 3), 0, (ol - 3 : U32)), k}) 1315 1316 # Untrusted servers must not grow the buffer without bound. 1317 def fetch.max() -> Nat: 1318 U32.to_nat(16777216) 1319 1320 # A response read in pieces. Head: the head is not whole yet. Body: it is, and parts is the body so far. 1321 # Len: a Content-Length body in one buffer of that size, with left bytes still to come. 1322 type Rv is Type: 1323 RvHead{buf: Bytes.Bytes, k: U32} 1324 RvBody{res: Res, rb: Rb, parts: List<&1, Bytes.Bytes>} 1325 RvLen{res: Res, left: U32, body: Bytes.Bytes} 1326 1327 # More: read on. Done: the response, and the bytes after it when the body is self-delimited. 1328 type Rt is Type: 1329 RtMore{rv: Rv} 1330 RtBad{} 1331 RtDone{res: Res, rest: Maybe<&1, Bytes.Bytes>} 1332 1333 def rv.start() -> Rv: 1334 RvHead{Bytes.new(0), 0} 1335 1336 def rv.whole(res: Res, b: Bytes.Bytes, rest: Maybe<&1, Bytes.Bytes>) -> Rt: 1337 Res{status, headers, body} = res 1338 RtDone{Res{status, headers, b}, rest} 1339 1340 def rv.done(res: Res, parts: List<&1, Bytes.Bytes>, rest: Maybe<&1, Bytes.Bytes>) -> Rt: 1341 rv.whole(res, rb.body(parts), rest) 1342 1343 def rl.put.of(+total: U32, +len: U32, r: Array & Array) -> Bytes.Bytes & Bytes.Bytes: 1344 (src, dst) = r 1345 (Bytes.Bytes{total, dst}, Bytes.Bytes{len, src}) 1346 1347 # The first m bytes of piece, written into body after the total - left bytes already there. 1348 def rl.put(+m: U32, +left: U32, body: Bytes.Bytes, piece: Bytes.Bytes) -> Bytes.Bytes & Bytes.Bytes: 1349 Bytes.Bytes{+total, dst} = body 1350 Bytes.Bytes{+len, src} = piece 1351 rl.put.of(total, len, Bytes.copy(m, src, dst, 0, (total - left : U32))) 1352 1353 def rl.fed(short: Bool, res: Res, +left: U32, +len: U32, r: Bytes.Bytes & Bytes.Bytes) -> Rt: 1354 match short: 1355 case True{}: 1356 (body, piece) = r 1357 RtMore{RvLen{res, (left - len : U32), body}} 1358 case False{}: 1359 (body, piece) = r 1360 rv.whole(res, body, Some{bytes.snd(Bytes.slice(piece, left, (len - left : U32)))}) 1361 1362 def rl.feed(res: Res, +left: U32, body: Bytes.Bytes, piece: Bytes.Bytes) -> Rt: 1363 Bytes.Bytes{+len, buf} = piece 1364 rl.fed(U32.is_lt(len, left), res, left, len, rl.put(U32.min(len, left), left, body, Bytes.Bytes{len, buf})) 1365 1366 def rv.fed(res: Res, bf: Bf) -> Rt: 1367 Bf{rb, parts, rest} = bf 1368 match rb: 1369 case RbDone{}: 1370 rv.done(res, parts, Some{rest}) 1371 case RbBad{}: 1372 RtBad{} 1373 case RbLen{n}: 1374 RtMore{RvBody{res, RbLen{n}, parts}} 1375 case RbChunk{st}: 1376 RtMore{RvBody{res, RbChunk{st}, parts}} 1377 case RbClose{}: 1378 RtMore{RvBody{res, RbClose{}, parts}} 1379 1380 # Some{left}: a Content-Length body small enough to allocate up front. Larger ones fail at the fetch cap. 1381 def rb.buf(rb: Rb) -> Maybe<&2, U32>: 1382 match rb: 1383 case RbLen{+left}: 1384 Bool.pick(Maybe<&2, U32>, Nat.is_lt(fetch.max(), U32.to_nat(left)), None{}, Some{left}) 1385 case RbChunk{st}: 1386 None{} 1387 case RbClose{}: 1388 None{} 1389 case RbDone{}: 1390 None{} 1391 case RbBad{}: 1392 None{} 1393 1394 def rv.body(m: Maybe<&2, U32>, res: Res, rb: Rb, rest: Bytes.Bytes) -> Rt: 1395 match m: 1396 case None{}: 1397 rv.fed(res, rb.feed(rb, Nil{}, rest)) 1398 case Some{+left}: 1399 rl.feed(res, left, Bytes.new(left), rest) 1400 1401 def rv.head(hs: Hs) -> Rt: 1402 match hs: 1403 case HsWait{buf, k}: 1404 RtMore{RvHead{buf, k}} 1405 case HsBad{}: 1406 RtBad{} 1407 case HsSeek{buf, from, k}: 1408 RtBad{} 1409 case HsRes{res, +rb, rest}: 1410 rv.body(rb.buf(rb), res, rb, rest) 1411 1412 def rv.eof(res: Res, parts: List<&1, Bytes.Bytes>, ok: Bool) -> Rt: 1413 match ok: 1414 case True{}: 1415 rv.done(res, parts, None{}) 1416 case False{}: 1417 RtBad{} 1418 1419 # One read: piece, or the server closed (RFC 9112 §8: a message cut short is not a message). 1420 def rv.step(closed: Bool, +head: Bool, rv: Rv, piece: Bytes.Bytes) -> Rt: 1421 match closed: 1422 case True{}: 1423 match rv: 1424 case RvHead{buf, k}: 1425 RtBad{} 1426 case RvBody{res, rb, parts}: 1427 rv.eof(res, parts, rb.eof(rb)) 1428 case RvLen{res, left, body}: 1429 RtBad{} 1430 case False{}: 1431 match rv: 1432 case RvHead{buf, +k}: 1433 rv.head(hs.more(head, k, buf, piece)) 1434 case RvBody{res, rb, parts}: 1435 rv.fed(res, rb.feed(rb, parts, piece)) 1436 case RvLen{res, +left, body}: 1437 rl.feed(res, left, body, piece) 1438 1439 def rv.end(closed: Bool, +head: Bool, t: Rt) -> Rt: 1440 match closed: 1441 case False{}: 1442 t 1443 case True{}: 1444 match t: 1445 case RtMore{rv}: 1446 rv.step(True{}, head, rv, Bytes.new(0)) 1447 case RtBad{}: 1448 RtBad{} 1449 case RtDone{res, rest}: 1450 RtDone{res, rest} 1451 1452 def rv.go(xs: List<&2, String>, closed: Bool, +head: Bool, t: Rt) -> Rt: 1453 match xs: 1454 case Nil{}: 1455 rv.end(closed, head, t) 1456 case Con{+x, rest}: 1457 match t: 1458 case RtMore{rv}: 1459 rv.go(rest, closed, head, rv.step(False{}, head, rv, Bytes.from_string(x))) 1460 case RtBad{}: 1461 RtBad{} 1462 case RtDone{res, r}: 1463 RtDone{res, r} 1464 1465 def rt.frame(t: Rt) -> Frame: 1466 match t: 1467 case RtMore{rv}: 1468 FrameMore{} 1469 case RtBad{}: 1470 FrameBad{} 1471 case RtDone{res, rest}: 1472 FrameDone{res} 1473 1474 # What the read loop makes of these pieces, then a close when closed is set. 1475 def res.frame(xs: List<&2, String>, closed: Bool, +head: Bool) -> Frame: 1476 rt.frame(rv.go(xs, closed, head, RtMore{rv.start()})) 1477 1478 def rt.rest(t: Rt) -> Maybe<&1, Bytes.Bytes>: 1479 match t: 1480 case RtMore{rv}: 1481 None{} 1482 case RtBad{}: 1483 None{} 1484 case RtDone{res, rest}: 1485 rest 1486 1487 def after.of(m: Maybe<&1, Bytes.Bytes>) -> Maybe<&2, String>: 1488 match m: 1489 case None{}: 1490 None{} 1491 case Some{b}: 1492 Some{Bytes.to_string(b)} 1493 1494 # Bytes after a complete self-delimited message. None if it is not one yet. 1495 def after(+raw: String, +head: Bool) -> Maybe<&2, String>: 1496 after.of(rt.rest(rv.go([raw], False{}, head, RtMore{rv.start()}))) 1497 1498 def req.v11.ver(mpv: String & String & String) -> Bool: 1499 (+method, path, v) = mpv 1500 String.eq(v, "HTTP/1.1") 1501 1502 def req.v11.line(+line: String) -> Bool: 1503 req.v11.ver(start_line(line)) 1504 1505 def req.v11.lines(xs: List<&2, String>) -> Bool: 1506 match xs: 1507 case Nil{}: 1508 False{} 1509 case Con{line, headers}: 1510 req.v11.line(line) 1511 1512 def req.v11.pick(+head: String, ok: Bool) -> Bool: 1513 match ok: 1514 case False{}: 1515 False{} 1516 case True{}: 1517 req.v11.lines(String.lines(head)) 1518 1519 def req.v11.of(hb: String & String) -> Bool: 1520 (+head, rest) = hb 1521 req.v11.pick(head, blank_end(head)) 1522 1523 def req.v11(+raw: String) -> Bool: 1524 req.v11.of(split_at_blank(raw)) 1525 1526 def req.conn_close(+headers: Map<&2, List<&2, String>>) -> Bool: 1527 String.eq(String.to_lower(header(headers, "connection")), "close") 1528 1529 def res.conn_close(+headers: Map<&2, List<&2, String>>) -> Bool: 1530 String.eq(String.to_lower(header(headers, "connection")), "close") 1531 1532 def serve.fail_close(+status: U32) -> Bool: 1533 Bool.or(U32.is_eq(status, 400), Bool.or(U32.is_eq(status, 413), U32.is_eq(status, 431))) 1534 1535 def serve.keep.ok(fail: Bool, res_close: Bool) -> Bool: 1536 match fail: 1537 case True{}: 1538 False{} 1539 case False{}: 1540 Bool.not(res_close) 1541 1542 def serve.keep.v11(v11: Bool, +req_close: Bool, +status: U32, res_close: Bool) -> Bool: 1543 match v11: 1544 case False{}: 1545 False{} 1546 case True{}: 1547 match req_close: 1548 case True{}: 1549 False{} 1550 case False{}: 1551 serve.keep.ok(serve.fail_close(status), res_close) 1552 1553 def serve.keep(+headers: Map<&2, List<&2, String>>, v11: Bool, +status: U32, +res: Map<&2, List<&2, String>>) -> Bool: 1554 serve.keep.v11(v11, req.conn_close(headers), status, res.conn_close(res)) 1555 1556 1557 def again.keep(close: Bool, x: Bytes.Bytes) -> Maybe<&1, Bytes.Bytes>: 1558 match close: 1559 case True{}: 1560 None{} 1561 case False{}: 1562 Some{x} 1563 1564 def again.rest(close: Bool, rest: Maybe<&1, Bytes.Bytes>) -> Maybe<&1, Bytes.Bytes>: 1565 match rest: 1566 case None{}: 1567 None{} 1568 case Some{x}: 1569 again.keep(close, x) 1570 1571 # The bytes past the response when the socket can take another request: the request did not 1572 # ask to close, the response is self-delimited, and the peer did not say close. 1573 def again(close: Bool, +h: Map<&2, List<&2, String>>, rest: Maybe<&1, Bytes.Bytes>) -> Maybe<&1, Bytes.Bytes>: 1574 match close: 1575 case True{}: 1576 None{} 1577 case False{}: 1578 again.rest(String.eq(String.to_lower(header(h, "connection")), "close"), rest) 1579 1580 def res_body.res(res: Res) -> Bytes.Bytes: 1581 Res{status, headers, body} = res 1582 body 1583 1584 def res_body(m: Maybe<&1, Res>) -> Bytes.Bytes: 1585 match m: 1586 case None{}: 1587 Bytes.new(0) 1588 case Some{res}: 1589 res_body.res(res) 1590 1591 def text(res: Res) -> String: 1592 Enc.utf8.decode(res_body.res(res)) 1593 1594 # UTF-8 is checked, not repaired: bad UTF-8 in a string gives None (RFC 8259 §8.1). 1595 def json(res: Res) -> Maybe<&1, Json.Val>: 1596 Json.parse.bytes(res_body.res(res)) 1597 1598 def got_body.req(req: Req) -> Bytes.Bytes: 1599 Req{method, path, headers, body} = req 1600 body 1601 1602 def got_body(m: Maybe<&1, Req>) -> Bytes.Bytes: 1603 match m: 1604 case None{}: 1605 Bytes.new(0) 1606 case Some{req}: 1607 got_body.req(req) 1608 1609 def body.of(hb: String & String) -> String: 1610 (h, b) = hb 1611 b 1612 1613 def body(s: String) -> String: 1614 body.of(split_at_blank.go(s, False{}, 0, SNil{})) 1615 1616 def headers_lines(+k: String, vs: List<&2, String>, acc: String) -> String: 1617 match vs: 1618 case Nil{}: 1619 acc 1620 case Con{v, t}: 1621 headers_lines(k, t, acc ++ ("\r\n" ++ sanitize(k) ++ ": " ++ sanitize(v))) 1622 1623 def headers_block.go(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, acc: String) -> String: 1624 match xs: 1625 case Nil{}: 1626 acc 1627 case (k, vs) <> t: 1628 headers_block.go(t, headers_lines(k, vs, acc)) 1629 1630 def headers_block(h: Map<&2, List<&2, String>>) -> String: 1631 headers_block.go(Map.to_list(&2, List<&2, String>, h), "") 1632 1633 # RFC 9110 §15. An unknown code gets an empty reason, which RFC 9112 §4 allows. 1634 type Reason is Data: 1635 Reason{code: U32, text: String} 1636 1637 def reasons() -> List<&2, Reason>: 1638 [Reason{100, "Continue"}, Reason{101, "Switching Protocols"}, Reason{103, "Early Hints"}, 1639 Reason{200, "OK"}, Reason{201, "Created"}, Reason{202, "Accepted"}, Reason{203, "Non-Authoritative Information"}, Reason{204, "No Content"}, Reason{205, "Reset Content"}, Reason{206, "Partial Content"}, 1640 Reason{300, "Multiple Choices"}, Reason{301, "Moved Permanently"}, Reason{302, "Found"}, Reason{303, "See Other"}, Reason{304, "Not Modified"}, Reason{307, "Temporary Redirect"}, Reason{308, "Permanent Redirect"}, 1641 Reason{400, "Bad Request"}, Reason{401, "Unauthorized"}, Reason{402, "Payment Required"}, Reason{403, "Forbidden"}, Reason{404, "Not Found"}, Reason{405, "Method Not Allowed"}, Reason{406, "Not Acceptable"}, Reason{407, "Proxy Authentication Required"}, Reason{408, "Request Timeout"}, Reason{409, "Conflict"}, Reason{410, "Gone"}, Reason{411, "Length Required"}, Reason{412, "Precondition Failed"}, Reason{413, "Content Too Large"}, Reason{414, "URI Too Long"}, Reason{415, "Unsupported Media Type"}, Reason{416, "Range Not Satisfiable"}, Reason{417, "Expectation Failed"}, Reason{421, "Misdirected Request"}, Reason{422, "Unprocessable Content"}, Reason{426, "Upgrade Required"}, Reason{428, "Precondition Required"}, Reason{429, "Too Many Requests"}, Reason{431, "Request Header Fields Too Large"}, 1642 Reason{500, "Internal Server Error"}, Reason{501, "Not Implemented"}, Reason{502, "Bad Gateway"}, Reason{503, "Service Unavailable"}, Reason{504, "Gateway Timeout"}, Reason{505, "HTTP Version Not Supported"}] 1643 1644 def reason.go(xs: List<&2, Reason>, +status: U32) -> String: 1645 match xs: 1646 case Nil{}: 1647 "" 1648 case Con{Reason{k, v}, t}: 1649 Bool.pick(String, U32.is_eq(k, status), v, reason.go(t, status)) 1650 1651 def reason(+status: U32) -> String: 1652 reason.go(reasons(), status) 1653 1654 def response(+status: U32, headers: Map<&2, List<&2, String>>, body: String) -> String: 1655 ("HTTP/1.1 " ++ U32.show(status) ++ " " ++ reason(status) ++ headers_block(headers)) ++ ("\r\n\r\n" ++ body) 1656 1657 def encode.body(head: Bool, top: Bytes.Bytes, body: Bytes.Bytes) -> Bytes.Bytes: 1658 match head: 1659 case True{}: 1660 top 1661 case False{}: 1662 Bytes.append(top, body) 1663 1664 def encode.len(+status: U32, headers: Map<&2, List<&2, String>>, head: Bool, body: Bytes.Bytes) -> Bytes.Bytes: 1665 Bytes.Bytes{+len, buf} = body 1666 encode.body(head, Bytes.from_string(response(status, set(headers, "content-length", U32.show(len)), "")), Bytes.Bytes{len, buf}) 1667 1668 # RFC 9110 §6.4.1, §8.6: 1xx, 204 and 304 have no body; HEAD gets the length but no body. 1669 def encode.pick(+status: U32, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, head: Bool, nobody: Bool) -> Bytes.Bytes: 1670 match nobody: 1671 case True{}: 1672 Bytes.from_string(response(status, headers, "")) 1673 case False{}: 1674 encode.len(status, headers, head, body) 1675 1676 def encode.on(r: Res, head: Bool) -> Bytes.Bytes: 1677 Res{+status, headers, body} = r 1678 encode.pick(status, headers, body, head, frame.nobody(status, False{})) 1679 1680 def encode(r: Res) -> Bytes.Bytes: 1681 encode.on(r, False{}) 1682 1683 def request(method: String, path: String, headers: Map<&2, List<&2, String>>, body: String) -> String: 1684 (method ++ " " ++ path ++ " HTTP/1.1" ++ headers_block(headers)) ++ ("\r\n\r\n" ++ body) 1685 1686 def req.reserved(+k: String) -> Bool: 1687 Bool.or(Bool.or(String.eq(k, "host"), String.eq(k, "connection")), Bool.or(String.eq(k, "content-length"), String.eq(k, "transfer-encoding"))) 1688 1689 def req.put.one(m: Map<&2, List<&2, String>>, +k: String, vs: List<&2, String>, skip: Bool) -> Map<&2, List<&2, String>>: 1690 match skip: 1691 case True{}: 1692 m 1693 case False{}: 1694 Map.set(&2, List<&2, String>, m, k, vs) 1695 1696 def req.put(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 1697 match xs: 1698 case Nil{}: 1699 m 1700 case (k, vs) <> t: 1701 +lk = String.to_lower(k) 1702 req.put(t, req.put.one(m, lk, vs, req.reserved(lk))) 1703 1704 # RFC 9110 §8.6: no Content-Length on a bodiless request whose method gives a body no meaning. 1705 def req.cl(+m: Map<&2, List<&2, String>>, +method: String, +len: U32) -> Map<&2, List<&2, String>>: 1706 +bodied = Bool.or(Bool.not(U32.is_zero(len)), Bool.or(String.eq(method, "POST"), Bool.or(String.eq(method, "PUT"), String.eq(method, "PATCH")))) 1707 Bool.pick(Map<&2, List<&2, String>>, bodied, set(m, "content-length", U32.show(len)), m) 1708 1709 # Caller headers are lowercased so they cannot duplicate ours; host, connection, 1710 # content-length and transfer-encoding are always ours (no smuggling through them). 1711 def encode_req.word(close: Bool) -> String: 1712 match close: 1713 case True{}: 1714 "close" 1715 case False{}: 1716 "keep-alive" 1717 1718 def encode_req.on(+method: String, target: String, host: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, close: Bool) -> Bytes.Bytes: 1719 Bytes.Bytes{+len, buf} = body 1720 +h = set(set(req.put(Map.to_list(&2, List<&2, String>, headers), empty()), "host", host), "connection", encode_req.word(close)) 1721 Bytes.append(Bytes.from_string(request(method, target, req.cl(h, method, len), "")), Bytes.Bytes{len, buf}) 1722 1723 def encode_req(+method: String, target: String, host: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> Bytes.Bytes: 1724 encode_req.on(method, target, host, headers, body, True{}) 1725 1726 # Redirects (RFC 9110 §15.4, as WHATWG fetch applies them) 1727 # -------------------------------------------------------- 1728 1729 # One request to make: headers are already lowercased (see req.put). 1730 type Hop is Type: 1731 Hop{method: String, url: Url.Abs, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes} 1732 1733 def redirect.code(+s: U32) -> Bool: 1734 Bool.or(Bool.or(U32.is_eq(s, 301), U32.is_eq(s, 302)), Bool.or(U32.is_eq(s, 303), Bool.or(U32.is_eq(s, 307), U32.is_eq(s, 308)))) 1735 1736 def redirect.origin(a: Url.Abs) -> String: 1737 Url.Abs{scheme, host, port, target} = a 1738 scheme ++ "://" ++ host ++ ":" ++ U32.show(port) 1739 1740 def redirect.del(ks: List<&2, String>, m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 1741 match ks: 1742 case Nil{}: 1743 m 1744 case Con{k, t}: 1745 redirect.del(t, Map.del(&2, List<&2, String>, m, k)) 1746 1747 def redirect.hdrs(+h: Map<&2, List<&2, String>>, drop: Bool, cross: Bool) -> Map<&2, List<&2, String>>: 1748 +h2 = Bool.pick(Map<&2, List<&2, String>>, drop, redirect.del(["content-type", "content-encoding", "content-language", "content-location"], h), h) 1749 Bool.pick(Map<&2, List<&2, String>>, cross, redirect.del(["authorization", "cookie", "proxy-authorization"], h2), h2) 1750 1751 # 303 turns any method but HEAD into GET; 301 and 302 turn POST into GET; 1752 # 307 and 308 keep method and body. A GET carries no body or body headers. 1753 # Credentials do not follow a hop to another origin. 1754 def redirect.body(drop: Bool, body: Bytes.Bytes) -> Bytes.Bytes: 1755 match drop: 1756 case True{}: 1757 Bytes.new(0) 1758 case False{}: 1759 body 1760 1761 def redirect.to(+status: U32, +method: String, +from: Url.Abs, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, next: Maybe<&2, Url.Abs>) -> Maybe<&1, Hop>: 1762 match next: 1763 case None{}: 1764 None{} 1765 case Some{+to}: 1766 +drop = Bool.or(Bool.and(U32.is_eq(status, 303), Bool.not(String.eq(method, "HEAD"))), Bool.and(Bool.or(U32.is_eq(status, 301), U32.is_eq(status, 302)), String.eq(method, "POST"))) 1767 Some{Hop{Bool.pick(String, drop, "GET", method), to, redirect.hdrs(headers, drop, Bool.not(String.eq(redirect.origin(from), redirect.origin(to)))), redirect.body(drop, body)}} 1768 1769 def redirect.hop(status: U32, h: Hop, loc: String) -> Maybe<&1, Hop>: 1770 Hop{method, +url, headers, body} = h 1771 redirect.to(status, method, url, headers, body, Url.resolve(url, loc)) 1772 1773 def redirect.if(status: U32, h: Hop, loc: String, ok: Bool) -> Maybe<&1, Hop>: 1774 match ok: 1775 case False{}: 1776 None{} 1777 case True{}: 1778 redirect.hop(status, h, loc) 1779 1780 # The next request after a response, or None when it is not a redirect we follow. 1781 def redirect(+status: U32, +headers: Map<&2, List<&2, String>>, h: Hop) -> Maybe<&1, Hop>: 1782 redirect.if(status, h, header(headers, "location"), Bool.and(redirect.code(status), has_header(headers, "location"))) 1783 1784 def io.recv.words(tls: Bool, s: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, U32 & Array>): 1785 match tls: 1786 case True{}: 1787 Wire.tls.recv.words(s, max, ms) 1788 case False{}: 1789 Wire.recv.words(s, max, ms) 1790 1791 def io.send(tls: Bool, s: Socket, data: String) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): 1792 match tls: 1793 case True{}: 1794 Wire.tls.send(s, data) 1795 case False{}: 1796 Wire.send(s, data) 1797 1798 def io.send.words(tls: Bool, s: Socket, +len: U32, buf: Array) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): 1799 match tls: 1800 case True{}: 1801 Wire.tls.send.words(s, len, buf) 1802 case False{}: 1803 Wire.send.words(s, len, buf) 1804 1805 def io.send.bytes(tls: Bool, s: Socket, b: Bytes.Bytes) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): 1806 Bytes.Bytes{+len, buf} = b 1807 io.send.words(tls, s, len, buf) 1808 1809 def io.close(tls: Bool, s: Socket) -> IO(Unit): 1810 match tls: 1811 case True{}: 1812 Wire.tls.close(s) 1813 case False{}: 1814 Socket.close(s) 1815 1816 def fetch.close(tls: Bool, s: Socket, r: Result<&1, &1, Err, Res>) -> IO(Result<&1, &1, Err, Res>): 1817 do IO>: 1818 io.close(tls, s) 1819 return r 1820 1821 # none: no response byte arrived, so an idempotent request may be retried (RFC 9112 §9.3.1). 1822 # sock: the socket, when it can take another request. 1823 type Out is Type: 1824 OutDone{res: Res, sock: Maybe<&1, Socket>, rest: Bytes.Bytes} 1825 OutFail{e: Err, none: Bool} 1826 1827 def fetch.boxed(r: Result<&1, &1, Err, Res>, none: Bool) -> Out: 1828 match r: 1829 case Done{res}: 1830 OutDone{res, None{}, Bytes.new(0)} 1831 case Fail{e}: 1832 OutFail{e, none} 1833 1834 def fetch.bad(tls: Bool, s: Socket, e: Maybe<&2, Err>) -> IO(Result<&1, &1, Err, Res>): 1835 match e: 1836 case None{}: 1837 fetch.close(tls, s, Fail{ErrBad{}}) 1838 case Some{err}: 1839 fetch.close(tls, s, Fail{err}) 1840 1841 def fetch.shut(tls: Bool, s: Socket, r: Result<&1, &1, Err, Res>, none: Bool) -> IO(Out): 1842 do IO: 1843 x : Result<&1, &1, Err, Res> <- fetch.close(tls, s, r) 1844 return fetch.boxed(x, none) 1845 1846 def fetch.hold.go(tls: Bool, s: Socket, res: Res, left: Maybe<&1, Bytes.Bytes>) -> IO(Out): 1847 match left: 1848 case Some{x}: 1849 IO.pure(Out, OutDone{res, Some{s}, x}) 1850 case None{}: 1851 fetch.shut(tls, s, Done{res}, False{}) 1852 1853 def fetch.hold(tls: Bool, close: Bool, s: Socket, res: Res, left: Maybe<&1, Bytes.Bytes>) -> IO(Out): 1854 Res{status, +headers, body} = res 1855 fetch.hold.go(tls, s, Res{status, headers, body}, again(close, headers, left)) 1856 1857 def fetch.failout(tls: Bool, s: Socket, e: Maybe<&2, Err>, none: Bool) -> IO(Out): 1858 do IO: 1859 r : Result<&1, &1, Err, Res> <- fetch.bad(tls, s, e) 1860 return fetch.boxed(r, none) 1861 1862 # Read state: the response so far and the bytes read, or how it ended. 1863 # none: no byte arrived, so an idempotent request may be retried. 1864 type Fl is Type: 1865 FlMore{rv: Rv, n: U32} 1866 FlBad{err: Maybe<&2, Err>, none: Bool} 1867 FlDone{res: Res, rest: Maybe<&1, Bytes.Bytes>} 1868 1869 def fetch.rt(+n: U32, t: Rt) -> Fl: 1870 match t: 1871 case RtMore{rv}: 1872 FlMore{rv, n} 1873 case RtBad{}: 1874 FlBad{None{}, U32.is_zero(n)} 1875 case RtDone{res, rest}: 1876 FlDone{res, rest} 1877 1878 # 64 KiB of head past the body cap, as serve allows. 1879 def fetch.over(+n: U32) -> Bool: 1880 Nat.is_lt(Nat.add(fetch.max(), 65536n), U32.to_nat(n)) 1881 1882 def fetch.piece.cap(over: Bool, +head: Bool, rv: Rv, +n2: U32, +len: U32, buf: Array) -> Fl: 1883 match over: 1884 case True{}: 1885 FlBad{None{}, False{}} 1886 case False{}: 1887 fetch.rt(n2, rv.step(U32.is_zero(len), head, rv, Bytes.Bytes{len, buf})) 1888 1889 def fetch.piece(+head: Bool, rv: Rv, +n: U32, p: U32 & Array) -> Fl: 1890 (+len, buf) = p 1891 +n2 = (n + len : U32) 1892 fetch.piece.cap(fetch.over(n2), head, rv, n2, len, buf) 1893 1894 def fetch.next(+head: Bool, rv: Rv, +n: U32, m: Socket & Result<&1, &1, U32 & String, U32 & Array>) -> Socket & Fl: 1895 (s, r) = m 1896 match r: 1897 case Fail{(+code, why)}: 1898 (s, FlBad{Some{err.or_late(code, ErrRead{code, why})}, U32.is_zero(n)}) 1899 case Done{p}: 1900 (s, fetch.piece(head, rv, n, p)) 1901 1902 # Reads until the response is whole or cannot be; an empty read means the server closed. 1903 @unsafe 1904 def fetch.loop(+close: Bool, +head: Bool, +tls: Bool, +ms: U32, st: Socket & Fl) -> IO(Out): 1905 (s, fl) = st 1906 match fl: 1907 case FlMore{rv, +n}: 1908 do IO: 1909 m : Socket & Result<&1, &1, U32 & String, U32 & Array> <- io.recv.words(tls, s, 65536, ms) 1910 fetch.loop(close, head, tls, ms, fetch.next(head, rv, n, m)) 1911 case FlBad{err, none}: 1912 fetch.failout(tls, s, err, none) 1913 case FlDone{res, rest}: 1914 fetch.hold(tls, close, s, res, rest) 1915 1916 def exchange.sent(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Out): 1917 (s, r) = m 1918 match r: 1919 case Fail{(+code, why)}: 1920 fetch.shut(tls, s, Fail{err.or_late(code, ErrWrite{code, why})}, True{}) 1921 case Done{u}: 1922 fetch.loop(close, head, tls, ms, (s, FlMore{rv.start(), 0})) 1923 1924 def exchange.go(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, s: Socket, wire: Bytes.Bytes) -> IO(Out): 1925 do IO: 1926 sent : Socket & Result<&1, &1, U32 & String, Unit> <- io.send.bytes(tls, s, wire) 1927 exchange.sent(tls, ms, close, head, sent) 1928 1929 def exchange.tup(o: Out) -> Maybe<&1, Socket> & Result<&1, &1, Err, Res> & Bytes.Bytes: 1930 match o: 1931 case OutFail{e, none}: 1932 (None{}, Fail{e}, Bytes.new(0)) 1933 case OutDone{res, sock, rest}: 1934 (sock, Done{res}, rest) 1935 1936 # One request on an open socket. Some{socket}: it can take another request. 1937 def exchange(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, s: Socket, wire: Bytes.Bytes) -> IO(Maybe<&1, Socket> & Result<&1, &1, Err, Res> & Bytes.Bytes): 1938 do IO & Result<&1, &1, Err, Res> & Bytes.Bytes>: 1939 o : Out <- exchange.go(tls, ms, close, head, s, wire) 1940 return exchange.tup(o) 1941 1942 # Paths to a PEM client chain and its private key for mutual TLS. 1943 type Cert is Data: 1944 Cert{chain: String, key: String} 1945 1946 # Connection pool: idle sockets by origin and client identity, newest first, at most cap per key. 1947 # fetch uses a fresh pool per call. 1948 1949 type Conn is Type: 1950 Conn{tls: Bool, s: Socket} 1951 ConnH2{s: Socket, client: H2.Client} 1952 1953 type Send is Type: 1954 Send{method: String, target: String, authority: String, 1955 headers: Map<&2, List<&2, String>>, body: Bytes.Bytes} 1956 1957 type H2Step is Type: 1958 H2Reply{s: Socket, reply: H2.Reply} 1959 H2Sent{client: H2.Client, events: List<&1, H2.ClientEvent>, 1960 sent: Result<&1, &1, Err, Socket>} 1961 H2Events{s: Socket, client: H2.Client, result: Result<&1, &1, Err, Maybe<&1, Res>>} 1962 H2Received{client: H2.Client, 1963 answer: Socket & Result<&1, &1, U32 & String, U32 & Array>} 1964 1965 type Pool is Type: 1966 Pool{cap: U32, idle: Map<&1, List<&1, Conn>>} 1967 1968 def pool.new.with(+cap: U32) -> Pool: 1969 Pool{cap, Map.new(&1, List<&1, Conn>)} 1970 1971 # Up to 8 idle sockets per origin. 1972 def pool.new() -> Pool: 1973 pool.new.with(8) 1974 1975 def pool.key(+scheme: String, +host: String, +port: U32) -> String: 1976 scheme ++ "://" ++ host ++ ":" ++ U32.show(port) 1977 1978 def pool.key.cert(+base: String, cert: Maybe<&2, Cert>) -> String: 1979 match cert: 1980 case None{}: 1981 base 1982 case Some{Cert{+chain, +key}}: 1983 base ++ "|cert:" ++ Nat.show(String.length(chain)) ++ ":" ++ chain 1984 ++ Nat.show(String.length(key)) ++ ":" ++ key 1985 1986 def pool.take.of(+cap: U32, +key: String, r: Map<&1, List<&1, Conn>> & Maybe<&1, List<&1, Conn>>) -> Pool & Maybe<&1, Conn>: 1987 (m, got) = r 1988 match got: 1989 case None{}: 1990 (Pool{cap, m}, None{}) 1991 case Some{xs}: 1992 match xs: 1993 case Nil{}: 1994 (Pool{cap, m}, None{}) 1995 case Con{c, t}: 1996 (Pool{cap, Map.set(&1, List<&1, Conn>, m, key, t)}, Some{c}) 1997 1998 # The socket given back last. 1999 def pool.take(p: Pool, +key: String) -> Pool & Maybe<&1, Conn>: 2000 Pool{+cap, idle} = p Lines 1-2000 of 4238. More: https://jend.leeeet.dev/read.txt?path=bend-kit-http@0.20.1.0/http.bend&start_line=2001