initial commit
This commit is contained in:
commit
ce376eaf63
|
|
@ -0,0 +1,7 @@
|
|||
.clangd
|
||||
.kateproject
|
||||
__pycache__/
|
||||
|
||||
mcp_test.py
|
||||
test
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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\"}}}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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__
|
||||
|
||||
|
|
@ -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__
|
||||
|
|
@ -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__
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue