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