commit ce376eaf637f3879fa748820e369d90624d7aa8b Author: eleos Date: Mon Apr 27 19:24:31 2026 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a2cdf2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.clangd +.kateproject +__pycache__/ + +mcp_test.py +test + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..42f1f5d --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/bash + +CC=gcc +CFLAGS="$(pkg-config --cflags --libs libcurl) -std=c99 -Wall" +LDFLAGS="$(pkg-config --libs libcurl) -pthread -I./inlcude" +C_FILES="rc/strcmp.c src/http.c src/main.c s" +#gcc -L "$LIBP" -lcurl -o mcp_client src/main.c && ./mcp_client + +$CC src/*.c -o main $LDFLAGS && ./main + diff --git a/example.txt b/example.txt new file mode 100644 index 0000000..fd58925 --- /dev/null +++ b/example.txt @@ -0,0 +1,8 @@ +POST /mcp HTTP/1.1" +"Host: localhost:8080" +"User-Agent: my-mcp-client:0.1.0" +"Accept: application/json, text/event-stream" +"Content-Type: application/json" +"Content-Length: 216" + +{\"jsonrpc\": \"2.0\",\"id\": 1,\"method\": \"initialize\",\"params\": {\"protocolVersion\": \"2025-03-26\",\"capabilities\": {\"roots\": {\"listChanged\": true},\"sampling\": {}},\"clientInfo\": {\"name\": \"ExampleClient\",\"version\": \"1.0.0\"}}} diff --git a/http2.c b/http2.c new file mode 100644 index 0000000..baa7e49 --- /dev/null +++ b/http2.c @@ -0,0 +1,718 @@ +#include "http2.h" + +#include +#include +#include +#include +#include +#include + +static const char *HTTP2_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; +static const size_t HTTP2_FRAME_HEADER_SIZE = 9; + +int http2_init(void) +{ + return 0; +} + +static int http2_buffer_init(HTTP2_Buffer *buf, size_t initial_capacity) +{ + if (buf == NULL) return -1; + buf->data = malloc(initial_capacity); + if (buf->data == NULL) return -1; + buf->size = 0; + buf->capacity = initial_capacity; + return 0; +} + +static int http2_buffer_grow(HTTP2_Buffer *buf, size_t needed) +{ + if (buf == NULL || buf->data == NULL) return -1; + size_t new_cap = buf->capacity * 2; + while (new_cap < buf->size + needed) { + new_cap *= 2; + } + uint8_t *new_data = realloc(buf->data, new_cap); + if (new_data == NULL) return -1; + buf->data = new_data; + buf->capacity = new_cap; + return 0; +} + +static void http2_buffer_free(HTTP2_Buffer *buf) +{ + if (buf == NULL) return; + free(buf->data); + buf->data = NULL; + buf->size = 0; + buf->capacity = 0; +} + +HTTP2_Connection *http2_connection_new(void) +{ + HTTP2_Connection *conn = malloc(sizeof(HTTP2_Connection)); + if (conn == NULL) return NULL; + memset(conn, 0, sizeof(*conn)); + + if (http2_buffer_init(&conn->read_buffer, 4096) != 0) { + free(conn); + return NULL; + } + if (http2_buffer_init(&conn->write_buffer, 4096) != 0) { + http2_buffer_free(&conn->read_buffer); + free(conn); + return NULL; + } + if (http2_hpack_encode_init(&conn->encoder, HTTP2_DEFAULT_HEADER_TABLE_SIZE) != 0) { + http2_buffer_free(&conn->read_buffer); + http2_buffer_free(&conn->write_buffer); + free(conn); + return NULL; + } + if (http2_hpack_decode_init(&conn->decoder, HTTP2_DEFAULT_HEADER_TABLE_SIZE) != 0) { + http2_hpack_encode_free(&conn->encoder); + http2_buffer_free(&conn->read_buffer); + http2_buffer_free(&conn->write_buffer); + free(conn); + return NULL; + } + + conn->local_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE; + conn->local_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1; + conn->local_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS; + conn->local_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE; + conn->local_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE; + conn->local_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535; + + conn->remote_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE; + conn->remote_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1; + conn->remote_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS; + conn->remote_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE; + conn->remote_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE; + conn->remote_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535; + + conn->next_stream_id = 1; + conn->local_window_size = HTTP2_INITIAL_WINDOW_SIZE; + conn->remote_window_size = HTTP2_INITIAL_WINDOW_SIZE; + conn->remote_concurrent_streams = HTTP2_MAX_CONCURRENT_STREAMS; + conn->last_stream_id = 0; + + return conn; +} + +int http2_connection_init(HTTP2_Connection *conn, int sockfd, int is_server) +{ + if (conn == NULL) return -1; + + memset(conn, 0, sizeof(*conn)); + conn->sockfd = sockfd; + conn->is_server = is_server; + conn->next_stream_id = is_server ? 2 : 1; + conn->local_window_size = HTTP2_INITIAL_WINDOW_SIZE; + conn->remote_window_size = HTTP2_INITIAL_WINDOW_SIZE; + conn->remote_concurrent_streams = HTTP2_MAX_CONCURRENT_STREAMS; + + conn->local_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE; + conn->local_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1; + conn->local_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS; + conn->local_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE; + conn->local_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE; + conn->local_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535; + + conn->remote_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE; + conn->remote_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1; + conn->remote_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS; + conn->remote_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE; + conn->remote_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE; + conn->remote_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535; + + if (http2_buffer_init(&conn->read_buffer, 4096) != 0) return -1; + if (http2_buffer_init(&conn->write_buffer, 4096) != 0) { + http2_buffer_free(&conn->read_buffer); + return -1; + } + + return 0; +} + +void http2_connection_free(HTTP2_Connection *conn) +{ + if (conn == NULL) return; + http2_buffer_free(&conn->read_buffer); + http2_buffer_free(&conn->write_buffer); + free(conn); +} + +void http2_connection_close(HTTP2_Connection *conn) +{ + if (conn == NULL) return; + if (conn->sockfd >= 0) { + close(conn->sockfd); + conn->sockfd = -1; + } +} + +int http2_send_preface(HTTP2_Connection *conn) +{ + if (conn == NULL || conn->sockfd < 0) return -1; + + if (send(conn->sockfd, HTTP2_CONNECTION_PREFACE, strlen(HTTP2_CONNECTION_PREFACE), 0) < 0) { + perror("Failed to send HTTP/2 connection preface"); + return -1; + } + + uint32_t settings[12] = { + HTTP2_SETTINGS_HEADER_TABLE_SIZE, 4096, + HTTP2_SETTINGS_ENABLE_PUSH, 1, + HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100, + HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535, + HTTP2_SETTINGS_MAX_FRAME_SIZE, 16384, + HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, 65535 + }; + http2_encode_settings(conn, settings, 6); + + return 0; +} + +int http2_recv_preface(HTTP2_Connection *conn) +{ + if (conn == NULL || conn->sockfd < 0) return -1; + + char preface[24] = {0}; + ssize_t received = recv(conn->sockfd, preface, sizeof(preface) - 1, MSG_PEEK); + if (received < 0) { + perror("Failed to recv HTTP/2 connection preface"); + return -1; + } + + if ((size_t)received < strlen(HTTP2_CONNECTION_PREFACE)) { + received = recv(conn->sockfd, preface, strlen(HTTP2_CONNECTION_PREFACE), 0); + if (received < 0) { + perror("Failed to recv HTTP/2 connection preface"); + return -1; + } + if (strncmp(preface, HTTP2_CONNECTION_PREFACE, strlen(HTTP2_CONNECTION_PREFACE)) != 0) { + fprintf(stderr, "Invalid HTTP/2 connection preface\n"); + return -1; + } + } + + return 0; +} + +int http2_encode_frame(HTTP2_Frame *frame, HTTP2_Frame_Type type, uint32_t stream_id, uint8_t flags, const uint8_t *payload, size_t payload_len) +{ + if (frame == NULL || payload_len > HTTP2_MAX_FRAME_SIZE) return -1; + + frame->type = type; + frame->flags = flags; + frame->stream_id = stream_id; + frame->length = (uint32_t)payload_len; + + if (payload != NULL && payload_len > 0) { + memcpy(frame->payload, payload, payload_len); + } + + return 0; +} + +static size_t http2_format_frame(const HTTP2_Frame *frame, uint8_t *out) +{ + if (frame == NULL || out == NULL) return 0; + + out[0] = (uint8_t)((frame->length >> 16) & 0xFF); + out[1] = (uint8_t)((frame->length >> 8) & 0xFF); + out[2] = (uint8_t)(frame->length & 0xFF); + out[3] = (uint8_t)frame->type; + out[4] = frame->flags; + + uint32_t stream_id = frame->stream_id; + out[5] = (uint8_t)((stream_id >> 24) & 0xFF); + out[6] = (uint8_t)((stream_id >> 16) & 0xFF); + out[7] = (uint8_t)((stream_id >> 8) & 0xFF); + out[8] = (uint8_t)(stream_id & 0xFF); + + if (frame->length > 0) { + memcpy(out + HTTP2_FRAME_HEADER_SIZE, frame->payload, frame->length); + } + + return HTTP2_FRAME_HEADER_SIZE + frame->length; +} + +int http2_decode_frame(HTTP2_Frame *frame, const uint8_t *data, size_t len) +{ + if (frame == NULL || data == NULL || len < HTTP2_FRAME_HEADER_SIZE) return -1; + + frame->length = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | (uint32_t)data[2]; + frame->type = (HTTP2_Frame_Type)data[3]; + frame->flags = data[4]; + frame->stream_id = ((uint32_t)data[5] << 24) | ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 8) | (uint32_t)data[8]; + + if (frame->length > 0 && len >= HTTP2_FRAME_HEADER_SIZE + frame->length) { + memcpy(frame->payload, data + HTTP2_FRAME_HEADER_SIZE, frame->length); + } else if (frame->length > 0) { + return -1; + } + + return 0; +} + +int http2_recv_frame(HTTP2_Connection *conn, HTTP2_Frame *frame) +{ + if (conn == NULL || frame == NULL || conn->sockfd < 0) return -1; + + uint8_t header[HTTP2_FRAME_HEADER_SIZE]; + ssize_t received = recv(conn->sockfd, header, HTTP2_FRAME_HEADER_SIZE, 0); + if (received < 0) { + perror("Failed to recv HTTP/2 frame header"); + return -1; + } + if ((size_t)received < HTTP2_FRAME_HEADER_SIZE) { + return -1; + } + + if (http2_decode_frame(frame, header, HTTP2_FRAME_HEADER_SIZE) != 0) return -1; + + if (frame->length > 0) { + size_t total_len = HTTP2_FRAME_HEADER_SIZE + frame->length; + if (conn->read_buffer.capacity < total_len) { + if (http2_buffer_grow(&conn->read_buffer, total_len) != 0) return -1; + } + + received = recv(conn->sockfd, conn->read_buffer.data, frame->length, 0); + if (received < 0) { + perror("Failed to recv HTTP/2 frame payload"); + return -1; + } + + memcpy(frame->payload, conn->read_buffer.data, received); + frame->length = (uint32_t)received; + } + + return 0; +} + +int http2_send_frame(HTTP2_Connection *conn, HTTP2_Frame *frame) +{ + if (conn == NULL || frame == NULL || conn->sockfd < 0) return -1; + + size_t total_len = HTTP2_FRAME_HEADER_SIZE + frame->length; + if (conn->write_buffer.capacity < total_len) { + if (http2_buffer_grow(&conn->write_buffer, total_len) != 0) return -1; + } + + size_t written = http2_format_frame(frame, conn->write_buffer.data); + ssize_t sent = send(conn->sockfd, conn->write_buffer.data, written, 0); + if (sent < 0) { + perror("Failed to send HTTP/2 frame"); + return -1; + } + + return 0; +} + +int http2_encode_settings(HTTP2_Connection *conn, uint32_t *settings, size_t count) +{ + if (conn == NULL || settings == NULL) return -1; + + uint8_t payload[256] = {0}; + size_t payload_len = 0; + + for (size_t i = 0; i < count; i++) { + uint16_t id = (uint16_t)(settings[i * 2]); + uint32_t value = settings[i * 2 + 1]; + + payload[payload_len++] = (uint8_t)((id >> 8) & 0xFF); + payload[payload_len++] = (uint8_t)(id & 0xFF); + payload[payload_len++] = (uint8_t)((value >> 24) & 0xFF); + payload[payload_len++] = (uint8_t)((value >> 16) & 0xFF); + payload[payload_len++] = (uint8_t)((value >> 8) & 0xFF); + payload[payload_len++] = (uint8_t)(value & 0xFF); + + if (id > 0 && id <= HTTP2_SETTINGS_MAX_PARAM_ID) { + conn->local_settings[id] = value; + } + } + + HTTP2_Frame frame = {0}; + http2_encode_frame(&frame, HTTP2_FRAME_SETTINGS, 0, 0, payload, payload_len); + + return http2_send_frame(conn, &frame); +} + +int http2_decode_settings(const HTTP2_Frame *frame, uint32_t *settings, size_t *count) +{ + if (frame == NULL || settings == NULL || count == NULL) return -1; + if (frame->type != HTTP2_FRAME_SETTINGS) return -1; + + *count = 0; + size_t i = 0; + while (i < frame->length) { + if (i + 5 >= frame->length) break; + + uint16_t id = ((uint16_t)frame->payload[i] << 8) | frame->payload[i + 1]; + uint32_t value = ((uint32_t)frame->payload[i + 2] << 24) | + ((uint32_t)frame->payload[i + 3] << 16) | + ((uint32_t)frame->payload[i + 4] << 8) | + frame->payload[i + 5]; + + settings[(*count) * 2] = id; + settings[(*count) * 2 + 1] = value; + (*count)++; + i += 6; + } + + return 0; +} + +int http2_encode_priority(HTTP2_Priority_Spec *prio, uint8_t *out, size_t *out_len) +{ + if (prio == NULL || out == NULL || out_len == NULL) return -1; + + uint8_t *p = out; + *p++ = (uint8_t)((prio->stream_dependency >> 24) & 0xFF); + *p++ = (uint8_t)((prio->stream_dependency >> 16) & 0xFF); + *p++ = (uint8_t)((prio->stream_dependency >> 8) & 0xFF); + *p++ = (uint8_t)(prio->stream_dependency & 0xFF); + *p++ = (prio->exclusive << 7) | prio->weight; + *out_len = 5; + + return 0; +} + +int http2_decode_priority(const uint8_t *data, size_t len, HTTP2_Priority_Spec *prio) +{ + if (data == NULL || prio == NULL || len < 5) return -1; + + prio->stream_dependency = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | + ((uint32_t)data[2] << 8) | (uint32_t)data[3]; + prio->exclusive = (data[4] >> 7) & 0x1; + prio->weight = (data[4] & 0xFF) + 1; + + return 0; +} + +int http2_hpack_encode_init(HTTP2_Encoder_Context *ctx, uint32_t max_size) +{ + if (ctx == NULL) return -1; + + memset(ctx, 0, sizeof(*ctx)); + ctx->max_size = max_size; + ctx->current_size = 0; + ctx->dynamic_table.cap = 128; + ctx->dynamic_table.data = malloc(ctx->dynamic_table.cap); + if (ctx->dynamic_table.data == NULL) return -1; + ctx->dynamic_table.len = 0; + + return 0; +} + +static uint32_t http2_hpack_table_size(uint32_t entries) +{ + return 32 + entries * 32; +} + +static int http2_hpack_evict(HTTP2_Encoder_Context *ctx, uint32_t amount) +{ + if (ctx == NULL) return -1; + + while (ctx->current_size + amount > ctx->max_size && ctx->dynamic_table.len > 0) { + ctx->current_size -= 32; + ctx->dynamic_table.len--; + } + + return 0; +} + +static int http2_hpack_encode_indexed(uint8_t idx, uint8_t *out, size_t *out_len) +{ + if (out == NULL || out_len == NULL) return -1; + + if (idx < 61) { + out[0] = 0x80 | idx; + *out_len = 1; + } else { + out[0] = 0x80 | 0x3F; + out[1] = (uint8_t)(idx - 60); + *out_len = 2; + } + + return 0; +} + +static int http2_hpack_encode_literal(const uint8_t *name, size_t name_len, const uint8_t *value, size_t value_len, uint8_t idx_mod, uint8_t *out, size_t *out_len) +{ + if (name == NULL || value == NULL || out == NULL || out_len == NULL) return -1; + + size_t pos = 0; + out[pos++] = idx_mod; + + if (name_len < 61) { + out[pos++] = (uint8_t)name_len; + } else { + out[pos++] = 0x3F; + out[pos++] = (uint8_t)(name_len - 60); + } + memcpy(out + pos, name, name_len); + pos += name_len; + + if (value_len < 61) { + out[pos++] = (uint8_t)value_len; + } else { + out[pos++] = 0x3F; + out[pos++] = (uint8_t)(value_len - 60); + } + memcpy(out + pos, value, value_len); + pos += value_len; + + *out_len = pos; + return 0; +} + +int http2_hpack_encode(HTTP2_Encoder_Context *ctx, const char **headers, size_t headers_len, uint8_t *out, size_t *out_len) +{ + if (ctx == NULL || headers == NULL || out == NULL || out_len == NULL) return -1; + + size_t pos = 0; + for (size_t i = 0; i < headers_len; i += 2) { + const char *name = headers[i]; + const char *value = headers[i + 1]; + size_t name_len = strlen(name); + size_t value_len = strlen(value); + + if (http2_hpack_encode_literal((const uint8_t *)name, name_len, (const uint8_t *)value, value_len, 0x40, out + pos, &pos) != 0) { + continue; + } + } + + *out_len = pos; + return 0; +} + +int http2_hpack_decode_init(HTTP2_Decoder_Context *ctx, uint32_t max_size) +{ + if (ctx == NULL) return -1; + + memset(ctx, 0, sizeof(*ctx)); + ctx->max_size = max_size; + ctx->current_size = 0; + ctx->dynamic_table.cap = 128; + ctx->dynamic_table.data = malloc(ctx->dynamic_table.cap); + if (ctx->dynamic_table.data == NULL) return -1; + ctx->dynamic_table.len = 0; + ctx->mirror = 0; + + return 0; +} + +void http2_hpack_encode_free(HTTP2_Encoder_Context *ctx) +{ + if (ctx == NULL) return; + free(ctx->dynamic_table.data); + ctx->dynamic_table.data = NULL; + ctx->dynamic_table.len = 0; +} + +void http2_hpack_decode_free(HTTP2_Decoder_Context *ctx) +{ + if (ctx == NULL) return; + free(ctx->dynamic_table.data); + ctx->dynamic_table.data = NULL; + ctx->dynamic_table.len = 0; +} + +int http2_hpack_decode(HTTP2_Decoder_Context *ctx, const uint8_t *data, size_t len, char **headers, size_t *headers_len) +{ + if (ctx == NULL || data == NULL || headers == NULL || headers_len == NULL) return -1; + + *headers_len = 0; + size_t pos = 0; + + while (pos < len) { + uint8_t byte = data[pos]; + + if ((byte & 0x80) != 0) { + pos++; + continue; + } + + if ((byte & 0x40) != 0) { + pos++; + continue; + } + + pos++; + } + + return 0; +} + +static int http2_static_table_index(const char *name, size_t name_len) +{ + static const char *names[] = { + ":authority", ":method", ":method", ":path", ":path", ":scheme", ":status", + ":status", ":status", ":status", ":status", ":status", ":status", ":status", ":status", + "accept-encoding", "accept-language", "accept-ranges", "age", "allow", "authorization", + "cache-control", "content-encoding", "content-language", "content-length", + "content-location", "content-range", "content-type", "cookie", "date", "etag", + "expect", "expires", "from", "host", "if-match", "if-modified-since", + "if-none-match", "if-range", "if-unmodified-since", "last-modified", "link", + "location", "max-forwards", "proxy-authenticate", "proxy-authorization", "range", + "referer", "refresh", "retry-after", "server", "set-cookie", "strict-transport-security", + "transfer-encoding", "user-agent", "vary", "via", "www-authenticate" + }; + + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + if (strncmp(name, names[i], name_len) == 0) { + return (int)(i + 1); + } + } + + return 0; +} + +int http2_encode_headers(HTTP2_Connection *conn, const char **headers, size_t headers_len, uint8_t *out, size_t *out_len) +{ + if (conn == NULL || headers == NULL || out == NULL || out_len == NULL) return -1; + + return http2_hpack_encode(&conn->encoder, headers, headers_len, out, out_len); +} + +int http2_decode_headers(HTTP2_Connection *conn, const uint8_t *data, size_t len, char **headers, size_t *headers_len) +{ + if (conn == NULL || data == NULL || headers == NULL || headers_len == NULL) return -1; + + return http2_hpack_decode(&conn->decoder, data, len, headers, headers_len); +} + +int http2_encode_priority_frame(uint32_t stream_id, HTTP2_Priority_Spec *prio, uint8_t *out, size_t *out_len) +{ + if (prio == NULL || out == NULL || out_len == NULL) return -1; + if (stream_id == 0 || (stream_id & 1) == 0) return -1; + + HTTP2_Frame frame = {0}; + http2_encode_priority(prio, frame.payload, out_len); + http2_encode_frame(&frame, HTTP2_FRAME_PRIORITY, stream_id, 0, frame.payload, *out_len); + + return 0; +} + +int http2_encode_rst_stream(uint32_t stream_id, HTTP2_Error_Code error, uint8_t *out, size_t *out_len) +{ + if (out == NULL || out_len == NULL) return -1; + if (stream_id == 0 || (stream_id & 1) == 0) return -1; + + out[0] = (uint8_t)((error >> 24) & 0xFF); + out[1] = (uint8_t)((error >> 16) & 0xFF); + out[2] = (uint8_t)((error >> 8) & 0xFF); + out[3] = (uint8_t)(error & 0xFF); + *out_len = 4; + + return 0; +} + +int http2_encode_ping(HTTP2_Connection *conn, uint8_t *data, uint8_t flags, uint8_t *out, size_t *out_len) +{ + if (conn == NULL || data == NULL || out == NULL || out_len == NULL) return -1; + + HTTP2_Frame frame = {0}; + memcpy(frame.payload, data, 8); + http2_encode_frame(&frame, HTTP2_FRAME_PING, 0, flags, frame.payload, 8); + *out_len = HTTP2_FRAME_HEADER_SIZE + 8; + + return 0; +} + +int http2_encode_goaway(uint32_t last_stream_id, HTTP2_Error_Code error, const uint8_t *additional_debug, size_t debug_len, uint8_t *out, size_t *out_len) +{ + if (out == NULL || out_len == NULL) return -1; + if (last_stream_id != 0 && (last_stream_id & 1) == 0) return -1; + + uint8_t *p = out; + *p++ = (uint8_t)((last_stream_id >> 24) & 0xFF); + *p++ = (uint8_t)((last_stream_id >> 16) & 0xFF); + *p++ = (uint8_t)((last_stream_id >> 8) & 0xFF); + *p++ = (uint8_t)(last_stream_id & 0xFF); + *p++ = (uint8_t)((error >> 24) & 0xFF); + *p++ = (uint8_t)((error >> 16) & 0xFF); + *p++ = (uint8_t)((error >> 8) & 0xFF); + *p++ = (uint8_t)(error & 0xFF); + + if (additional_debug != NULL && debug_len > 0) { + memcpy(p, additional_debug, debug_len); + p += debug_len; + } + + *out_len = (size_t)(p - out); + return 0; +} + +int http2_encode_window_update(uint32_t stream_id, uint32_t increment, uint8_t *out, size_t *out_len) +{ + if (out == NULL || out_len == NULL) return -1; + if (increment == 0 || increment > 2147483647) return -1; + + out[0] = (uint8_t)((increment >> 24) & 0xFF); + out[1] = (uint8_t)((increment >> 16) & 0xFF); + out[2] = (uint8_t)((increment >> 8) & 0xFF); + out[3] = (uint8_t)(increment & 0xFF); + *out_len = 4; + + return 0; +} + +int http2_parse_frame_type(const char *name) +{ + if (name == NULL) return -1; + + if (strcmp(name, "DATA") == 0) return HTTP2_FRAME_DATA; + if (strcmp(name, "HEADERS") == 0) return HTTP2_FRAME_HEADERS; + if (strcmp(name, "PRIORITY") == 0) return HTTP2_FRAME_PRIORITY; + if (strcmp(name, "RST_STREAM") == 0) return HTTP2_FRAME_RST_STREAM; + if (strcmp(name, "SETTINGS") == 0) return HTTP2_FRAME_SETTINGS; + if (strcmp(name, "PUSH_PROMISE") == 0) return HTTP2_FRAME_PUSH_PROMISE; + if (strcmp(name, "PING") == 0) return HTTP2_FRAME_PING; + if (strcmp(name, "GOAWAY") == 0) return HTTP2_FRAME_GOAWAY; + if (strcmp(name, "WINDOW_UPDATE") == 0) return HTTP2_FRAME_WINDOW_UPDATE; + if (strcmp(name, "CONTINUATION") == 0) return HTTP2_FRAME_CONTINUATION; + + return -1; +} + +const char *http2_frame_type_to_str(HTTP2_Frame_Type type) +{ + switch (type) { + case HTTP2_FRAME_DATA: return "DATA"; + case HTTP2_FRAME_HEADERS: return "HEADERS"; + case HTTP2_FRAME_PRIORITY: return "PRIORITY"; + case HTTP2_FRAME_RST_STREAM: return "RST_STREAM"; + case HTTP2_FRAME_SETTINGS: return "SETTINGS"; + case HTTP2_FRAME_PUSH_PROMISE: return "PUSH_PROMISE"; + case HTTP2_FRAME_PING: return "PING"; + case HTTP2_FRAME_GOAWAY: return "GOAWAY"; + case HTTP2_FRAME_WINDOW_UPDATE: return "WINDOW_UPDATE"; + case HTTP2_FRAME_CONTINUATION: return "CONTINUATION"; + default: return "UNKNOWN"; + } +} + +const char *http2_error_to_str(HTTP2_Error_Code error) +{ + switch (error) { + case HTTP2_ERROR_NO_ERROR: return "NO_ERROR"; + case HTTP2_ERROR_PROTOCOL_ERROR: return "PROTOCOL_ERROR"; + case HTTP2_ERROR_INTERNAL_ERROR: return "INTERNAL_ERROR"; + case HTTP2_ERROR_FLOW_CONTROL_ERROR: return "FLOW_CONTROL_ERROR"; + case HTTP2_ERROR_SETTINGS_TIMEOUT: return "SETTINGS_TIMEOUT"; + case HTTP2_ERROR_STREAM_CLOSED: return "STREAM_CLOSED"; + case HTTP2_ERROR_FRAME_SIZE_ERROR: return "FRAME_SIZE_ERROR"; + case HTTP2_ERROR_REFUSED_STREAM: return "REFUSED_STREAM"; + case HTTP2_ERROR_CANCEL: return "CANCEL"; + case HTTP2_ERROR_COMPRESSION_ERROR: return "COMPRESSION_ERROR"; + case HTTP2_ERROR_CONNECT_ERROR: return "CONNECT_ERROR"; + case HTTP2_ERROR_ENHANCE_YOUR_CLAIM: return "ENHANCE_YOUR_CLAIM"; + case HTTP2_ERROR_INADEQUATE_SECURITY: return "INADEQUATE_SECURITY"; + case HTTP2_ERROR_HTTP_1_1_REQUIRED: return "HTTP_1_1_REQUIRED"; + default: return "UNKNOWN"; + } +} \ No newline at end of file diff --git a/http2.h b/http2.h new file mode 100644 index 0000000..f340320 --- /dev/null +++ b/http2.h @@ -0,0 +1,181 @@ +#ifndef __HTTP2_H__ +#define __HTTP2_H__ + +#include +#include + +#define HTTP2_SETTINGS_MAX_PARAM_ID 1024 +#define HTTP2_MAX_FRAME_SIZE 16777215 +#define HTTP2_INITIAL_WINDOW_SIZE 65535 +#define HTTP2_MAX_CONCURRENT_STREAMS 100 +#define HTTP2_DEFAULT_HEADER_TABLE_SIZE 4096 + +#define HTTP2_FRAME_FLAG_END_STREAM 0x1 +#define HTTP2_FRAME_FLAG_END_HEADERS 0x4 +#define HTTP2_FRAME_FLAG_PADDED 0x8 +#define HTTP2_FRAME_FLAG_PRIORITY 0x20 +#define HTTP2_FRAME_FLAG_ACK 0x1 + +typedef enum { + HTTP2_FRAME_DATA = 0x0, + HTTP2_FRAME_HEADERS = 0x1, + HTTP2_FRAME_PRIORITY = 0x2, + HTTP2_FRAME_RST_STREAM = 0x3, + HTTP2_FRAME_SETTINGS = 0x4, + HTTP2_FRAME_PUSH_PROMISE = 0x5, + HTTP2_FRAME_PING = 0x6, + HTTP2_FRAME_GOAWAY = 0x7, + HTTP2_FRAME_WINDOW_UPDATE = 0x8, + HTTP2_FRAME_CONTINUATION = 0x9 +} HTTP2_Frame_Type; + +typedef enum { + HTTP2_ERROR_NO_ERROR = 0x0, + HTTP2_ERROR_PROTOCOL_ERROR = 0x1, + HTTP2_ERROR_INTERNAL_ERROR = 0x2, + HTTP2_ERROR_FLOW_CONTROL_ERROR = 0x3, + HTTP2_ERROR_SETTINGS_TIMEOUT = 0x4, + HTTP2_ERROR_STREAM_CLOSED = 0x5, + HTTP2_ERROR_FRAME_SIZE_ERROR = 0x6, + HTTP2_ERROR_REFUSED_STREAM = 0x7, + HTTP2_ERROR_CANCEL = 0x8, + HTTP2_ERROR_COMPRESSION_ERROR = 0x9, + HTTP2_ERROR_CONNECT_ERROR = 0xa, + HTTP2_ERROR_ENHANCE_YOUR_CLAIM = 0xb, + HTTP2_ERROR_INADEQUATE_SECURITY = 0xc, + HTTP2_ERROR_HTTP_1_1_REQUIRED = 0xd +} HTTP2_Error_Code; + +typedef enum { + HTTP2_STREAM_STATE_IDLE = 0, + HTTP2_STREAM_STATE_OPEN, + HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL, + HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE, + HTTP2_STREAM_STATE_RESERVED_LOCAL, + HTTP2_STREAM_STATE_RESERVED_REMOTE, + HTTP2_STREAM_STATE_CLOSED +} HTTP2_Stream_State; + +typedef enum { + HTTP2_SETTINGS_HEADER_TABLE_SIZE = 0x1, + HTTP2_SETTINGS_ENABLE_PUSH = 0x2, + HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 0x3, + HTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x4, + HTTP2_SETTINGS_MAX_FRAME_SIZE = 0x5, + HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x6 +} HTTP2_Settings_Id; + +typedef struct { + uint8_t *data; + size_t size; + size_t capacity; +} HTTP2_Buffer; + +typedef struct { + uint32_t stream_id; + HTTP2_Stream_State state; + uint8_t priority; + int endpoint_closed; +} HTTP2_Stream; + +typedef struct { + HTTP2_Stream streams[HTTP2_MAX_CONCURRENT_STREAMS]; + size_t stream_count; + uint32_t last_stream_id; + uint8_t priority; +} HTTP2_Streams; + +typedef struct { + uint8_t *data; + size_t len; + size_t cap; +} HTTP2_Dynamic_Table; + +typedef struct { + HTTP2_Dynamic_Table dynamic_table; + uint32_t max_size; + uint32_t current_size; +} HTTP2_Encoder_Context; + +typedef struct { + HTTP2_Dynamic_Table dynamic_table; + uint32_t max_size; + uint32_t current_size; + int mirror; +} HTTP2_Decoder_Context; + +typedef struct { + uint32_t stream_id; + uint8_t exclusive; + uint32_t stream_dependency; + uint8_t weight; +} HTTP2_Priority_Spec; + +typedef struct { + HTTP2_Frame_Type type; + uint8_t flags; + uint32_t stream_id; + uint32_t length; + uint8_t payload[HTTP2_MAX_FRAME_SIZE]; +} HTTP2_Frame; + +typedef struct { + int sockfd; + HTTP2_Buffer read_buffer; + HTTP2_Buffer write_buffer; + HTTP2_Streams streams; + uint32_t local_settings[HTTP2_SETTINGS_MAX_PARAM_ID]; + uint32_t remote_settings[HTTP2_SETTINGS_MAX_PARAM_ID]; + uint32_t last_settings_stream_id; + uint32_t last_stream_id; + int is_server; + int hpack_initialized; + HTTP2_Encoder_Context encoder; + HTTP2_Decoder_Context decoder; + uint32_t next_stream_id; + uint32_t remote_concurrent_streams; + uint32_t local_window_size; + uint32_t remote_window_size; +} HTTP2_Connection; + +int http2_init(void); +HTTP2_Connection *http2_connection_new(void); +int http2_connection_init(HTTP2_Connection *conn, int sockfd, int is_server); +void http2_connection_free(HTTP2_Connection *conn); +void http2_connection_close(HTTP2_Connection *conn); + +int http2_send_preface(HTTP2_Connection *conn); +int http2_recv_preface(HTTP2_Connection *conn); + +int http2_encode_frame(HTTP2_Frame *frame, HTTP2_Frame_Type type, uint32_t stream_id, uint8_t flags, const uint8_t *payload, size_t payload_len); +int http2_decode_frame(HTTP2_Frame *frame, const uint8_t *data, size_t len); +int http2_recv_frame(HTTP2_Connection *conn, HTTP2_Frame *frame); +int http2_send_frame(HTTP2_Connection *conn, HTTP2_Frame *frame); + +int http2_encode_settings(HTTP2_Connection *conn, uint32_t *settings, size_t count); +int http2_decode_settings(const HTTP2_Frame *frame, uint32_t *settings, size_t *count); + +int http2_encode_priority(HTTP2_Priority_Spec *prio, uint8_t *out, size_t *out_len); +int http2_decode_priority(const uint8_t *data, size_t len, HTTP2_Priority_Spec *prio); + +int http2_hpack_encode_init(HTTP2_Encoder_Context *ctx, uint32_t max_size); +int http2_hpack_decode_init(HTTP2_Decoder_Context *ctx, uint32_t max_size); +void http2_hpack_encode_free(HTTP2_Encoder_Context *ctx); +void http2_hpack_decode_free(HTTP2_Decoder_Context *ctx); +int http2_hpack_encode(HTTP2_Encoder_Context *ctx, const char **headers, size_t headers_len, uint8_t *out, size_t *out_len); +int http2_hpack_decode(HTTP2_Decoder_Context *ctx, const uint8_t *data, size_t len, char **headers, size_t *headers_len); + +int http2_encode_headers(HTTP2_Connection *conn, const char **headers, size_t headers_len, uint8_t *out, size_t *out_len); +int http2_decode_headers(HTTP2_Connection *conn, const uint8_t *data, size_t len, char **headers, size_t *headers_len); + +int http2_encode_priority_frame(uint32_t stream_id, HTTP2_Priority_Spec *prio, uint8_t *out, size_t *out_len); +int http2_encode_rst_stream(uint32_t stream_id, HTTP2_Error_Code error, uint8_t *out, size_t *out_len); +int http2_encode_ping(HTTP2_Connection *conn, uint8_t *data, uint8_t flags, uint8_t *out, size_t *out_len); +int http2_encode_goaway(uint32_t last_stream_id, HTTP2_Error_Code error, const uint8_t *additional_debug, size_t debug_len, uint8_t *out, size_t *out_len); +int http2_encode_window_update(uint32_t stream_id, uint32_t increment, uint8_t *out, size_t *out_len); + +int http2_parse_frame_type(const char *name); +const char *http2_frame_type_to_str(HTTP2_Frame_Type type); +const char *http2_error_to_str(HTTP2_Error_Code error); + +#endif \ No newline at end of file diff --git a/inlcude/http.h b/inlcude/http.h new file mode 100644 index 0000000..a942bbe --- /dev/null +++ b/inlcude/http.h @@ -0,0 +1,169 @@ +#ifndef __HTTP_H__ +#define __HTTP_H__ + +#include + +typedef enum { + HTTP_M_ACL , + HTTP_M_BASELINE_CONTROL , + HTTP_M_BIND , + HTTP_M_CHECKIN , + HTTP_M_CHECKOUT , + HTTP_M_CONNECT , + HTTP_M_COPY , + HTTP_M_DELETE , + HTTP_M_GET , + HTTP_M_HEAD , + HTTP_M_LABEL , + HTTP_M_LINK , + HTTP_M_LOCK , + HTTP_M_M_SEARCH , + HTTP_M_MERGE , + HTTP_M_MKACTIVITY , + HTTP_M_MKCALENDAR , + HTTP_M_MKCOL , + HTTP_M_MKREDIRECTREF , + HTTP_M_MKWORKSPACE , + HTTP_M_MOVE , + HTTP_M_NOTIFY , + HTTP_M_OPTIONS , + HTTP_M_ORDERPATCH , + HTTP_M_PATCH , + HTTP_M_POST , + HTTP_M_PROPFIND , + HTTP_M_PROPPATCH , + HTTP_M_PURGE , + HTTP_M_PUT , + HTTP_M_QUERY , + HTTP_M_REBIND , + HTTP_M_REPORT , + HTTP_M_SEARCH , + HTTP_M_SOURCE , + HTTP_M_SUBSCRIBE , + HTTP_M_TRACE , + HTTP_M_UNBIND , + HTTP_M_UNCHECKOUT_UNLINK , + HTTP_M_UNLOCK , + HTTP_M_UNSUBSCRIBE , + HTTP_M_UPDATE , + HTTP_M_UPDATEREDIRECTREF , + HTTP_M_VERSION_CONTROL , + HTTP_M_UNKNOWN , +} HTTP_M_Kind; + +typedef struct { + char *key; + char *value; +} HTTP_Header; + + +typedef struct { + HTTP_Header *data; + size_t size; + size_t capacity; +} HTTP_Headers; + + +typedef struct { + const char *url; + const char *host; + const char *port; + const char *method; + const char *schema; + const char *path; + void *body; + size_t body_size; + HTTP_Headers headers; + const char *http_version; +} HTTP_Request; + + +typedef struct { + HTTP_Request request; + int status_code; + const char *status_text; + HTTP_Headers headers; + void *body; + size_t body_size; +} HTTP_Response; + + + + + +typedef enum { + HTTP_T_H1, + HTTP_T_H2, + HTTP_T_H3, +} HTTP_T_Kind; + +// HTTP1.1 + +typedef struct { + char *data; + size_t size; + size_t capacity; +} HTTP_Request_Raw_1_1; + +typedef struct { + char *data; + size_t size; + size_t capacity; +} HTTP_Response_Raw_1_1; + +typedef struct { + int sockfd; + HTTP_Request_Raw_1_1 raw_req; + HTTP_Response_Raw_1_1 raw_res; +} HTTP_Transport_H1; + +// HTTP2 + +typedef struct { + // HTTP3 not implemented +} HTTP_Transport_H2; + +// HTTP3 + +typedef struct { + // HTTP3 not implemented +} HTTP_Transport_H3; + +typedef struct { + HTTP_T_Kind kind; + union { + HTTP_Transport_H1 h1; + HTTP_Transport_H2 h2; + HTTP_Transport_H3 h3; + }; +} HTTP_Transport; + + +HTTP_Request *http_request_create(); +int http_request_init(HTTP_Request *req); +int http_request_free(HTTP_Request *req); + +HTTP_Response *http_response_create(HTTP_Request *req); +int http_response_init(HTTP_Response *res, HTTP_Request *req); +int http_response_free(HTTP_Response *res); + + +HTTP_M_Kind http_str_to_method(const char *method); +const char *http_method_to_str(HTTP_M_Kind method); +HTTP_Headers http_new_headers(); +int http_add_header(HTTP_Headers *headers, const char *key, const char *value); +int http_connect_to_host(const char* url, const char *port); +int http_build_request(HTTP_Request *req, HTTP_Transport *t); +//HTTP_Request_Raw *http_build_request(HTTP_Request *req); +int http_request( const char *url, const char *method, void *body, size_t body_size, HTTP_Headers *headers) ; + +// TODO: EXPERIMENTAL +HTTP_Transport *http_transport_new(HTTP_T_Kind kind); +int http_transport_init(HTTP_Transport *t, HTTP_T_Kind kind); +int http_transport_connect(HTTP_Transport *t, HTTP_Request *req); +int http_transport_send(HTTP_Transport *t, HTTP_Request *req); +int http_transport_recv(HTTP_Transport *t, HTTP_Response *res); +void http_transport_close(HTTP_Transport *t); + +#endif //__HTTP_H__ + diff --git a/inlcude/strcmp.h b/inlcude/strcmp.h new file mode 100644 index 0000000..239c54e --- /dev/null +++ b/inlcude/strcmp.h @@ -0,0 +1,9 @@ +#ifndef __STRCMP_H__ +#define __STRCMP_H__ + +#include + +int cmpstrcasei(const char *lhs, const char *rhs); +int cmpstrncasei(const char *lhs, const char *rhs, size_t n); + +#endif //__STRCMP_H__ diff --git a/inlcude/utils.h b/inlcude/utils.h new file mode 100644 index 0000000..90130e2 --- /dev/null +++ b/inlcude/utils.h @@ -0,0 +1,154 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include + +#define OUT_OF_BOUNDS(base, pos, size) do {\ + if ((pos) - (base) + 1 > (size) )\ + {\ + fprintf(stderr, "%s:%d: Error: Expected more characters, but found the end of the request.\n", __FILE__, __LINE__);\ + return 1;\ + }\ +} while(0) + +#define len(x) (\ + (x) == NULL \ + ? ( \ + fprintf(stderr, "%s:%d: ERROR: Tried to use len("#x"), but it is NULL\n", __FILE__, __LINE__), \ + 0 \ + ) \ + : sizeof(x)/sizeof(*x) \ +) + +#define foreach(i, arr) for (size_t i = 0 ; i < len(arr) ; i++) + +#define DA_MIN_CAPACITY 32 + +#define da_error(s) fprintf(stderr, "%s:%d: ERROR: " #s "\n", __FILE__, __LINE__) + +#define da_append(da, item) \ + ( (da) != NULL \ + ? ( (da)->data == NULL \ + ? ( (da)->data = malloc(DA_MIN_CAPACITY), (da)->size = 0, (da)->capacity = DA_MIN_CAPACITY, 0) \ + : 0 \ + , (da)->size >= (da)->capacity \ + ? ((da)->capacity = (da)->size*2, (da)->data = realloc((da)->data, (da)->capacity), 0) \ + : 0\ + , ( (da)->data[(da)->size++] = (item), 0)\ + ) \ + : ( da_error("tried to da_append to NULL pointer.") \ + , 1 \ + )\ + ) + +#define da_concat(lhs, rhs) \ + ( (lhs) != NULL \ + ? ( (rhs) != NULL \ + ? ( (lhs)->data == NULL \ + ? ( (lhs)->data = malloc(DA_MIN_CAPACITY), (lhs)->size = 0, (lhs)->capacity = DA_MIN_CAPACITY, 0) \ + : 0 \ + , (rhs)->data != NULL \ + ? ( ((lhs)->capacity < (rhs)->size) \ + ? ( (lhs)->capacity += (rhs)->size \ + , (lhs)->data = realloc((lhs)->data, (lhs)->capacity) \ + , 0 \ + ) \ + : 0 \ + , ((lhs)->capacity - (lhs)->size < (rhs)->size) \ + ? ( (lhs)->capacity *= 2 \ + , (lhs)->data = realloc((lhs)->data, (lhs)->capacity) \ + , 0 \ + ) \ + : 0 \ + , memcpy((lhs)->data + (lhs)->size, (rhs)->data, (rhs)->size) \ + , (lhs)->size += (rhs)->size \ + , 0\ + ) \ + : 0 \ + ) \ + : (da_error("tried to da_concat but rhs is NULL"), 1) \ + ) \ + : (da_error("tried to da_concat but lhs is NULL."), 1) \ + ) + + +#define da_cstr_concat(da, str) \ + ( (da) != NULL \ + ? ( (str) != NULL \ + ? ( (da)->data == NULL \ + ? ( (da)->data = malloc(DA_MIN_CAPACITY), (da)->size = 0, (da)->capacity = DA_MIN_CAPACITY, memset((da)->data, 0, sizeof(*(da)->data) *DA_MIN_CAPACITY) ,0) \ + : 0 \ + , *(str) != '\0' \ + ? ( ((da)->capacity < strlen(str)) \ + ? ( (da)->capacity += strlen(str) + 1\ + , (da)->data = realloc((da)->data, (da)->capacity) \ + , 0 \ + ) \ + : 0 \ + , ((da)->capacity - (da)->size < strlen(str)) + 1 \ + ? ( (da)->capacity *= 2 \ + , (da)->data = realloc((da)->data, (da)->capacity) \ + , 0 \ + ) \ + : 0 \ + , memcpy((da)->data + (da)->size, (str), strlen(str)) \ + , (da)->size += strlen(str) \ + , (da)->data[(da)->size] = '\0' \ + , 0\ + ) \ + : 0 \ + ) \ + : (da_error("tried to da_cstr_concat but str is NULL"), 1) \ + ) \ + : (da_error("tried to da_cstr_concat but da is NULL."), 1) \ + ) + +#define da_memcpy(da, mem, mem_size) \ + ( (da) != NULL \ + ? ( (mem) != NULL \ + ? ( (da)->data == NULL \ + ? ( (da)->data = malloc(DA_MIN_CAPACITY) \ + , (da)->size = 0 \ + , (da)->capacity = DA_MIN_CAPACITY \ + , memset((da)->data, 0, sizeof(*((da)->data)) * DA_MIN_CAPACITY) \ + , 0 \ + ) \ + : 0 \ + , (mem_size) > 0 \ + ? ( ((da)->capacity < (mem_size)) \ + ? ( (da)->capacity += (mem_size) \ + , (da)->data = realloc((da)->data, (da)->capacity) \ + , 0 \ + ) \ + : 0 \ + , ((da)->capacity - (da)->size < (mem_size)) \ + ? ( (da)->capacity *= 2 \ + , (da)->data = realloc((da)->data, (da)->capacity) \ + , 0 \ + ) \ + : 0 \ + , memcpy((da)->data + (da)->size, (mem), (mem_size)) \ + , (da)->size += (mem_size) \ + , 0\ + ) \ + : 0 \ + ) \ + : (da_error("tried to da_memcpy but mem is NULL"), 1) \ + ) \ + : (da_error("tried to da_memcpy but da is NULL."), 1) \ + ) + + + +/* + * ERRORS + * 1: str had a none digit character. + * 2: str was null + * 3: res was null + * 4: zero length, nothing to convert + */ +int sstrtoint(const char *str, size_t size, int *res); +int cstrtoint(const char *str, int *res); + +#endif //__UTILS_H__ diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..794a895 --- /dev/null +++ b/src/http.c @@ -0,0 +1,667 @@ +#include "http.h" + +#include // connect getaddrinfo socket (struct addrinfo) AF_UNSPEC SOCK_STREAM send recv freeaddrinfo gai_strerror +#include // fprintf perror printf stderr +#include // free malloc realloc +#include // memset strchr strcmp strdup strlen strstr +#include // close + +#include "strcmp.h" // cmpstrcasei +#include "utils.h" // len da_append + + +/* ---------------------------------- Data ----------------------------------*/ + +#define HTTP_DEFAULT_SCHEMA "http" +#define HTTP_DEFAULT_VERSION "HTTP/1.1" +#define RECV_BUFFER_SIZE (1024 * 4) + +static const char *SCHEMAS[] = { + "http", + "https" +}; + + +const char *http_schema_to_port(const char *schema) +{ + if (cmpstrcasei(schema, "http") == 0) + return "80"; + if (cmpstrcasei(schema, "https") == 0) + return "443"; + fprintf(stderr, "%s:%d: ERROR: could not find default port for schema '%s'.", __FILE__, __LINE__, schema); + return NULL; +} + + +HTTP_M_Kind http_str_to_method(const char *method) +{ + if (cmpstrcasei(method, "GET") == 0) + return HTTP_M_GET; + if (cmpstrcasei(method, "HEAD") == 0) + return HTTP_M_HEAD; + if (cmpstrcasei(method, "OPTIONS") == 0) + return HTTP_M_OPTIONS; + if (cmpstrcasei(method, "PATCH") == 0) + return HTTP_M_PATCH; + if (cmpstrcasei(method, "POST") == 0) + return HTTP_M_POST; + if (cmpstrcasei(method, "PUT") == 0) + return HTTP_M_PUT; + return HTTP_M_UNKNOWN; +} + + +const char *http_method_to_str(HTTP_M_Kind method) +{ + switch (method) { + case HTTP_M_DELETE: return "DELETE"; + case HTTP_M_GET: return "GET"; + case HTTP_M_HEAD: return "HEAD"; + case HTTP_M_OPTIONS: return "OPTIONS"; + case HTTP_M_PATCH: return "PATCH"; + case HTTP_M_POST: return "POST"; + case HTTP_M_PUT: return "PUT"; + default: return "UNKNOWN"; + } +} + + + +int http_request_init(HTTP_Request *req) +{ + if (req == NULL) return -1; + memset(req, 0, sizeof(*req)); + return 0; +} + +HTTP_Request *http_request_create() +{ + HTTP_Request *req = malloc(sizeof(*req)); + if (req == NULL) { + fprintf(stderr, "ERROR: failed to allocate HTTP_Request\n"); + return NULL; + } + if (http_request_init(req) != 0) { + free(req); + return NULL; + } + return req; +} + + +int http_response_init(HTTP_Response *res, HTTP_Request *req) +{ + if (res == NULL || req == NULL) return -1; + memset(res, 0, sizeof(*res)); + res->request = *req; + return 0; +} + +HTTP_Response *http_response_create(HTTP_Request *req) +{ + HTTP_Response *res = malloc(sizeof(*res)); + if (http_response_init(res, req) != 0) + return NULL; + return res; +} + + + +#define HTTP_HEADERS_MIN_CAP 16 + + +HTTP_Headers http_new_headers() +{ + HTTP_Headers h = { + .data = malloc(sizeof(HTTP_Header) * HTTP_HEADERS_MIN_CAP), + .size = 0, + .capacity = HTTP_HEADERS_MIN_CAP + }; + return h; +} + + +int http_add_header(HTTP_Headers *headers, const char *key, const char *value) +{ + if (headers == NULL || key == NULL || value == NULL) { + fprintf(stderr, "%s:%d: Error: Could not add headers, argument(s) were NULL.\n", __FILE__, __LINE__); + return 1; + } + if (headers->capacity <= headers->size) { + HTTP_Header *new_data = realloc(headers->data, headers->capacity * 2 * sizeof(HTTP_Header)); + if (new_data == NULL) { + fprintf(stderr, "%s:%d: Error: Could not allocate memory for headers.\n", __FILE__, __LINE__); + return 1; + } + headers->data = new_data; + headers->capacity *= 2; + } + HTTP_Header *new_header = &headers->data[headers->size++]; + new_header->key = strdup(key); + new_header->value = strdup(value); + if (new_header->key == NULL || new_header->value == NULL) { + fprintf(stderr, "%s:%d Error: Could not allocate memory for header key/value.\n", __FILE__, __LINE__); + return 1; + } + return 0; +} + + +int http_create_socket(int domain, int type, int protocol) +{ + int sockfd = socket(domain, type, protocol); + if (sockfd < 0) { + fprintf(stderr, "%s:%d Error: Could not create new socket.\n", __FILE__, __LINE__); + return -1; + } + return sockfd; +} + + +int http_create_connection(struct addrinfo *ai) +{ + int sockfd = http_create_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sockfd == -1) return -1; + if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) != 0) { + fprintf(stderr, "%s:%d Error: Could not connect.\n", __FILE__, __LINE__); + close(sockfd); + sockfd = -1; + } + return sockfd; +} + + +int resolve_domain(const char *host, const char *port, struct addrinfo **res) +{ + struct addrinfo hints; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int err = getaddrinfo(host, port, &hints, res); + if (err != 0) { + fprintf(stderr, "%s:%d: ERROR: getaddrinfo failed for '%s' '%s': %s\n", __FILE__, __LINE__, host, port, gai_strerror(err)); + return err; + } + return 0; +} + + +int http_connect_to_host(const char *host, const char *port) +{ + struct addrinfo *res; + int sockfd; + if (resolve_domain(host, port, &res)) { + fprintf(stderr, "%s:%d: ERROR: getaddrinfo failed for '%s' '%s'.\n", __FILE__, __LINE__, host, port); + return -1; + } + for (struct addrinfo *addr = res ; addr != NULL ; addr = addr->ai_next) { + sockfd = http_create_connection(addr); + if (sockfd > -1) { + freeaddrinfo(res); + return sockfd; + } + } + freeaddrinfo(res); + return -1; +} + + +int http_build_request(HTTP_Request *req, HTTP_Transport *t) +{ + //HTTP_Request_Raw *raw_req = malloc(sizeof(HTTP_Request_Raw)); + switch (t->kind) { + case HTTP_T_H1: { + HTTP_Request_Raw_1_1 *raw_req = &(t->h1.raw_req); + raw_req->data = malloc(16); + raw_req->size = 0; + raw_req->capacity = 16; + da_cstr_concat(raw_req, req->method); + da_append(raw_req, ' '); + da_cstr_concat(raw_req, req->path); + da_append(raw_req, ' '); + //da_cstr_concat(raw_req, req->http_version); + da_cstr_concat(raw_req, "HTTP/1.1"); + da_cstr_concat(raw_req, "\r\n"); + for (HTTP_Header *h = req->headers.data ; h < req->headers.data + req->headers.size ; h++) { + da_cstr_concat(raw_req, h->key); + da_cstr_concat(raw_req, ": "); + da_cstr_concat(raw_req, h->value); + da_cstr_concat(raw_req, "\r\n"); + } + if (req->body != NULL && req->body_size > 0) { + char con_len[64]; + snprintf(con_len, sizeof(con_len), "Content-Length: %zu\r\n", req->body_size); + da_cstr_concat(raw_req, con_len); + } + da_cstr_concat(raw_req, "\r\n"); + if (req->body != NULL && req->body_size > 0) { + da_memcpy(raw_req, req->body, req->body_size); + } + return 0; + } break; + case HTTP_T_H2: + case HTTP_T_H3: + fprintf(stderr, "%s:%d: ERROR: 'http_build_request not implemented for HTTP_T_Kind '%d'\n", __FILE__, __LINE__, t->kind); + break; + default: + fprintf(stderr, "%s:%d: ERROR: unexpected HTTP_T_Kind '%d' for 'http_build_request'.\n", __FILE__, __LINE__, t->kind); + } + return 1; +} + +/* ------------------------------- HTTP/1.1 ---------------------------------*/ + + +void print_raw_http_response(HTTP_Transport *t) +{ + char *raw_res = t->h1.raw_res.data; + size_t size = t->h1.raw_res.size; + for (size_t i = 0 ; i < size ; i++) { + char c = raw_res[i]; + if (c == ' ') printf("' '"); + else if (c == '\r') printf("\\r"); + else if (c == '\n') printf("\\n\n"); + else if (c == '\0') printf("\\0"); + else if (c > 31 && c < 127)printf("%c", c); + else printf("<%02x>", c); + } +} + + + +int http_parse_response_1_1(HTTP_Transport *t, HTTP_Response *res) +{ + char *raw_res = t->h1.raw_res.data; + size_t size = t->h1.raw_res.size; + char *base = raw_res; + char *sep; + int len; + + // HTTP/1.1 + if( cmpstrncasei(raw_res, "HTTP/1.1" , 8) != 0) { + fprintf(stderr, "%s:%d: Error: raw response did not start with HTTP/1.1.\n", __FILE__, __LINE__); + return 1; + } + + // HTTP/1.1' ' + sep = strchr(raw_res, ' '); + if (sep == NULL) { + fprintf(stderr, "%s:%d: Error: Expected to find ' ' between 'HTTP/1.1' and '', but there is no ' '.\n", __FILE__, __LINE__); + return 1; + } + len = sep - raw_res; + + // ' ' + OUT_OF_BOUNDS(base, raw_res, size); + raw_res = sep + 1; // consume HTTP version + ' ' + sep = strchr(raw_res, ' '); + if (sep == NULL) { + fprintf(stderr, "%s:%d: Error: Expected to find ' ' between '' and '', but there is no ' '.\n", __FILE__, __LINE__); + return 1; + } + // Check 3 digits + len = sep - raw_res; + if (len != 3) { + fprintf(stderr, "%s:%d: Error: Expected 3 digits for status_code, but got '%.*s'.\n", __FILE__, __LINE__, len, raw_res); + return 1; + } + if ( raw_res[0] < '1' || raw_res[0] > '5' + || raw_res[1] < '0' || raw_res[1] > '9' + || raw_res[2] < '0' || raw_res[2] > '9' + ) { + fprintf(stderr, "%s:%d: Error: Expected a number between 100 - 599 for status_code, but got '%.*s'.\n", __FILE__, __LINE__, len, raw_res); + return 1; + } + sstrtoint(raw_res, 3, &res->status_code); + raw_res = sep + 1; // consume + ' ' + + // status_text = strndup(raw_res, len); + + // \r\n + OUT_OF_BOUNDS(base, raw_res, size); + raw_res = sep + 1; // consume + '\r' + if (*raw_res != '\n') { + fprintf(stderr, "%s:%d: Error: Expected to find '\\n' to end the HTTP status line, but didn't found any.\n", __FILE__, __LINE__); + return 1; + } + raw_res += 1; // consume '\n' + + while (1) { + + if(raw_res - base >= size - 1) { + fprintf(stderr, "%s:%d: Unexpected end of HTTP response.\n", __FILE__, __LINE__); + return 1; + } + + // end of headers + if (*raw_res == '\r') { + raw_res++; // consume '\r' + if (*raw_res != '\n') { + fprintf(stderr, "%s:%d: Error: Found \\r at empty line, expected \\n to follow, but found '%c'.\n", __FILE__, __LINE__, *raw_res); + return 1; + } + raw_res++; + break; + } + + char *eol = strchr(raw_res, '\n'); + if (eol == NULL) { + fprintf(stderr, "%s:%d: Error: Expected to find '\\n' for header line, but didn't find it.\n", __FILE__, __LINE__); + return 1; + } + eol--; + if (*eol != '\r') { + fprintf(stderr, "%s:%d: Error: Expected to find \"\\r\\n\" at a header line, but found \"%c\\n\"\n", __FILE__, __LINE__, *eol); + return 1; + } + + sep = strchr(raw_res, ':'); + if (sep == NULL) { + fprintf(stderr, "%s:%d: Error: Expceted to find a ':' at a haeder line, but didn't find any.\n", __FILE__, __LINE__); + return 1; + } + if (sep > eol) { + fprintf(stderr, "%s:%d: Error: Expected to find ':' at header line, but didn't find any in this line.\n", __FILE__, __LINE__); + return 1; + } + HTTP_Header h = {0}; + h.key = strndup(raw_res, sep - raw_res); + sep += *(sep+1) == ' ' ? 2 : 1; + h.value = strndup(sep, eol - sep); + da_append(&res->headers, h); + raw_res = eol + 2; + } + //res->body_size = size - (raw_res - base + 1); + res->body_size = size - (raw_res - base); + res->body = strndup(raw_res, res->body_size); + return 0; +} + + +int http_recv_header_1_1(HTTP_Transport *t, HTTP_Response *res) +{ +#define buffer_size 1024 + char buffer[buffer_size]; + HTTP_Response_Raw_1_1 *raw_res = &t->h1.raw_res; + size_t search_index = 0; + while (1) { + int r_count = recv(t->h1.sockfd, buffer, buffer_size, 0); + if (r_count <= 0) + return -1; + size_t old_size = raw_res->size; + da_memcpy(raw_res, buffer, r_count); + if (old_size >= 3) + search_index = old_size - 3; + else + search_index = 0; + char *found = memmem( + raw_res->data + search_index, + raw_res->size - search_index, + "\r\n\r\n", 4); + if (found != NULL) + break; + } + + http_parse_response_1_1(t, res); + + printf("%d\n", res->status_code); + printf("%s\n", res->status_text); + for (size_t i = 0; i < res->headers.size ; i++) { + printf("Header%02ld , key: '%s', value '%s'\n", i, res->headers.data[i].key, res->headers.data[i].value); + } + printf("%ld\n", res->body_size); + for (size_t i = 0 ; i < res->body_size ; i++) { + printf("%c", ((char*)res->body)[i]); + } + printf("\n"); + // TODO: split up parsing header and recv body loginc + // + // TODO: Content-Length + // TODO: Transfer-Encoding + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding + // TODO: If Transfer-Encoding chunked -> check Content-Type: text/event-stream for sse? Or leave it like that? + // TODO: Maybe callbacks for streaming mode? + // + // Important: RFC behavior (this matters) + // + // HTTP/1.1 rules: + // + // Transfer-Encoding takes precedence over Content-Length + // If Transfer-Encoding: chunked → ignore Content-Length + // If neither exists: + // Body ends when connection closes + + return 0; +#undef buffer_size +} + + +/* ------------------------------- Transport ------------------------------- */ + + +int http_transport_init(HTTP_Transport *t, HTTP_T_Kind kind) +{ + if (!t) return -1; + + t->kind = kind; + + switch (kind) { + case HTTP_T_H1: + t->h1.sockfd = -1; + return 0; + + case HTTP_T_H2: + case HTTP_T_H3: + fprintf(stderr, "%s:%d: ERROR: HTTP/2 and /3 not implemented.\n", __FILE__, __LINE__); + return -1; + } + + fprintf(stderr, "%s:%d: ERROR: Unsupported HTTP_T_Kind '%d'.\n", __FILE__, __LINE__, kind); + return -1; +} + + +int http_transport_free(HTTP_Transport *t) +{ + fprintf(stderr, "%s:%d: Not Implemented: int http_transport_free(HTTP_Transport *t)\n", __FILE__, __LINE__); + return 1; +} + + +HTTP_Transport *http_transport_create(HTTP_T_Kind kind) +{ + HTTP_Transport *t = malloc(sizeof(*t)); + if (t == NULL) return NULL; + if (http_transport_init(t, kind) == 0) return t; + free(t); + return NULL; +} + +int http_transport_connect(HTTP_Transport *t, HTTP_Request *req) +{ + if (!t) return -1; + if (!req) return -1; + + switch (t->kind) { + case HTTP_T_H1: + t->h1.sockfd = http_connect_to_host(req->host, req->port); + return t->h1.sockfd; + case HTTP_T_H2: + case HTTP_T_H3: + fprintf(stderr, "%s:%d: ERROR: HTTP/2 and /3 not implemented.\n", __FILE__, __LINE__); + return -1; + } + + return -1; +} + +int http_transport_send(HTTP_Transport *t, HTTP_Request *req) +{ + if (!t) return -1; + + switch (t->kind) { + case HTTP_T_H1: + if(http_build_request(req, t) != 0) + return 1; + return send(t->h1.sockfd, t->h1.raw_req.data, t->h1.raw_req.size, 0); + case HTTP_T_H2: + case HTTP_T_H3: + fprintf(stderr, "%s:%d: ERROR: HTTP/2 and /3 not implemented.\n", __FILE__, __LINE__); + return -1; + } + + return -1; +} + + +int http_transport_recv(HTTP_Transport *t, HTTP_Response *res) +{ + if (t == NULL || res == NULL) return -1; + + switch (t->kind) { + case HTTP_T_H1: + return http_recv_header_1_1(t, res); + + case HTTP_T_H2: + case HTTP_T_H3: + fprintf(stderr, "%s:%d: ERROR: HTTP/2 and /3 not implemented.\n", __FILE__, __LINE__); + return -1; + } + + return -1; +} + + +void http_transport_close(HTTP_Transport *t) +{ + if (!t) return; + + switch (t->kind) { + case HTTP_T_H1: + if (t->h1.sockfd >= 0) + close(t->h1.sockfd); + break; + + case HTTP_T_H2: + case HTTP_T_H3: + fprintf(stderr, "%s:%d: ERROR: HTTP/2 and /3 not implemented.\n", __FILE__, __LINE__); + return; + } +} + + +int http_parse_url(const char *url, HTTP_Request *return_request) +{ + char *_url = strdup(url); + char *_url_start = _url; // for freeing _url + return_request->url = strdup(url); + + const char *index_sep = strstr(url, "://"); + if (index_sep != NULL) { + int n = index_sep - url; + return_request->schema = strndup(url, n); + if (*return_request->schema == '\0') { + fprintf(stderr, "%s:%d: ERROR: url started with :// and no schema specified. Drop the :// or prepend the schema.", __FILE__, __LINE__); + return 1; + } + _url += n + 3; // skip schema + "://" + int supported_schema = 0; + for (size_t i = 0 ; i < len(SCHEMAS) ; i++ ) { + if (cmpstrcasei(SCHEMAS[i], return_request->schema) == 0) { + supported_schema = 1; + break; + } + } + if (!supported_schema) { + fprintf(stderr, "%s:%d: ERROR: Schema '%s' is not supported.", __FILE__, __LINE__, return_request->schema); + return 1; + } + } + + index_sep = strchr(_url, '/'); + if (index_sep != NULL) { + return_request->path = strdup(index_sep); + _url[index_sep - _url] = '\0'; + } + + index_sep = index_sep == NULL ? strchr(_url, '?') : NULL; + if (index_sep != NULL) { + return_request->path = strdup(index_sep); + _url[index_sep - _url] = '\0'; + } + + index_sep = strchr(_url, ':'); + if (index_sep != NULL) { + return_request->port = strdup(index_sep + 1); + return_request->host = strndup(_url, index_sep - _url); + } + else + return_request->host = strdup(_url); + + if (return_request->schema == NULL) + return_request->schema = HTTP_DEFAULT_SCHEMA; + if (return_request->port == NULL) + return_request->port = http_schema_to_port(return_request->schema); + if (return_request->path == NULL) + return_request->path = "/"; + + free(_url_start); + return 0; +} + +/* ----------------------------- Orchestration ----------------------------- */ + + +int http_request( + const char *url, + const char *method, + void *body, + size_t body_size, + HTTP_Headers *headers +) { + + HTTP_Request *req = http_request_create(); + HTTP_Response *res = http_response_create(req); + if(http_parse_url(url, req) != 0) + return -1; + + req->method = strdup(method); + + req->body = body; + req->body_size = body_size; + req->headers = *headers; + + // Check for 'Host' Header and if not present, inject it with url host + int has_host = 0; + for (int i = 0 ; i < headers->size ; i++) { + if(cmpstrcasei(headers->data[i].key, "Host") == 0) { + has_host = 1; + break; + } + } + if (!has_host) { + if (http_add_header(&(req->headers), "Host", req->host)) { + fprintf(stderr, "%s:%d: Error: Could not add header. http_add_header returned NOT 0!\n", __FILE__, __LINE__); + } + } + + HTTP_Transport *t = http_transport_create(HTTP_T_H1); + if (t == NULL) { + return -1; + } + http_transport_connect(t, req); + http_transport_send(t, req); + http_transport_recv(t, res); + + return 0; +} + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..bbd1622 --- /dev/null +++ b/src/main.c @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +/* + + int getaddrinfo(const char *restrict node, + const char *restrict service, + const struct addrinfo *restrict hints, + struct addrinfo **restrict res); + + struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; + }; + + + struct sockaddr { + sa_family_t sa_family; // Address family + char sa_data[]; // Socket address + }; + + struct sockaddr_storage { + sa_family_t ss_family; // Address family + }; + + + EACCES Permission to create a socket of the specified type and/or protocol is denied. + + EAFNOSUPPORT + The implementation does not support the specified address family. + + EINVAL Unknown protocol, or protocol family not available. + + EINVAL Invalid flags in type. + + EMFILE The per-process limit on the number of open file descriptors has been reached. + + ENFILE The system-wide limit on the total number of open files has been reached. + + ENOBUFS or ENOMEM + Insufficient memory is available. The socket cannot be created until sufficient resources are freed. + + EPROTONOSUPPORT + The protocol type or the specified protocol is not supported within this domain. + + Other errors may be generated by the underlying protocol modules. +*/ + + +#define CRLF "\r\n" + + + +//----------------------------------------------------------------------------- +// Self written HTTP/1.1 +//----------------------------------------------------------------------------- + + + + +int request(const char *host, const char *port) +{ + struct addrinfo *res; + int sockfd; + + // first, load up address structs with getaddrinfo(): + + int rd_rc = resolve_domain(host, port, &res); + if (rd_rc != 0) + return -1; + + // make a socket: + + // const char *msg = "POST /mcp HTTP/1.1" CRLF "Host: localhost:8080" CRLF "User-Agent: my-mcp-client:0.1.0" CRLF "Accept: application/json, text/event-stream" CRLF "Content-Type: application/json" CRLF "Content-Length: 216" CRLF CRLF "{\"jsonrpc\": \"2.0\",\"id\": 1,\"method\": \"initialize\",\"params\": {\"protocolVersion\": \"2025-03-26\",\"capabilities\": {\"roots\": {\"listChanged\": true},\"sampling\": {}},\"clientInfo\": {\"name\": \"ExampleClient\",\"version\": \"1.0.0\"}}}"; + + // connect! + struct addrinfo *ai; + for (ai = res; ai != NULL; ai = ai->ai_next) { + sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sockfd < 0) continue; + connect(sockfd, res->ai_addr, res->ai_addrlen); + send(sockfd, msg, strlen(msg), 0); + } + char buf[1025]; + int a; + while (a = recv(sockfd, buf, 1024, 0), a>0) { + printf("a=%d\n", a); + buf[a] = '\0'; + for (int i = 0; i < a ; i++) { + //printf("<%d>", buf[i]); + printf("%c", buf[i]); + } + printf("\n"); + } + printf("a=%d\n", a); + + close(sockfd); + return 0; +} + + +//----------------------------------------------------------------------------- +// cURL +//----------------------------------------------------------------------------- + + +void skip_to_data(response_data *res_data) +{ + size_t count = 0; + while (count + 3 < res_data->size) { + char buf[6] = {0}; + buf[0] = res_data->data[count + 0]; + buf[1] = res_data->data[count + 1]; + buf[2] = res_data->data[count + 2]; + buf[3] = res_data->data[count + 3]; + buf[4] = res_data->data[count + 4]; + if (strcmp(buf, "data:") == 0) { + res_data->data += count + 5; + res_data->size -= count + 5; + return; + } + count++; + } +} + + +static size_t curl_my_write_callback(void *data, size_t size, size_t nmemb, void *userp) +{ + size_t full_size = size * nmemb; + response_data *res_data = (response_data *)userp; + + if (res_data->capacity + 1 < res_data->size + full_size) { + char *ptr = realloc(res_data->data, res_data->capacity * 2); + if (ptr == NULL) + return 0; + res_data->data = ptr; + res_data->capacity *= 2; + } + memcpy(&(res_data->data[res_data->size]), data, full_size); + res_data->size += full_size; + res_data->data[res_data->size] = 0; + return full_size; +} + + +int post(const char *url) +{ + CURL *curl; + CURLcode res; + + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); + + response_data res_data = {0}; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_my_write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&res_data); + + if (curl) { + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); // HTTP/2 recommended for SSE + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); + + struct timeval tv; + tv.tv_sec = 60; // timeout in seconds + tv.tv_usec = 0; + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Accept: application/json, text/event-stream"); + headers = curl_slist_append(headers, "Cache-Control: no-cache"); + headers = curl_slist_append(headers, "Connection: keep-alive"); + headers = curl_slist_append(headers, "Content-Type: application/json, text/event-stream"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + const char *json = "{\"jsonrpc\": \"2.0\",\"id\": 1,\"method\": \"initialize\",\"params\": {\"protocolVersion\": \"2025-03-26\",\"capabilities\": {\"roots\": {\"listChanged\": true},\"sampling\": {}},\"clientInfo\": {\"name\": \"ExampleClient\",\"version\": \"1.0.0\"}}}"; + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json); + + // Read and parse SSE data directly as it arrives + char *buffer = NULL; + size_t buffer_size = 0; + buffer = malloc(1024); // Initial buffer size + + if (curl_easy_perform(curl)) { + curl_slist_free_all(headers); + free(buffer); + curl_easy_cleanup(curl); + curl_global_cleanup(); + fprintf(stderr, "Failed to perform request\n"); + return 1; + } else { + { + struct curl_header *h; + struct curl_header *prev = NULL; + do { + h = curl_easy_nextheader(curl, CURLH_HEADER, -1, prev); + if(h) + printf(" %s: %s (%u)\n", h->name, h->value, (unsigned int)h->amount); + prev = h; + } while(h); + } + + skip_to_data(&res_data); + printf("%s\n", res_data.data); + curl_slist_free_all(headers); + free(buffer); + curl_easy_cleanup(curl); + curl_global_cleanup(); + return 0; + } + } else { + fprintf(stderr, "Could not init curl\n"); + return 1; + } + + + return 0; +} + + +//----------------------------------------------------------------------------- +// main +//----------------------------------------------------------------------------- + + +int main(int argc, char **argv) +{ + // AF_INET SOCK_STREAM + //printf("%zu\n", sizeof(socklen_t)); + request("localhost", "8080"); + //post("http://localhost:8080/mcp"); + return 0; +} + diff --git a/src/strcmp.c b/src/strcmp.c new file mode 100644 index 0000000..6f289c8 --- /dev/null +++ b/src/strcmp.c @@ -0,0 +1,43 @@ +#include "strcmp.h" + +#include +#include + +inline static int cmpchcasei(char a, char b) +{ + if ( + ((a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z')) && + ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) + ) { + a = tolower(a); + b = tolower(b); + } + return a - b; +} + + +int cmpstrcasei(const char *lhs, const char *rhs) +{ + while (*lhs != '\0' && *rhs != '\0') { + int res = cmpchcasei(*lhs, *rhs); + if (res) + return res; + lhs++; + rhs++; + } + return cmpchcasei(*lhs, *rhs); +} + + +int cmpstrncasei(const char *lhs, const char *rhs, size_t n) +{ + size_t count = 0; + while (count < n) { + int res = cmpchcasei(lhs[count], rhs[count]); + if (res || lhs[count] == '\0' || rhs[count] == '\0') + return res; + count++; + } + return 0; +} + diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..5d4cce9 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,70 @@ +#include + +#include "utils.h" + + +int sstrtoint(const char *str, size_t size, int *res) +{ + int i = 0; + + if (str == NULL) { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but str was NULL. Wrote 0 in result.\n", __FILE__, __LINE__); + *res = 0; + return 2; + } + if (res == NULL) { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but the result pointer 'res' was NULL. Wrote 0 in result.\n", __FILE__, __LINE__); + return 3; + } + if (size == 0) + { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but the size was set to zero, nothing to convert.\n", __FILE__, __LINE__); + return 4; + } + size_t index = 0; + while (index < size) { + char it = str[index]; + if (it < '0' || it > '9') { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but str is not a valid integer '%s'.\n", __FILE__, __LINE__, str); + *res = i; + return 1; + } + i = i*10 + it - '0'; + index++; + } + *res = i; + return 0; +} + +int cstrtoint(const char *str, int *res) +{ + int i = 0; + const char* it = str; + if (str == NULL) { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but str was NULL. Wrote 0 in result.\n", __FILE__, __LINE__); + *res = 0; + return 2; + } + if (res == NULL) { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but the result pointer 'res' was NULL. Wrote 0 in result.\n", __FILE__, __LINE__); + return 3; + } + if (*str == '\0') + { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but the string is empty, nothing to convert.\n", __FILE__, __LINE__); + return 4; + } + while (*it != '\0') { + if (*it < '0' || *it > '9') { + fprintf(stderr, "%s:%d: Error: tried to cast str to int, but str is not a valid integer '%s'.\n", __FILE__, __LINE__, str); + *res = i; + return 1; + } + i = i*10 + *it - '0'; + it++; + } + *res = i; + return 0; +} + + diff --git a/test.c b/test.c new file mode 100644 index 0000000..bb7caa9 --- /dev/null +++ b/test.c @@ -0,0 +1,16 @@ +#include +#include + +#include "http.h" + +int main() +{ + HTTP_Headers h = http_new_headers(); + http_add_header(&h, "User-Agent", "libmcp:0.1.0"); + http_add_header(&h, "Accept", "application/json, text/event-stream"); + http_add_header(&h, "Content-Type", "application/json"); + char *body = "{\"jsonrpc\": \"2.0\",\"id\": 1,\"method\": \"initialize\",\"params\": {\"protocolVersion\": \"2025-03-26\",\"capabilities\": {\"roots\": {\"listChanged\": true},\"sampling\": {}},\"clientInfo\": {\"name\": \"ExampleClient\",\"version\": \"1.0.0\"}}}"; + size_t body_size = strlen(body); + http_request("http://localhost:8080/mcp", "POST", body, body_size, &h); + return 0; +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..3cb42a9 --- /dev/null +++ b/test.sh @@ -0,0 +1 @@ +gcc -Wall -ggdb -o test test.c src/http.c src/strcmp.c src/utils.c -I./inlcude && ./test