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