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