tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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)))