Branch :
quote require HTTP
require HTTP
quote require HTTP.Request
require HTTP.Request
quote require Socket
require Socket
quote require Socket.Buf
require Socket.Buf
quote require Thread
require Thread
quote (server = Socket.listen("127.0.0.1", "57004")) ; void
(server = Socket.listen("127.0.0.1", "57004")) ; void
quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
(client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
quote (server_client = Socket.Buf.accept(server)) ; void
(server_client = Socket.Buf.accept(server)) ; void
# An opaque body is preserved and cannot become the next request.
quote Buf.write_str(client.buf_rw.w,
"POST /json HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: 7\r\n\r\n{\"x\":1}GET /next HTTP/1.1\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"POST /json HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: 7\r\n\r\n{\"x\":1}GET /next HTTP/1.1\r\n\r\n")
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
request.body
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
{request.method, request.url}
# Multipart parsing is bounded and leaves the following request intact.
quote Buf.write_str(client.buf_rw.w,
"POST /multipart HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 67\r\n\r\n--X\r\nContent-Disposition: form-data; name=\"field\"\r\n\r\nvalue\r\n--X--\r\nGET /after-multipart HTTP/1.1\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"POST /multipart HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 67\r\n\r\n--X\r\nContent-Disposition: form-data; name=\"field\"\r\n\r\nvalue\r\n--X--\r\nGET /after-multipart HTTP/1.1\r\n\r\n")
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
request.body
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
{request.method, request.url}
# A body without Content-Type is still consumed and preserved.
quote Buf.write_str(client.buf_rw.w,
"POST /raw HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcGET /after-raw HTTP/1.1\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"POST /raw HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcGET /after-raw HTTP/1.1\r\n\r\n")
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
request.body
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
{request.method, request.url}
# Bodies larger than the 64 KiB socket buffer are read in chunks.
quote (chunk = "a") ; void
(chunk = "a") ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (chunk = chunk + chunk) ; void
(chunk = chunk + chunk) ; void
quote (parser_thread = Thread.new(fn () {
request = HTTP.Request.buf_parse(server_client.buf_rw.r)
Str.size(request.body)
})) ; void
(parser_thread = Thread.new(fn () {
request = HTTP.Request.buf_parse(server_client.buf_rw.r)
Str.size(request.body)
})) ; void
quote Buf.write_str(client.buf_rw.w,
"POST /large HTTP/1.1\r\nContent-Length: 66560\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"POST /large HTTP/1.1\r\nContent-Length: 66560\r\n\r\n")
quote do
i = 0
while i < 65 do
Buf.write_str(client.buf_rw.w, chunk)
i = i + 1
end
end
do
i = 0
while i < 65 do
Buf.write_str(client.buf_rw.w, chunk)
i = i + 1
end
end
quote Thread.delete(parser_thread)
Thread.delete(parser_thread)
quote Buf.write_str(client.buf_rw.w,
"GET /after-large HTTP/1.1\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"GET /after-large HTTP/1.1\r\n\r\n")
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
{request.method, request.url}
# Media type parameters do not disable form decoding.
quote Buf.write_str(client.buf_rw.w,
"POST /form HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nContent-Length: 6\r\n\r\nempty=")
Buf.write_str(client.buf_rw.w,
"POST /form HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nContent-Length: 6\r\n\r\nempty=")
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
request.body
# Identical duplicate lengths are unambiguous and accepted.
quote Buf.write_str(client.buf_rw.w,
"POST /duplicate HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 3\r\n\r\nabc")
Buf.write_str(client.buf_rw.w,
"POST /duplicate HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 3\r\n\r\nabc")
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
request.body
quote Socket.Buf.close(client)
Socket.Buf.close(client)
quote Socket.Buf.close(server_client)
Socket.Buf.close(server_client)
# A malformed multipart body cannot consume bytes beyond Content-Length.
quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
(client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
quote (server_client = Socket.Buf.accept(server)) ; void
(server_client = Socket.Buf.accept(server)) ; void
quote Buf.write_str(client.buf_rw.w,
"POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 10\r\n\r\n--X\r\nabcdeGET /not-consumed HTTP/1.1\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=X\r\nContent-Length: 10\r\n\r\n--X\r\nabcdeGET /not-consumed HTTP/1.1\r\n\r\n")
HTTP.Request.buf_parse(server_client.buf_rw.r)
quote (request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
(request = HTTP.Request.buf_parse(server_client.buf_rw.r)) ; void
{request.method, request.url}
quote Socket.Buf.close(client)
Socket.Buf.close(client)
quote Socket.Buf.close(server_client)
Socket.Buf.close(server_client)
# Conflicting lengths are rejected.
quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
(client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
quote (server_client = Socket.Buf.accept(server)) ; void
(server_client = Socket.Buf.accept(server)) ; void
quote Buf.write_str(client.buf_rw.w,
"POST / HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 4\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"POST / HTTP/1.1\r\nContent-Length: 3\r\nContent-Length: 4\r\n\r\n")
HTTP.Request.buf_parse(server_client.buf_rw.r)
quote Socket.Buf.close(client)
Socket.Buf.close(client)
quote Socket.Buf.close(server_client)
Socket.Buf.close(server_client)
# Unsupported transfer coding is rejected instead of desynchronizing.
quote (client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
(client = Socket.Buf.connect("127.0.0.1", "57004")) ; void
quote (server_client = Socket.Buf.accept(server)) ; void
(server_client = Socket.Buf.accept(server)) ; void
quote Buf.write_str(client.buf_rw.w,
"POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n")
Buf.write_str(client.buf_rw.w,
"POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n")
HTTP.Request.buf_parse(server_client.buf_rw.r)
quote Socket.Buf.close(client)
Socket.Buf.close(client)
quote Socket.Buf.close(server_client)
Socket.Buf.close(server_client)
quote Socket.close(server)
Socket.close(server)