Add engine tests

This commit is contained in:
2025-05-12 22:23:33 +02:00
parent 2d4f062917
commit f725e63fb0
10 changed files with 638 additions and 182 deletions
+122 -103
View File
@@ -1,4 +1,4 @@
#include "http.h"
#include "tinyhttp.h"
#include <stdio.h>
#include <stdarg.h>
@@ -936,6 +936,14 @@ static int parse_request(Scanner *s, HTTP_Request *req)
return 1;
}
static int find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
{
for (int i = 0; i < num_headers; i++)
if (http_streqcase(name, headers[i].name))
return i;
return -1;
}
static int parse_response(Scanner *s, HTTP_Response *res)
{
if (!contains_head(s->src + s->cur, s->len - s->cur))
@@ -992,14 +1000,16 @@ static int parse_response(Scanner *s, HTTP_Response *res)
return num_headers;
res->num_headers = num_headers;
unsigned long long content_length;
for (int i = 0; i < num_headers; i++)
if (http_streqcase(res->headers[i].name, HTTP_STR("Content-Length"))) {
if (parse_content_length(res->headers[i].value.ptr, res->headers[i].value.len, &content_length) < 0)
return -1;
break;
}
int content_length_index = find_header(res->headers, res->num_headers, HTTP_STR("Content-Length"));
if (content_length_index == -1) {
res->body.ptr = NULL;
res->body.len = 0;
return 1;
}
ASSERT(0);
return -1;
/*
if (content_length > 1<<20) {
// TODO
}
@@ -1010,7 +1020,10 @@ static int parse_response(Scanner *s, HTTP_Response *res)
res->body.ptr = s->src + s->cur;
res->body.len = content_length;
}
return 1;
*/
}
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
@@ -1380,6 +1393,9 @@ static void
byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off,
char *src, unsigned int len)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
// Check that the offset is in range
ASSERT(off >= queue->curs && off - queue->curs < queue->used);
@@ -1394,6 +1410,9 @@ byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off,
static void
byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
unsigned long long num = (queue->curs + queue->used) - offset;
ASSERT(num <= queue->used);
@@ -1514,21 +1533,16 @@ char *http_engine_recvbuf(HTTP_Engine *eng, int *cap)
byte_queue_write_setmincap(&eng->input, 1<<9);
if (byte_queue_error(&eng->input)) {
*cap = 0;
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
else
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
return NULL;
}
return byte_queue_write_buf(&eng->input, cap);
}
static int find_header(HTTP_Request *req, HTTP_String name)
{
for (int i = 0; i < req->num_headers; i++)
if (http_streqcase(name, req->headers[i].name))
return i;
return -1;
}
static int
should_keep_alive(HTTP_Engine *eng)
{
@@ -1553,7 +1567,7 @@ should_keep_alive(HTTP_Engine *eng)
// TODO: This assumes "Connection" can only hold a single token,
// but this is not true.
int i = find_header(req, HTTP_STR("Connection"));
int i = find_header(req->headers, req->num_headers, HTTP_STR("Connection"));
if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close")))
return 0;
@@ -1564,7 +1578,7 @@ static void process_incoming_request(HTTP_Engine *eng)
{
ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK
|| eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR);
char *src;
@@ -1695,6 +1709,8 @@ void http_engine_url(HTTP_Engine *eng, HTTP_Method method, char *url, int minor)
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL)
return;
eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients
int len = strlen(url);
HTTP_URL parsed_url;
@@ -1704,44 +1720,33 @@ void http_engine_url(HTTP_Engine *eng, HTTP_Method method, char *url, int minor)
return;
}
char *method_str = "???";
HTTP_String method_and_space = HTTP_STR("???");
switch (method) {
case HTTP_METHOD_GET : method_str = "GET"; break;
case HTTP_METHOD_HEAD : method_str = "HEAD"; break;
case HTTP_METHOD_POST : method_str = "POST"; break;
case HTTP_METHOD_PUT : method_str = "PUT"; break;
case HTTP_METHOD_DELETE : method_str = "DELETE"; break;
case HTTP_METHOD_CONNECT: method_str = "CONNECT"; break;
case HTTP_METHOD_OPTIONS: method_str = "OPTIONS"; break;
case HTTP_METHOD_TRACE : method_str = "TRACE"; break;
case HTTP_METHOD_PATCH : method_str = "PATCH"; break;
case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break;
case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break;
case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break;
case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break;
case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break;
case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break;
case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break;
case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break;
case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break;
}
HTTP_String path = parsed_url.path;
if (path.len == 0)
path = HTTP_STR("/");
byte_queue_write_fmt(&eng->output,
"%s %.*s%.*s HTTP/1.%d\r\n",
method_str,
path.len,
path.ptr,
parsed_url.query.len,
parsed_url.query.ptr,
minor
);
byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len);
byte_queue_write(&eng->output, path.ptr, path.len);
byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len);
byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1);
byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len);
if (parsed_url.authority.port > 0)
byte_queue_write_fmt(&eng->output,
"Host: %.*s:%d\r\n",
parsed_url.authority.host.text.len,
parsed_url.authority.host.text.ptr,
parsed_url.authority.port);
else
byte_queue_write_fmt(&eng->output,
"Host: %.*s\r\n",
parsed_url.authority.host.text.len,
parsed_url.authority.host.text.ptr);
byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port);
byte_queue_write(&eng->output, "\r\n", 2);
eng->keepalive = 1; // TODO
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER;
}
@@ -1831,6 +1836,8 @@ void http_engine_header(HTTP_Engine *eng, const char *src, int len)
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0)
return;
if (len < 0) len = strlen(src);
// TODO: Check that the header is valid
byte_queue_write(&eng->output, src, len);
@@ -1857,14 +1864,10 @@ void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...)
}
static void
append_special_headers(HTTP_Engine *eng)
complete_message_head(HTTP_Engine *eng)
{
ASSERT((eng->state & HTTP_ENGINE_STATEBIT_CLIENT) == 0);
if (eng->keepalive)
byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1);
else
byte_queue_write(&eng->output, "Connection: Close\r\n", -1);
if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1);
else byte_queue_write(&eng->output, "Connection: Close\r\n", -1);
byte_queue_write(&eng->output, "Content-Length: ", -1);
eng->content_length_value_offset = byte_queue_offset(&eng->output);
@@ -1874,9 +1877,37 @@ append_special_headers(HTTP_Engine *eng)
eng->content_length_offset = byte_queue_offset(&eng->output);
}
static void complete_message_body(HTTP_Engine *eng)
{
unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset);
if (content_length > UINT32_MAX) {
// TODO
}
char tmp[10];
tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000;
tmp[1] = '0' + content_length / 100000000; content_length %= 100000000;
tmp[2] = '0' + content_length / 10000000; content_length %= 10000000;
tmp[3] = '0' + content_length / 1000000; content_length %= 1000000;
tmp[4] = '0' + content_length / 100000; content_length %= 100000;
tmp[5] = '0' + content_length / 10000; content_length %= 10000;
tmp[6] = '0' + content_length / 1000; content_length %= 1000;
tmp[7] = '0' + content_length / 100; content_length %= 100;
tmp[8] = '0' + content_length / 10; content_length %= 10;
tmp[9] = '0' + content_length;
int i = 0;
while (i < 9 && tmp[i] == '0')
i++;
byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i);
}
void http_engine_body(HTTP_Engine *eng, void *src, int len)
{
ASSERT(len >= 0);
if (len < 0) len = strlen(src);
http_engine_bodycap(eng, len);
int cap;
@@ -1892,15 +1923,15 @@ static void ensure_body_entered(HTTP_Engine *eng)
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
byte_queue_write(&eng->output, "\r\n", 2);
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY;
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
}
} else {
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
append_special_headers(eng);
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY;
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
}
}
}
@@ -1908,8 +1939,8 @@ static void ensure_body_entered(HTTP_Engine *eng)
void http_engine_bodycap(HTTP_Engine *eng, int mincap)
{
ensure_body_entered(eng);
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY)
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
return;
byte_queue_write_setmincap(&eng->output, mincap);
@@ -1918,21 +1949,32 @@ void http_engine_bodycap(HTTP_Engine *eng, int mincap)
char *http_engine_bodybuf(HTTP_Engine *eng, int *cap)
{
ensure_body_entered(eng);
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY) {
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) {
*cap = 0;
return NULL;
}
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK;
else
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK;
return byte_queue_write_buf(&eng->output, cap);
}
void http_engine_bodyack(HTTP_Engine *eng, int num)
{
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY)
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK &&
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK)
return;
byte_queue_write_ack(&eng->output, num);
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
else
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
}
void http_engine_done(HTTP_Engine *eng)
@@ -1948,20 +1990,21 @@ void http_engine_done(HTTP_Engine *eng)
}
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
byte_queue_write(&eng->output, "\r\n", 2);
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY;
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
}
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY) {
// TODO
}
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF)
complete_message_body(eng);
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) {
// TODO
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
return;
}
if (byte_queue_error(&eng->output)) {
// TODO
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
return;
}
eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF;
@@ -1969,37 +2012,12 @@ void http_engine_done(HTTP_Engine *eng)
} else {
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
append_special_headers(eng);
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY;
complete_message_head(eng);
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
}
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY) {
unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset);
if (content_length > UINT32_MAX) {
// TODO
}
char tmp[10];
tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000;
tmp[1] = '0' + content_length / 100000000; content_length %= 100000000;
tmp[2] = '0' + content_length / 10000000; content_length %= 10000000;
tmp[3] = '0' + content_length / 1000000; content_length %= 1000000;
tmp[4] = '0' + content_length / 100000; content_length %= 100000;
tmp[5] = '0' + content_length / 10000; content_length %= 10000;
tmp[6] = '0' + content_length / 1000; content_length %= 1000;
tmp[7] = '0' + content_length / 100; content_length %= 100;
tmp[8] = '0' + content_length / 10; content_length %= 10;
tmp[9] = '0' + content_length;
int i = 0;
while (i < 9 && tmp[i] == '0')
i++;
byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i);
}
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
complete_message_body(eng);
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) {
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
@@ -2027,6 +2045,7 @@ void http_engine_undo(HTTP_Engine *eng)
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0)
return;
byte_queue_write_ack(&eng->output, 0);
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)