2c0ccdaabea661d7288e7faf58dbbdb9f0823f8929892391d2402200d87993b9.tri (20373B)
1 !import prelude * 2 !import io * 3 !import patterns * 4 !import socket * 5 !import parser * 6 !import native_math as Nat 7 !export * 8 9 -- --------------------------------------------------------------------------- 10 -- Constants 11 -- --------------------------------------------------------------------------- 12 13 maxHeaderBytes = 65536 14 maxBodyBytes = 1048576 15 maxUriBytes = 8192 16 17 crlf = pair 13 (pair 10 t) 18 crlfcrlf = pair 13 (pair 10 (pair 13 (pair 10 t))) 19 20 -- --------------------------------------------------------------------------- 21 -- Response construction 22 -- --------------------------------------------------------------------------- 23 24 statusPhrases = 25 [(pair 200 "OK") 26 (pair 201 "Created") 27 (pair 204 "No Content") 28 (pair 400 "Bad Request") 29 (pair 404 "Not Found") 30 (pair 405 "Method Not Allowed") 31 (pair 431 "Request Header Fields Too Large") 32 (pair 501 "Not Implemented") 33 (pair 505 "HTTP Version Not Supported")] 34 35 lookupStatusPhrase_ self code phrases = 36 lazyList 37 (_ : "Internal Server Error") 38 (h r : 39 lazyBool 40 (_ : snd h) 41 (_ : self code r) 42 (equal? code (fst h))) 43 phrases 44 45 statusPhrase = (code : 46 y lookupStatusPhrase_ code statusPhrases) 47 48 statusLine = (code phrase : 49 append "HTTP/1.1 " (append (showNumber code) (append " " (append phrase "\r\n")))) 50 51 headerLine = (key value : 52 append key (append ": " (append value "\r\n"))) 53 54 buildResponse = (status headers body : 55 append 56 (statusLine status (statusPhrase status)) 57 (append 58 (foldl (acc h : append acc (headerLine (fst h) (snd h))) "" headers) 59 (append "\r\n" body))) 60 61 response = (status contentType body : 62 buildResponse status 63 [(pair "Content-Type" contentType) 64 (pair "Content-Length" (showNumber (length body))) 65 (pair "Connection" "close")] 66 body) 67 68 emptyResponse = (status : 69 buildResponse status 70 [(pair "Content-Length" "0") 71 (pair "Connection" "close")] 72 "") 73 74 okResponse = (body : 75 response 200 "text/plain; charset=utf-8" body) 76 77 textResponse = (body : 78 response 200 "text/plain; charset=utf-8" body) 79 80 jsonResponse = (body : 81 response 200 "application/json" body) 82 83 htmlResponse = (body : 84 response 200 "text/html; charset=utf-8" body) 85 86 createdResponse = (body : 87 response 201 "text/plain; charset=utf-8" body) 88 89 notFoundResponse = ( 90 response 404 "text/plain; charset=utf-8" "Not found\n") 91 92 badRequestResponse = (msg : 93 response 400 "text/plain; charset=utf-8" msg) 94 95 errorResponse = (status msg : 96 response status "text/plain; charset=utf-8" msg) 97 98 headerEndState state h = 99 lazyBool 100 (_ : 101 lazyBool 102 (_ : 3) 103 (_ : 1) 104 (equal? state 2)) 105 (_ : 106 lazyBool 107 (_ : 108 lazyBool 109 (_ : 4) 110 (_ : 2) 111 (equal? state 3)) 112 (_ : 0) 113 (boolAnd? 114 (equal? h 10) 115 (boolOr? (equal? state 1) (equal? state 3)))) 116 (equal? h 13) 117 118 headersOnly_ self bs state acc = 119 lazyList 120 (_ : reverse acc) 121 (h r : 122 let nextAcc = pair h acc in 123 let nextState = headerEndState state h in 124 lazyBool 125 (_ : reverse nextAcc) 126 (_ : self r nextState nextAcc) 127 (equal? nextState 4)) 128 bs 129 130 headersOnly = (response : 131 y headersOnly_ response 0 t) 132 133 responseForMethod = (method resp : 134 lazyBool 135 (_ : headersOnly resp) 136 (_ : resp) 137 (equal? method "HEAD")) 138 139 -- --------------------------------------------------------------------------- 140 -- Header receive / framing 141 -- --------------------------------------------------------------------------- 142 143 recvUntilMax_ = (y (self sock pattern maxBytes acc accLen : 144 onResult_ (recv sock 4096) 145 (err : 146 pure (err 400 acc)) 147 (chunk : 148 lazyBool 149 (_ : pure (err 400 acc)) 150 (_ : 151 let chunkLen = length chunk in 152 let nextLen = add accLen chunkLen in 153 let next = append acc chunk in 154 lazyBool 155 (_ : 156 lazyBool 157 (_ : pure (ok next t)) 158 (_ : self sock pattern maxBytes next nextLen) 159 (contains? pattern next)) 160 (_ : pure (err 431 next)) 161 (lte? nextLen maxBytes)) 162 (emptyList? chunk)))) 163 164 recvUntilMax = (sock pattern maxBytes : 165 recvUntilMax_ sock pattern maxBytes t 0) 166 167 recvUntil = (sock pattern : 168 recvUntilMax sock pattern maxHeaderBytes) 169 170 recvHeaders = (sock : 171 recvUntilMax sock crlfcrlf maxHeaderBytes) 172 173 -- --------------------------------------------------------------------------- 174 -- Request line parsing 175 -- --------------------------------------------------------------------------- 176 177 -- HTTP/1.1 lines require CRLF. A bare CR/LF is an error, never a byte to 178 -- discard. Failure retains the offending suffix and prior consumption; EOF 179 -- cannot masquerade as a successfully parsed empty line. 180 httpLineError code found rest consumed = (parserReplyFail 181 (parserDiagnostic code "CRLF" found t ["http-line"]) 182 rest consumed false) 183 184 httpScanLine [13 10 | rest] reversed = parserReplyOk (reverse reversed) rest true false 185 httpScanLine input{[13 | _]} reversed = httpLineError 2 13 input (not? (emptyList? reversed)) 186 httpScanLine input{[10 | _]} reversed = httpLineError 2 10 input (not? (emptyList? reversed)) 187 httpScanLine [byte | rest] reversed = httpScanLine rest (pair byte reversed) 188 httpScanLine [] reversed = httpLineError 1 t [] (not? (emptyList? reversed)) 189 httpScanLine input reversed = httpLineError 2 input input (not? (emptyList? reversed)) 190 191 httpLineReply input = httpScanLine input [] 192 httpBadRequest = err 400 "Bad Request\n" 193 194 -- Unlike the old pair-only helper, this Result distinguishes a blank CRLF 195 -- line from truncated/malformed input. Both callers propagate the failure. 196 readLineBytes input = (parserMatchReply 197 (_ _ _ _ : httpBadRequest) 198 (line rest _ _ : ok line rest) 199 (httpLineReply input)) 200 201 -- The finite request line is already framed by readLineBytes. Deliberately 202 -- retain the historical three-fields grammar: empty fields are permitted, 203 -- but a third separating space is an error. The parser reply exposes byte 204 -- consumption to new clients, while parseThreeWords preserves HTTP's public 205 -- Result/400 boundary and the original "Bad Request" remainder. 206 httpNonSpace? byte = not? (equal? byte 32) 207 208 httpThreeWordsReply = 209 parserContext "request-line" 210 (parserBind (parserTakeWhile httpNonSpace?) (method : 211 parserThen (parserExpect 32) 212 (parserBind (parserTakeWhile httpNonSpace?) (target : 213 parserThen (parserExpect 32) 214 (parserBind (parserTakeWhile httpNonSpace?) (version : 215 parserMap (_ : pair method (pair target version)) 216 parserEof)))))) 217 218 parseThreeWords = (bs : 219 parserMatchReply 220 (diagnostic rest consumed committed : err 400 "Bad Request\n") 221 (words rest consumed committed : ok words rest) 222 (httpThreeWordsReply bs)) 223 224 parseRequestLine input = (let 225 parseLine ((t t), (line, rest)) = (lazyResult 226 (_ _ : httpBadRequest) 227 (request _ : ok request rest) 228 (parseThreeWords line)) 229 parseLine _ = httpBadRequest 230 in parseLine (readLineBytes input)) 231 232 -- --------------------------------------------------------------------------- 233 -- Header parsing 234 -- --------------------------------------------------------------------------- 235 236 237 boolOr? = (a b : 238 matchBool true b a) 239 240 boolAnd? = (a b : 241 matchBool b false a) 242 243 bit0? = (x : 244 isZero? x) 245 246 bit1? = (x : 247 triage 248 false 249 (a : isZero? a) 250 (_ _ : false) 251 x) 252 253 -- Preserve the old bit predicate's truthiness, even for malformed bit trees. 254 httpAsciiBit bit = matchBool true false (bit1? bit) 255 256 -- Uppercase ASCII is 65..90. For byte inputs the normalized code below is 257 -- the input itself once the high-bit checks pass. Keep original low-bit trees 258 -- in the result: the historical helper also accepts some malformed bit trees, 259 -- and this readability rewrite does not change that policy. 260 toLowerAsciiByte byte{[b0 b1 b2 b3 b4 b5 b6 | rest]} = (let 261 code = [(httpAsciiBit b0) (httpAsciiBit b1) (httpAsciiBit b2) 262 (httpAsciiBit b3) (httpAsciiBit b4) false true] 263 lower ^true = [b0 b1 b2 b3 b4 true true] 264 lower _ = byte 265 in lower (and? (isZero? rest) 266 (and? (bit1? b6) (and? (bit0? b5) 267 (Nat.inRange code 65 90))))) 268 toLowerAsciiByte byte = byte 269 270 -- RFC 9110 token names, OWS, and field-value bytes. obs-text (128..255) 271 -- stays opaque; only the ASCII header name is lowercased. Reject obs-fold. 272 httpTokenByte? byte = (or? (Nat.letter byte) 273 (or? (Nat.digit byte) (any? (equal? byte) "!#$%&'*+-.^_`|~"))) 274 httpOws? byte = or? (equal? byte 32) (equal? byte 9) 275 httpFieldByte? byte = (or? (equal? byte 9) 276 (or? (Nat.printable byte) (Nat.inRange byte 128 255))) 277 httpTrimOws value = reverse (dropWhile httpOws? (reverse (dropWhile httpOws? value))) 278 279 -- Parse exactly one finite line, excluding CRLF. Preserve parser diagnostics 280 -- and consumption, but never accept a valid prefix followed by invalid bytes. 281 httpHeaderLineReply = (parserContext "header-line" 282 (parserBind 283 (parserLabel "header-name" 284 (parserSome (parserSatisfy httpTokenByte?))) 285 (name : parserThen (parserLabel "colon" (parserExpect 58)) 286 (parserMap (value : pair (map toLowerAsciiByte name) 287 (httpTrimOws value)) 288 (parserLabel "header-value" 289 (parserBefore (parserTakeWhile httpFieldByte?) parserEof)))))) 290 291 -- Only an actual empty CRLF line ends the headers. The returned suffix is 292 -- opaque body data, even if it contains CR, LF, NUL or header-looking bytes. 293 httpParseHeaderLines input reversedHeaders = (let 294 parseLine ((t t), ([], rest)) = ok (reverse reversedHeaders) rest 295 parseLine ((t t), (line, rest)) = (let 296 continue ((t t), (header, _)) = httpParseHeaderLines rest (pair header reversedHeaders) 297 continue _ = httpBadRequest 298 in continue (httpHeaderLineReply line)) 299 parseLine _ = httpBadRequest 300 in parseLine (readLineBytes input)) 301 302 parseHeaders input = httpParseHeaderLines input [] 303 304 -- --------------------------------------------------------------------------- 305 -- Content-Length parsing 306 -- --------------------------------------------------------------------------- 307 308 -- The accumulator never exceeds maxBodyBytes. A single checked step can 309 -- reach at most 10 * maxBodyBytes + 9; after that, validate digits without 310 -- constructing a larger number. Trailing junk still means 400, not 413. 311 httpBodyTooLarge = err 413 "Request body too large\n" 312 313 httpLengthOverflow [] = httpBodyTooLarge 314 httpLengthOverflow [byte | rest] = (let 315 continue ^true = httpLengthOverflow rest 316 continue _ = httpBadRequest 317 in continue (Nat.digit byte)) 318 httpLengthOverflow _ = httpBadRequest 319 320 httpLengthDigits [] value = ok (just value) t 321 httpLengthDigits [byte | rest] value = (let 322 digit ^true = (let 323 next = Nat.decimal value (Nat.subtract byte 48) 324 bounded ^true = httpLengthDigits rest next 325 bounded _ = httpLengthOverflow rest 326 in bounded (Nat.lte next maxBodyBytes)) 327 digit _ = httpBadRequest 328 in digit (Nat.digit byte)) 329 httpLengthDigits _ _ = httpBadRequest 330 331 -- Input is a finite canonical byte list, with no OWS: parseHeaders already 332 -- trims field edges. Empty strings, signs, commas and interior whitespace fail. 333 parseContentLengthValue input{[_ | _]} = httpLengthDigits input 0 334 parseContentLengthValue _ = httpBadRequest 335 336 -- Input names are normalized by parseHeaders. Select the sole raw length 337 -- before decoding: duplicate fields and unsupported transfer coding are 400 338 -- regardless of field order or whether a length would otherwise exceed limits. 339 -- This server supports Content-Length framing only, not chunked decoding. 340 httpSelectLength [] t = ok nothing t 341 httpSelectLength [] (t value) = parseContentLengthValue value 342 httpSelectLength [("transfer-encoding", _) | _] _ = httpBadRequest 343 httpSelectLength [("content-length", value) | rest] t = httpSelectLength rest (just value) 344 httpSelectLength [("content-length", _) | _] _ = httpBadRequest 345 httpSelectLength [(_, _) | rest] selected = httpSelectLength rest selected 346 httpSelectLength _ _ = httpBadRequest 347 348 contentLength headers = httpSelectLength headers nothing 349 350 -- --------------------------------------------------------------------------- 351 -- Body reading 352 -- --------------------------------------------------------------------------- 353 354 bodyReadState = (remaining accRev rest : 355 pair remaining (pair accRev rest)) 356 357 bodyReadRemaining = (state : 358 fst state) 359 360 bodyReadAccRev = (state : 361 fst (snd state)) 362 363 bodyReadRest = (state : 364 snd (snd state)) 365 366 takeBodyBytes_ = (self bytes remaining accRev : 367 lazyBool 368 (_ : bodyReadState 0 accRev bytes) 369 (_ : 370 lazyList 371 (_ : bodyReadState remaining accRev t) 372 (h r : 373 self r (pred remaining) (pair h accRev)) 374 bytes) 375 (isZero? remaining)) 376 377 takeBodyBytes = (bytes remaining accRev : 378 y takeBodyBytes_ bytes remaining accRev) 379 380 shiftRight1 n = triage 0 (_ : 0) (_ rest : rest) n 381 382 shiftRight2 n = shiftRight1 (shiftRight1 n) 383 shiftRight4 n = shiftRight2 (shiftRight2 n) 384 shiftRight8 n = shiftRight4 (shiftRight4 n) 385 shiftRight12 n = shiftRight4 (shiftRight8 n) 386 387 shiftRight6 n = shiftRight2 (shiftRight4 n) 388 389 atLeast16? n = not? (isZero? (shiftRight4 n)) 390 atLeast64? n = not? (isZero? (shiftRight6 n)) 391 atLeast256? n = not? (isZero? (shiftRight8 n)) 392 atLeast1024? n = not? (isZero? (shiftRight2 (shiftRight8 n))) 393 atLeast4096? n = not? (isZero? (shiftRight12 n)) 394 395 recvChunkMax4096 remaining = 396 lazyBool 397 (_ : 4096) 398 (_ : 399 lazyBool 400 (_ : 1024) 401 (_ : 402 lazyBool 403 (_ : 256) 404 (_ : 405 lazyBool 406 (_ : 64) 407 (_ : 408 lazyBool 409 (_ : 16) 410 (_ : 1) 411 (atLeast16? remaining)) 412 (atLeast64? remaining)) 413 (atLeast256? remaining)) 414 (atLeast1024? remaining)) 415 (atLeast4096? remaining) 416 417 readBodyRecv = (self sock remaining accRev recvBytes : 418 onResult_ (recv sock recvBytes) 419 (errMsg : 420 pure 421 (err 422 400 423 (append "recv failed while reading body: " errMsg))) 424 (chunk : 425 let state = takeBodyBytes chunk remaining accRev in 426 let nextRemaining = bodyReadRemaining state in 427 let nextAccRev = bodyReadAccRev state in 428 lazyBool 429 (_ : pure (ok (reverse nextAccRev) (bodyReadRest state))) 430 (_ : self sock nextRemaining nextAccRev) 431 (isZero? nextRemaining))) 432 433 readBodyMore_ = (self sock remaining accRev : 434 lazyBool 435 (_ : pure (ok (reverse accRev) t)) 436 (_ : readBodyRecv self sock remaining accRev (recvChunkMax4096 remaining)) 437 (isZero? remaining)) 438 439 readBodyMore = (sock remaining accRev : 440 y readBodyMore_ sock remaining accRev) 441 442 readBodyExact = (sock expected initialBytes : 443 let state = takeBodyBytes initialBytes expected t in 444 let remaining = bodyReadRemaining state in 445 let accRev = bodyReadAccRev state in 446 lazyBool 447 (_ : pure (ok (reverse accRev) (bodyReadRest state))) 448 (_ : readBodyMore sock remaining accRev) 449 (isZero? remaining)) 450 451 validateBodyLength = (expected body rest : 452 let actual = length body in 453 lazyBool 454 (_ : pure (ok body rest)) 455 (_ : 456 pure 457 (err 458 400 459 (append 460 "body length mismatch expected=" 461 (append 462 (showNumber expected) 463 (append 464 " actual=" 465 (showNumber actual)))))) 466 (equal? actual expected)) 467 468 readBody = (sock headers initialBytes : 469 matchResult 470 (status msg : 471 pure (err status msg)) 472 (maybeLen rest : 473 lazyMaybe 474 (_ : pure (ok t initialBytes)) 475 (n : 476 onOk (readBodyExact sock n initialBytes) 477 (body rest : 478 validateBodyLength n body rest)) 479 maybeLen) 480 (contentLength headers)) 481 482 -- --------------------------------------------------------------------------- 483 -- Request validation 484 -- --------------------------------------------------------------------------- 485 486 validMethod? = (method : 487 lazyBool 488 (_ : true) 489 (_ : 490 lazyBool 491 (_ : true) 492 (_ : 493 lazyBool 494 (_ : true) 495 (_ : false) 496 (equal? method "HEAD")) 497 (equal? method "POST")) 498 (equal? method "GET")) 499 500 validVersion? = (version : 501 lazyBool 502 (_ : true) 503 (_ : equal? version "HTTP/1.0") 504 (equal? version "HTTP/1.1")) 505 506 validTarget? = (target : 507 startsWith? "/" target) 508 509 validateRequest = (method target version headers : 510 lazyBool 511 (_ : 512 lazyBool 513 (_ : 514 lazyBool 515 (_ : ok t t) 516 (_ : err 400 "Bad Request\n") 517 (validTarget? target)) 518 (_ : err 505 "HTTP Version Not Supported\n") 519 (validVersion? version)) 520 (_ : err 400 "Bad Request\n") 521 (validMethod? method)) 522 523 -- --------------------------------------------------------------------------- 524 -- 11. Handler pipeline 525 -- --------------------------------------------------------------------------- 526 527 routerMethod = (method : 528 lazyBool 529 (_ : "GET") 530 (_ : method) 531 (equal? method "HEAD")) 532 533 respondAndClose = (sock resp : 534 onOk_ (finally (send sock resp) (closeSocket_ sock)) (_ : 535 pure (ok t t))) 536 537 handleReadableRequest = (router client method target headers rest3 : 538 onResult (readBody client headers rest3) 539 (status msg : 540 respondAndClose client 541 (responseForMethod method 542 (errorResponse status msg))) 543 (body rest : 544 respondAndClose client 545 (responseForMethod method 546 (router (routerMethod method) target headers body)))) 547 548 handleParsedHeaders = (router client method target version rest2 : 549 matchResult 550 (code bad : 551 respondAndClose client (badRequestResponse "Bad Request\n")) 552 (headers rest3 : 553 matchResult 554 (status msg : 555 respondAndClose client 556 (responseForMethod method (errorResponse status msg))) 557 (ignored rest : 558 handleReadableRequest router client method target headers rest3) 559 (validateRequest method target version headers)) 560 (parseHeaders rest2)) 561 562 handleParsedRequest = (router client req rest2 : 563 ((method : 564 ((target : 565 ((version : 566 handleParsedHeaders router client method target version rest2) 567 (snd (snd req)))) 568 (fst (snd req)))) 569 (fst req))) 570 571 httpHandler = (router client peer : 572 onResult_ (recvHeaders client) 573 (status : 574 respondAndClose client 575 (badRequestResponse "Bad Request\n")) 576 (raw : 577 matchResult 578 (code bad : 579 respondAndClose client (badRequestResponse "Bad Request\n")) 580 (req rest2 : 581 handleParsedRequest router client req rest2) 582 (parseRequestLine raw))) 583 584 -- --------------------------------------------------------------------------- 585 -- 12. IO-aware handler pipeline 586 -- --------------------------------------------------------------------------- 587 588 handleReadableRequestIO = (routerIO client method target headers rest3 : 589 onResult (readBody client headers rest3) 590 (status msg : 591 respondAndClose client 592 (responseForMethod method 593 (errorResponse status msg))) 594 (body rest : 595 bind (routerIO (routerMethod method) target headers body) (resp : 596 respondAndClose client (responseForMethod method resp)))) 597 598 handleParsedHeadersIO = (routerIO client method target version rest2 : 599 matchResult 600 (code bad : 601 respondAndClose client (badRequestResponse "Bad Request\n")) 602 (headers rest3 : 603 matchResult 604 (status msg : 605 respondAndClose client 606 (responseForMethod method (errorResponse status msg))) 607 (ignored rest : 608 handleReadableRequestIO routerIO client method target headers rest3) 609 (validateRequest method target version headers)) 610 (parseHeaders rest2)) 611 612 handleParsedRequestIO = (routerIO client req rest2 : 613 ((method : 614 ((target : 615 ((version : 616 handleParsedHeadersIO routerIO client method target version rest2) 617 (snd (snd req)))) 618 (fst (snd req)))) 619 (fst req))) 620 621 httpHandlerIO = (routerIO client peer : 622 onResult_ (recvHeaders client) 623 (status : 624 respondAndClose client 625 (badRequestResponse "Bad Request\n")) 626 (raw : 627 matchResult 628 (code bad : 629 respondAndClose client (badRequestResponse "Bad Request\n")) 630 (req rest2 : 631 handleParsedRequestIO routerIO client req rest2) 632 (parseRequestLine raw)))