initial commit

This commit is contained in:
eleos 2026-04-27 19:24:31 +02:00
commit ce376eaf63
14 changed files with 2301 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.clangd
.kateproject
__pycache__/
mcp_test.py
test

10
build.sh Executable file
View File

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

8
example.txt Normal file
View File

@ -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\"}}}

718
http2.c Normal file
View File

@ -0,0 +1,718 @@
#include "http2.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
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";
}
}

181
http2.h Normal file
View File

@ -0,0 +1,181 @@
#ifndef __HTTP2_H__
#define __HTTP2_H__
#include <stddef.h>
#include <stdint.h>
#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

169
inlcude/http.h Normal file
View File

@ -0,0 +1,169 @@
#ifndef __HTTP_H__
#define __HTTP_H__
#include <stddef.h>
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__

9
inlcude/strcmp.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef __STRCMP_H__
#define __STRCMP_H__
#include <stddef.h>
int cmpstrcasei(const char *lhs, const char *rhs);
int cmpstrncasei(const char *lhs, const char *rhs, size_t n);
#endif //__STRCMP_H__

154
inlcude/utils.h Normal file
View File

@ -0,0 +1,154 @@
#ifndef __UTILS_H__
#define __UTILS_H__
#include <stdlib.h>
#include <string.h>
#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__

667
src/http.c Normal file
View File

@ -0,0 +1,667 @@
#include "http.h"
#include <netdb.h> // connect getaddrinfo socket (struct addrinfo) AF_UNSPEC SOCK_STREAM send recv freeaddrinfo gai_strerror
#include <stdio.h> // fprintf perror printf stderr
#include <stdlib.h> // free malloc realloc
#include <string.h> // memset strchr strcmp strdup strlen strstr
#include <unistd.h> // 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' '<status_code>
sep = strchr(raw_res, ' ');
if (sep == NULL) {
fprintf(stderr, "%s:%d: Error: Expected to find ' ' between 'HTTP/1.1' and '<status_code>', but there is no ' '.\n", __FILE__, __LINE__);
return 1;
}
len = sep - raw_res;
// <staus_code>' '<status_text>
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 '<status_code>' and '<status_text>', 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_code> + ' '
// <status_text\r
sep = strchr(raw_res, '\r');
if (sep == NULL) {
fprintf(stderr, "%s:%d: Error: Expected to find '\\r\\n' to end the HTTP status line, but didn't found any.\n", __FILE__, __LINE__);
return 1;
}
len = sep - raw_res;
res->status_text = strndup(raw_res, len);
// \r\n
OUT_OF_BOUNDS(base, raw_res, size);
raw_res = sep + 1; // consume <status_text> + '\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;
}

248
src/main.c Normal file
View File

@ -0,0 +1,248 @@
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <curl/curl.h>
/*
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;
}

43
src/strcmp.c Normal file
View File

@ -0,0 +1,43 @@
#include "strcmp.h"
#include <stdio.h>
#include <ctype.h>
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;
}

70
src/utils.c Normal file
View File

@ -0,0 +1,70 @@
#include <stdio.h>
#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;
}

16
test.c Normal file
View File

@ -0,0 +1,16 @@
#include <stdio.h>
#include <string.h>
#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;
}

1
test.sh Executable file
View File

@ -0,0 +1 @@
gcc -Wall -ggdb -o test test.c src/http.c src/strcmp.c src/utils.c -I./inlcude && ./test