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.0.3, -url@0.4.0.0, -encoding@0.3.0.0, -json@0.3.0.0, -bytes@0.3.0.0, -dns@0.3.2.1, -zlib@0.1.0.0. 4 import 0x096635686408886b7d907f16c4550317/wire.bend as Wire 5 import 0xd248560355ba8929ae030bc9c72f40be/url.bend as Url 6 import 0xcfc8be7b076f41f95c8e118383892d55/encoding.bend as Enc 7 import 0xaaa10a97bf5ac6990143da2c863f8a3f/json.bend as Json 8 import 0x49814d83de8f70993a43e1002be29ecd/bytes.bend as Bytes 9 import 0xc10a5eaaa9c896e1570e279945f4241e/dns.bend as Dns 10 import 0xe01785b64266bf3ba0068183b9f9f5e3/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 # A response read in pieces. Head: the head is not whole yet. Body: it is, and parts is the body so far. 1312 type Rv is Type: 1313 RvHead{buf: Bytes.Bytes, k: U32} 1314 RvBody{res: Res, rb: Rb, parts: List<&1, Bytes.Bytes>} 1315 1316 # More: read on. Done: the response, and the bytes after it when the body is self-delimited. 1317 type Rt is Type: 1318 RtMore{rv: Rv} 1319 RtBad{} 1320 RtDone{res: Res, rest: Maybe<&1, Bytes.Bytes>} 1321 1322 def rv.start() -> Rv: 1323 RvHead{Bytes.new(0), 0} 1324 1325 def rv.done(res: Res, parts: List<&1, Bytes.Bytes>, rest: Maybe<&1, Bytes.Bytes>) -> Rt: 1326 Res{status, headers, body} = res 1327 RtDone{Res{status, headers, rb.body(parts)}, rest} 1328 1329 def rv.fed(res: Res, bf: Bf) -> Rt: 1330 Bf{rb, parts, rest} = bf 1331 match rb: 1332 case RbDone{}: 1333 rv.done(res, parts, Some{rest}) 1334 case RbBad{}: 1335 RtBad{} 1336 case RbLen{n}: 1337 RtMore{RvBody{res, RbLen{n}, parts}} 1338 case RbChunk{st}: 1339 RtMore{RvBody{res, RbChunk{st}, parts}} 1340 case RbClose{}: 1341 RtMore{RvBody{res, RbClose{}, parts}} 1342 1343 def rv.head(hs: Hs) -> Rt: 1344 match hs: 1345 case HsWait{buf, k}: 1346 RtMore{RvHead{buf, k}} 1347 case HsBad{}: 1348 RtBad{} 1349 case HsSeek{buf, from, k}: 1350 RtBad{} 1351 case HsRes{res, rb, rest}: 1352 rv.fed(res, rb.feed(rb, Nil{}, rest)) 1353 1354 def rv.eof(res: Res, parts: List<&1, Bytes.Bytes>, ok: Bool) -> Rt: 1355 match ok: 1356 case True{}: 1357 rv.done(res, parts, None{}) 1358 case False{}: 1359 RtBad{} 1360 1361 # One read: piece, or the server closed (RFC 9112 §8: a message cut short is not a message). 1362 def rv.step(closed: Bool, +head: Bool, rv: Rv, piece: Bytes.Bytes) -> Rt: 1363 match closed: 1364 case True{}: 1365 match rv: 1366 case RvHead{buf, k}: 1367 RtBad{} 1368 case RvBody{res, rb, parts}: 1369 rv.eof(res, parts, rb.eof(rb)) 1370 case False{}: 1371 match rv: 1372 case RvHead{buf, +k}: 1373 rv.head(hs.more(head, k, buf, piece)) 1374 case RvBody{res, rb, parts}: 1375 rv.fed(res, rb.feed(rb, parts, piece)) 1376 1377 def rv.end(closed: Bool, +head: Bool, t: Rt) -> Rt: 1378 match closed: 1379 case False{}: 1380 t 1381 case True{}: 1382 match t: 1383 case RtMore{rv}: 1384 rv.step(True{}, head, rv, Bytes.new(0)) 1385 case RtBad{}: 1386 RtBad{} 1387 case RtDone{res, rest}: 1388 RtDone{res, rest} 1389 1390 def rv.go(xs: List<&2, String>, closed: Bool, +head: Bool, t: Rt) -> Rt: 1391 match xs: 1392 case Nil{}: 1393 rv.end(closed, head, t) 1394 case Con{+x, rest}: 1395 match t: 1396 case RtMore{rv}: 1397 rv.go(rest, closed, head, rv.step(False{}, head, rv, Bytes.from_string(x))) 1398 case RtBad{}: 1399 RtBad{} 1400 case RtDone{res, r}: 1401 RtDone{res, r} 1402 1403 def rt.frame(t: Rt) -> Frame: 1404 match t: 1405 case RtMore{rv}: 1406 FrameMore{} 1407 case RtBad{}: 1408 FrameBad{} 1409 case RtDone{res, rest}: 1410 FrameDone{res} 1411 1412 # What the read loop makes of these pieces, then a close when closed is set. 1413 def res.frame(xs: List<&2, String>, closed: Bool, +head: Bool) -> Frame: 1414 rt.frame(rv.go(xs, closed, head, RtMore{rv.start()})) 1415 1416 def rt.rest(t: Rt) -> Maybe<&1, Bytes.Bytes>: 1417 match t: 1418 case RtMore{rv}: 1419 None{} 1420 case RtBad{}: 1421 None{} 1422 case RtDone{res, rest}: 1423 rest 1424 1425 def after.of(m: Maybe<&1, Bytes.Bytes>) -> Maybe<&2, String>: 1426 match m: 1427 case None{}: 1428 None{} 1429 case Some{b}: 1430 Some{Bytes.to_string(b)} 1431 1432 # Bytes after a complete self-delimited message. None if it is not one yet. 1433 def after(+raw: String, +head: Bool) -> Maybe<&2, String>: 1434 after.of(rt.rest(rv.go([raw], False{}, head, RtMore{rv.start()}))) 1435 1436 def req.v11.ver(mpv: String & String & String) -> Bool: 1437 (+method, path, v) = mpv 1438 String.eq(v, "HTTP/1.1") 1439 1440 def req.v11.line(+line: String) -> Bool: 1441 req.v11.ver(start_line(line)) 1442 1443 def req.v11.lines(xs: List<&2, String>) -> Bool: 1444 match xs: 1445 case Nil{}: 1446 False{} 1447 case Con{line, headers}: 1448 req.v11.line(line) 1449 1450 def req.v11.pick(+head: String, ok: Bool) -> Bool: 1451 match ok: 1452 case False{}: 1453 False{} 1454 case True{}: 1455 req.v11.lines(String.lines(head)) 1456 1457 def req.v11.of(hb: String & String) -> Bool: 1458 (+head, rest) = hb 1459 req.v11.pick(head, blank_end(head)) 1460 1461 def req.v11(+raw: String) -> Bool: 1462 req.v11.of(split_at_blank(raw)) 1463 1464 def req.conn_close(+headers: Map<&2, List<&2, String>>) -> Bool: 1465 String.eq(String.to_lower(header(headers, "connection")), "close") 1466 1467 def res.conn_close(+headers: Map<&2, List<&2, String>>) -> Bool: 1468 String.eq(String.to_lower(header(headers, "connection")), "close") 1469 1470 def serve.fail_close(+status: U32) -> Bool: 1471 Bool.or(U32.is_eq(status, 400), Bool.or(U32.is_eq(status, 413), U32.is_eq(status, 431))) 1472 1473 def serve.keep.ok(fail: Bool, res_close: Bool) -> Bool: 1474 match fail: 1475 case True{}: 1476 False{} 1477 case False{}: 1478 Bool.not(res_close) 1479 1480 def serve.keep.v11(v11: Bool, +req_close: Bool, +status: U32, res_close: Bool) -> Bool: 1481 match v11: 1482 case False{}: 1483 False{} 1484 case True{}: 1485 match req_close: 1486 case True{}: 1487 False{} 1488 case False{}: 1489 serve.keep.ok(serve.fail_close(status), res_close) 1490 1491 def serve.keep(+headers: Map<&2, List<&2, String>>, v11: Bool, +status: U32, +res: Map<&2, List<&2, String>>) -> Bool: 1492 serve.keep.v11(v11, req.conn_close(headers), status, res.conn_close(res)) 1493 1494 1495 def again.keep(close: Bool, x: Bytes.Bytes) -> Maybe<&1, Bytes.Bytes>: 1496 match close: 1497 case True{}: 1498 None{} 1499 case False{}: 1500 Some{x} 1501 1502 def again.rest(close: Bool, rest: Maybe<&1, Bytes.Bytes>) -> Maybe<&1, Bytes.Bytes>: 1503 match rest: 1504 case None{}: 1505 None{} 1506 case Some{x}: 1507 again.keep(close, x) 1508 1509 # The bytes past the response when the socket can take another request: the request did not 1510 # ask to close, the response is self-delimited, and the peer did not say close. 1511 def again(close: Bool, +h: Map<&2, List<&2, String>>, rest: Maybe<&1, Bytes.Bytes>) -> Maybe<&1, Bytes.Bytes>: 1512 match close: 1513 case True{}: 1514 None{} 1515 case False{}: 1516 again.rest(String.eq(String.to_lower(header(h, "connection")), "close"), rest) 1517 1518 def res_body.res(res: Res) -> Bytes.Bytes: 1519 Res{status, headers, body} = res 1520 body 1521 1522 def res_body(m: Maybe<&1, Res>) -> Bytes.Bytes: 1523 match m: 1524 case None{}: 1525 Bytes.new(0) 1526 case Some{res}: 1527 res_body.res(res) 1528 1529 def text(res: Res) -> String: 1530 Enc.utf8.decode(res_body.res(res)) 1531 1532 def json(res: Res) -> Maybe<&2, Json.Val>: 1533 Json.parse(text(res)) 1534 1535 def got_body.req(req: Req) -> Bytes.Bytes: 1536 Req{method, path, headers, body} = req 1537 body 1538 1539 def got_body(m: Maybe<&1, Req>) -> Bytes.Bytes: 1540 match m: 1541 case None{}: 1542 Bytes.new(0) 1543 case Some{req}: 1544 got_body.req(req) 1545 1546 def body.of(hb: String & String) -> String: 1547 (h, b) = hb 1548 b 1549 1550 def body(s: String) -> String: 1551 body.of(split_at_blank.go(s, False{}, 0, SNil{})) 1552 1553 def headers_lines(+k: String, vs: List<&2, String>, acc: String) -> String: 1554 match vs: 1555 case Nil{}: 1556 acc 1557 case Con{v, t}: 1558 headers_lines(k, t, acc ++ ("\r\n" ++ sanitize(k) ++ ": " ++ sanitize(v))) 1559 1560 def headers_block.go(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, acc: String) -> String: 1561 match xs: 1562 case Nil{}: 1563 acc 1564 case (k, vs) <> t: 1565 headers_block.go(t, headers_lines(k, vs, acc)) 1566 1567 def headers_block(h: Map<&2, List<&2, String>>) -> String: 1568 headers_block.go(Map.to_list(&2, List<&2, String>, h), "") 1569 1570 # RFC 9110 §15. An unknown code gets an empty reason, which RFC 9112 §4 allows. 1571 type Reason is Data: 1572 Reason{code: U32, text: String} 1573 1574 def reasons() -> List<&2, Reason>: 1575 [Reason{100, "Continue"}, Reason{101, "Switching Protocols"}, Reason{103, "Early Hints"}, 1576 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"}, 1577 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"}, 1578 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"}, 1579 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"}] 1580 1581 def reason.go(xs: List<&2, Reason>, +status: U32) -> String: 1582 match xs: 1583 case Nil{}: 1584 "" 1585 case Con{Reason{k, v}, t}: 1586 Bool.pick(String, U32.is_eq(k, status), v, reason.go(t, status)) 1587 1588 def reason(+status: U32) -> String: 1589 reason.go(reasons(), status) 1590 1591 def response(+status: U32, headers: Map<&2, List<&2, String>>, body: String) -> String: 1592 ("HTTP/1.1 " ++ U32.show(status) ++ " " ++ reason(status) ++ headers_block(headers)) ++ ("\r\n\r\n" ++ body) 1593 1594 def encode.body(head: Bool, top: Bytes.Bytes, body: Bytes.Bytes) -> Bytes.Bytes: 1595 match head: 1596 case True{}: 1597 top 1598 case False{}: 1599 Bytes.append(top, body) 1600 1601 def encode.len(+status: U32, headers: Map<&2, List<&2, String>>, head: Bool, body: Bytes.Bytes) -> Bytes.Bytes: 1602 Bytes.Bytes{+len, buf} = body 1603 encode.body(head, Bytes.from_string(response(status, set(headers, "content-length", U32.show(len)), "")), Bytes.Bytes{len, buf}) 1604 1605 # RFC 9110 §6.4.1, §8.6: 1xx, 204 and 304 have no body; HEAD gets the length but no body. 1606 def encode.pick(+status: U32, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, head: Bool, nobody: Bool) -> Bytes.Bytes: 1607 match nobody: 1608 case True{}: 1609 Bytes.from_string(response(status, headers, "")) 1610 case False{}: 1611 encode.len(status, headers, head, body) 1612 1613 def encode.on(r: Res, head: Bool) -> Bytes.Bytes: 1614 Res{+status, headers, body} = r 1615 encode.pick(status, headers, body, head, frame.nobody(status, False{})) 1616 1617 def encode(r: Res) -> Bytes.Bytes: 1618 encode.on(r, False{}) 1619 1620 def request(method: String, path: String, headers: Map<&2, List<&2, String>>, body: String) -> String: 1621 (method ++ " " ++ path ++ " HTTP/1.1" ++ headers_block(headers)) ++ ("\r\n\r\n" ++ body) 1622 1623 def req.reserved(+k: String) -> Bool: 1624 Bool.or(Bool.or(String.eq(k, "host"), String.eq(k, "connection")), Bool.or(String.eq(k, "content-length"), String.eq(k, "transfer-encoding"))) 1625 1626 def req.put.one(m: Map<&2, List<&2, String>>, +k: String, vs: List<&2, String>, skip: Bool) -> Map<&2, List<&2, String>>: 1627 match skip: 1628 case True{}: 1629 m 1630 case False{}: 1631 Map.set(&2, List<&2, String>, m, k, vs) 1632 1633 def req.put(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 1634 match xs: 1635 case Nil{}: 1636 m 1637 case (k, vs) <> t: 1638 +lk = String.to_lower(k) 1639 req.put(t, req.put.one(m, lk, vs, req.reserved(lk))) 1640 1641 # RFC 9110 §8.6: no Content-Length on a bodiless request whose method gives a body no meaning. 1642 def req.cl(+m: Map<&2, List<&2, String>>, +method: String, +len: U32) -> Map<&2, List<&2, String>>: 1643 +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")))) 1644 Bool.pick(Map<&2, List<&2, String>>, bodied, set(m, "content-length", U32.show(len)), m) 1645 1646 # Caller headers are lowercased so they cannot duplicate ours; host, connection, 1647 # content-length and transfer-encoding are always ours (no smuggling through them). 1648 def encode_req.word(close: Bool) -> String: 1649 match close: 1650 case True{}: 1651 "close" 1652 case False{}: 1653 "keep-alive" 1654 1655 def encode_req.on(+method: String, target: String, host: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, close: Bool) -> Bytes.Bytes: 1656 Bytes.Bytes{+len, buf} = body 1657 +h = set(set(req.put(Map.to_list(&2, List<&2, String>, headers), empty()), "host", host), "connection", encode_req.word(close)) 1658 Bytes.append(Bytes.from_string(request(method, target, req.cl(h, method, len), "")), Bytes.Bytes{len, buf}) 1659 1660 def encode_req(+method: String, target: String, host: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> Bytes.Bytes: 1661 encode_req.on(method, target, host, headers, body, True{}) 1662 1663 # Redirects (RFC 9110 §15.4, as WHATWG fetch applies them) 1664 # -------------------------------------------------------- 1665 1666 # One request to make: headers are already lowercased (see req.put). 1667 type Hop is Type: 1668 Hop{method: String, url: Url.Abs, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes} 1669 1670 def redirect.code(+s: U32) -> Bool: 1671 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)))) 1672 1673 def redirect.origin(a: Url.Abs) -> String: 1674 Url.Abs{scheme, host, port, target} = a 1675 scheme ++ "://" ++ host ++ ":" ++ U32.show(port) 1676 1677 def redirect.del(ks: List<&2, String>, m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 1678 match ks: 1679 case Nil{}: 1680 m 1681 case Con{k, t}: 1682 redirect.del(t, Map.del(&2, List<&2, String>, m, k)) 1683 1684 def redirect.hdrs(+h: Map<&2, List<&2, String>>, drop: Bool, cross: Bool) -> Map<&2, List<&2, String>>: 1685 +h2 = Bool.pick(Map<&2, List<&2, String>>, drop, redirect.del(["content-type", "content-encoding", "content-language", "content-location"], h), h) 1686 Bool.pick(Map<&2, List<&2, String>>, cross, redirect.del(["authorization", "cookie", "proxy-authorization"], h2), h2) 1687 1688 # 303 turns any method but HEAD into GET; 301 and 302 turn POST into GET; 1689 # 307 and 308 keep method and body. A GET carries no body or body headers. 1690 # Credentials do not follow a hop to another origin. 1691 def redirect.body(drop: Bool, body: Bytes.Bytes) -> Bytes.Bytes: 1692 match drop: 1693 case True{}: 1694 Bytes.new(0) 1695 case False{}: 1696 body 1697 1698 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>: 1699 match next: 1700 case None{}: 1701 None{} 1702 case Some{+to}: 1703 +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"))) 1704 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)}} 1705 1706 def redirect.hop(status: U32, h: Hop, loc: String) -> Maybe<&1, Hop>: 1707 Hop{method, +url, headers, body} = h 1708 redirect.to(status, method, url, headers, body, Url.resolve(url, loc)) 1709 1710 def redirect.if(status: U32, h: Hop, loc: String, ok: Bool) -> Maybe<&1, Hop>: 1711 match ok: 1712 case False{}: 1713 None{} 1714 case True{}: 1715 redirect.hop(status, h, loc) 1716 1717 # The next request after a response, or None when it is not a redirect we follow. 1718 def redirect(+status: U32, +headers: Map<&2, List<&2, String>>, h: Hop) -> Maybe<&1, Hop>: 1719 redirect.if(status, h, header(headers, "location"), Bool.and(redirect.code(status), has_header(headers, "location"))) 1720 1721 def io.recv.words(tls: Bool, s: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, U32 & Array>): 1722 match tls: 1723 case True{}: 1724 Wire.tls.recv.words(s, max, ms) 1725 case False{}: 1726 Wire.recv.words(s, max, ms) 1727 1728 def io.send(tls: Bool, s: Socket, data: String) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): 1729 match tls: 1730 case True{}: 1731 Wire.tls.send(s, data) 1732 case False{}: 1733 Wire.send(s, data) 1734 1735 def io.send.words(tls: Bool, s: Socket, +len: U32, buf: Array) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): 1736 match tls: 1737 case True{}: 1738 Wire.tls.send.words(s, len, buf) 1739 case False{}: 1740 Wire.send.words(s, len, buf) 1741 1742 def io.send.bytes(tls: Bool, s: Socket, b: Bytes.Bytes) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): 1743 Bytes.Bytes{+len, buf} = b 1744 io.send.words(tls, s, len, buf) 1745 1746 def io.close(tls: Bool, s: Socket) -> IO(Unit): 1747 match tls: 1748 case True{}: 1749 Wire.tls.close(s) 1750 case False{}: 1751 Socket.close(s) 1752 1753 def fetch.close(tls: Bool, s: Socket, r: Result<&1, &1, Err, Res>) -> IO(Result<&1, &1, Err, Res>): 1754 do IO>: 1755 io.close(tls, s) 1756 return r 1757 1758 # Untrusted servers must not grow the buffer without bound. 1759 def fetch.max() -> Nat: 1760 U32.to_nat(16777216) 1761 1762 # none: no response byte arrived, so an idempotent request may be retried (RFC 9112 §9.3.1). 1763 # sock: the socket, when it can take another request. 1764 type Out is Type: 1765 OutDone{res: Res, sock: Maybe<&1, Socket>, rest: Bytes.Bytes} 1766 OutFail{e: Err, none: Bool} 1767 1768 def fetch.boxed(r: Result<&1, &1, Err, Res>, none: Bool) -> Out: 1769 match r: 1770 case Done{res}: 1771 OutDone{res, None{}, Bytes.new(0)} 1772 case Fail{e}: 1773 OutFail{e, none} 1774 1775 def fetch.bad(tls: Bool, s: Socket, e: Maybe<&2, Err>) -> IO(Result<&1, &1, Err, Res>): 1776 match e: 1777 case None{}: 1778 fetch.close(tls, s, Fail{ErrBad{}}) 1779 case Some{err}: 1780 fetch.close(tls, s, Fail{err}) 1781 1782 def fetch.shut(tls: Bool, s: Socket, r: Result<&1, &1, Err, Res>, none: Bool) -> IO(Out): 1783 do IO: 1784 x : Result<&1, &1, Err, Res> <- fetch.close(tls, s, r) 1785 return fetch.boxed(x, none) 1786 1787 def fetch.hold.go(tls: Bool, s: Socket, res: Res, left: Maybe<&1, Bytes.Bytes>) -> IO(Out): 1788 match left: 1789 case Some{x}: 1790 IO.pure(Out, OutDone{res, Some{s}, x}) 1791 case None{}: 1792 fetch.shut(tls, s, Done{res}, False{}) 1793 1794 def fetch.hold(tls: Bool, close: Bool, s: Socket, res: Res, left: Maybe<&1, Bytes.Bytes>) -> IO(Out): 1795 Res{status, +headers, body} = res 1796 fetch.hold.go(tls, s, Res{status, headers, body}, again(close, headers, left)) 1797 1798 def fetch.failout(tls: Bool, s: Socket, e: Maybe<&2, Err>, none: Bool) -> IO(Out): 1799 do IO: 1800 r : Result<&1, &1, Err, Res> <- fetch.bad(tls, s, e) 1801 return fetch.boxed(r, none) 1802 1803 # Read state: the response so far and the bytes read, or how it ended. 1804 # none: no byte arrived, so an idempotent request may be retried. 1805 type Fl is Type: 1806 FlMore{rv: Rv, n: U32} 1807 FlBad{err: Maybe<&2, Err>, none: Bool} 1808 FlDone{res: Res, rest: Maybe<&1, Bytes.Bytes>} 1809 1810 def fetch.rt(+n: U32, t: Rt) -> Fl: 1811 match t: 1812 case RtMore{rv}: 1813 FlMore{rv, n} 1814 case RtBad{}: 1815 FlBad{None{}, U32.is_zero(n)} 1816 case RtDone{res, rest}: 1817 FlDone{res, rest} 1818 1819 # 64 KiB of head past the body cap, as serve allows. 1820 def fetch.over(+n: U32) -> Bool: 1821 Nat.is_lt(Nat.add(fetch.max(), 65536n), U32.to_nat(n)) 1822 1823 def fetch.piece.cap(over: Bool, +head: Bool, rv: Rv, +n2: U32, +len: U32, buf: Array) -> Fl: 1824 match over: 1825 case True{}: 1826 FlBad{None{}, False{}} 1827 case False{}: 1828 fetch.rt(n2, rv.step(U32.is_zero(len), head, rv, Bytes.Bytes{len, buf})) 1829 1830 def fetch.piece(+head: Bool, rv: Rv, +n: U32, p: U32 & Array) -> Fl: 1831 (+len, buf) = p 1832 +n2 = (n + len : U32) 1833 fetch.piece.cap(fetch.over(n2), head, rv, n2, len, buf) 1834 1835 def fetch.next(+head: Bool, rv: Rv, +n: U32, m: Socket & Result<&1, &1, U32 & String, U32 & Array>) -> Socket & Fl: 1836 (s, r) = m 1837 match r: 1838 case Fail{(+code, why)}: 1839 (s, FlBad{Some{err.or_late(code, ErrRead{code, why})}, U32.is_zero(n)}) 1840 case Done{p}: 1841 (s, fetch.piece(head, rv, n, p)) 1842 1843 # Reads until the response is whole or cannot be; an empty read means the server closed. 1844 @unsafe 1845 def fetch.loop(+close: Bool, +head: Bool, +tls: Bool, +ms: U32, st: Socket & Fl) -> IO(Out): 1846 (s, fl) = st 1847 match fl: 1848 case FlMore{rv, +n}: 1849 do IO: 1850 m : Socket & Result<&1, &1, U32 & String, U32 & Array> <- io.recv.words(tls, s, 65536, ms) 1851 fetch.loop(close, head, tls, ms, fetch.next(head, rv, n, m)) 1852 case FlBad{err, none}: 1853 fetch.failout(tls, s, err, none) 1854 case FlDone{res, rest}: 1855 fetch.hold(tls, close, s, res, rest) 1856 1857 def exchange.sent(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Out): 1858 (s, r) = m 1859 match r: 1860 case Fail{(+code, why)}: 1861 fetch.shut(tls, s, Fail{err.or_late(code, ErrWrite{code, why})}, True{}) 1862 case Done{u}: 1863 fetch.loop(close, head, tls, ms, (s, FlMore{rv.start(), 0})) 1864 1865 def exchange.go(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, s: Socket, wire: Bytes.Bytes) -> IO(Out): 1866 do IO: 1867 sent : Socket & Result<&1, &1, U32 & String, Unit> <- io.send.bytes(tls, s, wire) 1868 exchange.sent(tls, ms, close, head, sent) 1869 1870 def exchange.tup(o: Out) -> Maybe<&1, Socket> & Result<&1, &1, Err, Res> & Bytes.Bytes: 1871 match o: 1872 case OutFail{e, none}: 1873 (None{}, Fail{e}, Bytes.new(0)) 1874 case OutDone{res, sock, rest}: 1875 (sock, Done{res}, rest) 1876 1877 # One request on an open socket. Some{socket}: it can take another request. 1878 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): 1879 do IO & Result<&1, &1, Err, Res> & Bytes.Bytes>: 1880 o : Out <- exchange.go(tls, ms, close, head, s, wire) 1881 return exchange.tup(o) 1882 1883 # Connection pool: idle sockets by scheme, host, and port. fetch uses a fresh pool per call. 1884 type Conn is Type: 1885 Conn{tls: Bool, s: Socket} 1886 1887 type Pool is Type: 1888 Pool{idle: Map<&1, Conn>} 1889 1890 def pool.new() -> Pool: 1891 Pool{Map.new(&1, Conn)} 1892 1893 def pool.key(+scheme: String, +host: String, +port: U32) -> String: 1894 scheme ++ "://" ++ host ++ ":" ++ U32.show(port) 1895 1896 def pool.take.of(r: Map<&1, Conn> & Maybe<&1, Conn>) -> Pool & Maybe<&1, Conn>: 1897 (m, c) = r 1898 (Pool{m}, c) 1899 1900 def pool.take(p: Pool, +key: String) -> Pool & Maybe<&1, Conn>: 1901 Pool{idle} = p 1902 pool.take.of(Map.pop(&1, Conn, idle, key)) 1903 1904 def conn.close(c: Conn) -> IO(Unit): 1905 Conn{tls, s} = c 1906 io.close(tls, s) 1907 1908 def conn.drop(m: Maybe<&1, Conn>) -> IO(Unit): 1909 match m: 1910 case None{}: 1911 IO.pure(Unit, Unit{}) 1912 case Some{c}: 1913 conn.close(c) 1914 1915 # ponytail: one idle socket per origin; a second one replaces the first. 1916 def pool.give.of(+key: String, c: Conn, r: Map<&1, Conn> & Maybe<&1, Conn>) -> IO(Pool): 1917 (m, old) = r 1918 do IO: 1919 conn.drop(old) 1920 return Pool{Map.set(&1, Conn, m, key, c)} 1921 1922 def pool.give(p: Pool, +key: String, c: Conn) -> IO(Pool): 1923 Pool{idle} = p 1924 pool.give.of(key, c, Map.pop(&1, Conn, idle, key)) 1925 1926 def pool.close.go(xs: List<&1, Conn>) -> IO(Unit): 1927 match xs: 1928 case Nil{}: 1929 IO.pure(Unit, Unit{}) 1930 case Con{c, t}: 1931 do IO: 1932 conn.close(c) 1933 pool.close.go(t) 1934 1935 def pool.close(p: Pool) -> IO(Unit): 1936 Pool{idle} = p 1937 pool.close.go(Map.values(&1, Conn, idle)) 1938 1939 # RFC 9110 §9.2.2 1940 def pool.idempotent(+method: String) -> Bool: 1941 Bool.or(String.eq(method, "GET"), Bool.or(String.eq(method, "HEAD"), Bool.or(String.eq(method, "OPTIONS"), Bool.or(String.eq(method, "TRACE"), Bool.or(String.eq(method, "PUT"), String.eq(method, "DELETE")))))) 1942 1943 def conn.tls(m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Result<&1, &1, Err, Conn>): 1944 (s, r) = m 1945 match r: 1946 case Fail{(+code, why)}: 1947 do IO>: 1948 Wire.tls.close(s) 1949 return Fail{err.or_late(code, ErrTls{code, why})} 1950 case Done{u}: 1951 IO.pure(Result<&1, &1, Err, Conn>, Done{Conn{True{}, s}}) 1952 1953 # A failed handshake is an error, never plaintext. 1954 def conn.secure(+ms: U32, sni: String, s: Socket, tls: Bool) -> IO(Result<&1, &1, Err, Conn>): 1955 match tls: 1956 case False{}: 1957 IO.pure(Result<&1, &1, Err, Conn>, Done{Conn{False{}, s}}) 1958 case True{}: 1959 do IO>: 1960 hs : Socket & Result<&1, &1, U32 & String, Unit> <- Wire.tls.connect(s, sni, ms) 1961 conn.tls(hs) 1962 1963 def conn.made(+ms: U32, sni: String, tls: Bool, r: Result<&1, &1, U32 & String, Socket>) -> IO(Result<&1, &1, Err, Conn>): 1964 match r: 1965 case Fail{(+code, why)}: 1966 IO.pure(Result<&1, &1, Err, Conn>, Fail{err.or_late(code, ErrConnect{code, why})}) 1967 case Done{s}: 1968 conn.secure(ms, sni, s, tls) 1969 1970 def conn.open(+tls: Bool, +ms: U32, sni: String, ip: String, port: U32) -> IO(Result<&1, &1, Err, Conn>): 1971 do IO>: 1972 c : Result<&1, &1, U32 & String, Socket> <- Wire.connect(ip, port, ms) 1973 conn.made(ms, sni, tls, c) 1974 1975 def conn.ex(c: Conn, +ms: U32, +head: Bool, wire: Bytes.Bytes) -> IO(Out): 1976 Conn{+tls, s} = c 1977 exchange.go(tls, ms, False{}, head, s, wire) 1978 1979 # A socket with bytes past the response is out of step with the server, so it is closed. 1980 def pool.keep.s(p: Pool, +key: String, +tls: Bool, res: Res, s: Socket, clean: Bool) -> IO(Pool & Result<&1, &1, Err, Res>): 1981 match clean: 1982 case True{}: 1983 do IO>: 1984 p2 : Pool <- pool.give(p, key, Conn{tls, s}) 1985 return (p2, Done{res}) 1986 case False{}: 1987 do IO>: 1988 io.close(tls, s) 1989 return (p, Done{res}) 1990 1991 def pool.keep.m(p: Pool, +key: String, +tls: Bool, res: Res, +clean: Bool, sock: Maybe<&1, Socket>) -> IO(Pool & Result<&1, &1, Err, Res>): 1992 match sock: 1993 case None{}: 1994 IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Done{res})) 1995 case Some{s}: 1996 pool.keep.s(p, key, tls, res, s, clean) 1997 1998 def pool.keep(p: Pool, +key: String, +tls: Bool, res: Res, sock: Maybe<&1, Socket>, rest: Bytes.Bytes) -> IO(Pool & Result<&1, &1, Err, Res>): 1999 Bytes.Bytes{+len, buf} = rest 2000 pool.keep.m(p, key, tls, res, U32.is_zero(len), sock) Lines 1-2000 of 3084. More: https://jend.leeeet.dev/read.txt?path=bend-kit-http@0.15.0.4/http.bend&start_line=2001