This PR introduces the Server module, an Async HTTP/1.1 server. This contains the same code as #10478, divided into separate pieces to facilitate easier review. The pieces of this feature are: - Core data structures: #12126 - Headers: #12127 - URI: #12128 - Body: #12144 - H1: #12146 - Server: #12151 - Client: --------- Co-authored-by: Rob23oba <152706811+Rob23oba@users.noreply.github.com>
166 lines
6 KiB
Text
166 lines
6 KiB
Text
import Std.Internal.Http.Test.Helpers
|
|
|
|
open Std.Internal.IO Async
|
|
open Std Http Internal Test
|
|
|
|
-- Shared fixtures
|
|
|
|
private def ok200 : String :=
|
|
"HTTP/1.1 200 OK\x0d\nContent-Type: text/plain; charset=utf-8\x0d\nServer: LeanHTTP/1.1\x0d\nConnection: close\x0d\nContent-Length: 2\x0d\n\x0d\nok"
|
|
|
|
-- RFC 9112 §3: Request Line
|
|
|
|
#eval runGroup "RFC 9112 §3: request-line parse failures" do
|
|
check "missing version → 400"
|
|
(raw := "GET /\x0d\nHost: example.com\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "missing URI (double space) → 400"
|
|
(raw := "GET HTTP/1.1\x0d\nHost: example.com\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "extra spaces in request-line → 400"
|
|
(raw := "GET / HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "whitespace-only request-line → 400"
|
|
(raw := " \x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "no spaces in request-line → 400"
|
|
(raw := "GETHTTP/1.1\x0d\nHost: example.com\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "garbage after request-line version → 400"
|
|
(raw := "GET / HTTP/1.1 xxxxxx\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
-- Empty connection: no bytes → silent close, no response
|
|
checkClose "empty connection → silent close"
|
|
(raw := "")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r "")
|
|
|
|
#eval runGroup "RFC 9112 §2.2: leading CRLF before request-line" do
|
|
check "single leading CRLF accepted"
|
|
(raw := "\x0d\nGET / HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r ok200)
|
|
|
|
-- RFC 9112 §9: HTTP version
|
|
|
|
#eval runGroup "RFC 9112 §9: HTTP version" do
|
|
check "HTTP/2.0 → 505"
|
|
(raw := "GET / HTTP/2.0\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r505)
|
|
|
|
-- RFC 9110 §9: Methods
|
|
|
|
#eval runGroup "RFC 9110 §9: method validation" do
|
|
check "unknown method FOOBAR → 400"
|
|
(raw := "FOOBAR / HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "lowercase method → 400"
|
|
(raw := "get / HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "non-ASCII method → 400"
|
|
(raw := "GÉT / HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "very long unrecognized method → 400"
|
|
(raw :=
|
|
let m := String.ofList (List.replicate 20 'G')
|
|
s!"{m} / HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "token method with hyphen (X-CUSTOM) → 400"
|
|
(raw := "X-CUSTOM / HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
-- RFC 9112 §3.2: Request target forms
|
|
|
|
#eval runGroup "RFC 9112 §3.2: request target forms" do
|
|
check "GET authority-form → 400"
|
|
(raw := "GET example.com:443 HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "CONNECT authority-form accepted"
|
|
(raw := "CONNECT example.com:443 HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r ok200)
|
|
|
|
check "CONNECT authority-form port mismatch → 400"
|
|
(raw := "CONNECT example.com:444 HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "GET asterisk-form → 400"
|
|
(raw := "GET * HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "OPTIONS * accepted"
|
|
(raw := "OPTIONS * HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r ok200)
|
|
|
|
check "absolute-form URI accepted"
|
|
(raw := "GET http://example.com/path HTTP/1.1\x0d\nHost: example.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r ok200)
|
|
|
|
-- RFC 9112 §3.3: Early invalid bytes
|
|
|
|
#eval runGroup "RFC 9112 §3: early invalid bytes" do
|
|
checkClose "NUL byte → 400"
|
|
(raw := "\x00")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
checkClose "SP byte → 400"
|
|
(raw := "\x20")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
checkClose "TLS client-hello prefix → 400"
|
|
(raw := "\x16\x03\x01\x00\xa5")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
-- RFC 7230 §5.4: Host header
|
|
|
|
#eval runGroup "RFC 7230 §5.4: Host header" do
|
|
check "missing Host header → 400"
|
|
(raw := "GET / HTTP/1.1\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "empty Host allowed for origin-form"
|
|
(raw := "GET / HTTP/1.1\x0d\nHost: \x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r ok200)
|
|
|
|
check "multiple Host headers → 400"
|
|
(raw := "GET / HTTP/1.1\x0d\nHost: example.com\x0d\nHost: other.com\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r r400)
|
|
|
|
check "absolute-form: URI authority takes precedence over Host"
|
|
(raw := "GET http://good.example/path HTTP/1.1\x0d\nHost: good.example\x0d\nConnection: close\x0d\n\x0d\n")
|
|
(handler := okHandler)
|
|
(expect := fun r => assertExact r ok200)
|