commit 68f1f8f566348136c6f1d5e769708cb148229efa
parent 223a7eee1a93f61f1cd381cdc790faacfc6fd3be
Author: cebem1nt <mineewarik@gmail.com>
Date: Tue, 25 Nov 2025 14:59:21 -0300
Added responses handling, bugfixes and improvements
Diffstat:
| M | httpp.h | | | 296 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
| M | test.c | | | 18 | ++++++++++++++++-- |
2 files changed, 252 insertions(+), 62 deletions(-)
diff --git a/httpp.h b/httpp.h
@@ -1,6 +1,6 @@
/*
- * Tiny header only http parser library for
- * http 1/1 version
+ * Tiny header only http parser library for
+ * http 1/1 version (enums don't respect your namespace much, sorry)
*/
#include <stddef.h>
@@ -14,53 +14,84 @@
#define HTTPP_MAX_METHOD_LENGTH (10 + 1)
#define HTTPP_VERSION_BUFSIZE (10 + 1)
+#define HTTPP_SUPPORTED_VERSION "HTTP/1.1"
+
+#define _HTTP_DELIMITER "\r\n"
+#define _HTTP_DELIMITER_SIZE 2
+#define _HTTP_MAX_STATUS_CODE_SIZE 3
#define httpp_string_to_method(s) (strcmp(s, "GET") == 0 ? 0 : \
strcmp(s, "POST") == 0 ? 1 : \
strcmp(s, "DELETE") == 0 ? 2 : -1)
#define HTTPP_ERRMEMRY 3
-#define HTTPP_ERRLOGIC 1
+#define HTTPP_ERRLOGIC 2
+#define HTTPP_ERRDEFLT 1
typedef enum {
GET,
POST,
DELETE,
UNKNOWN = -1
-} http_method_t;
+} httpp_method_t;
+
+typedef enum {
+ Continue = 100,
+ Switching_Protocols,
+
+ Ok = 200,
+ Created,
+ Accepted,
+
+ Bad_Request = 400,
+ Unauthorized,
+ Payment_Required, // Ahahaha
+ Forbiden,
+ Not_Found,
+ Unspecified = -1
+} httpp_status_t;
typedef struct {
char* name;
char* value;
-} http_header_t;
+} httpp_header_t;
typedef struct {
- http_header_t* arr;
+ httpp_header_t* arr;
size_t capacity;
size_t length;
-} http_headers_arr_t;
+} httpp_headers_arr_t;
typedef struct {
- http_method_t method;
- http_headers_arr_t* headers;
+ httpp_method_t method;
+ httpp_headers_arr_t* headers;
char* route;
char* body;
-} http_req_t;
+} httpp_req_t;
-const char* httpp_method_to_string(http_method_t m);
-http_req_t* httpp_req_new();
-http_req_t* httpp_parse_request(char* raw);
+typedef struct {
+ httpp_headers_arr_t* headers;
+ httpp_status_t code;
+ char* body;
+} httpp_res_t;
+
+const char* httpp_method_to_string(httpp_method_t m);
+httpp_req_t* httpp_req_new();
+httpp_req_t* httpp_parse_request(char* raw);
+
+int httpp_parse_header(httpp_headers_arr_t* hs, char* line);
+int httpp_headers_append(httpp_headers_arr_t* hs, httpp_header_t header);
+int httpp_headers_add(httpp_headers_arr_t* hs, char* name, char* value);
+void httpp_headers_arr_free(httpp_headers_arr_t* hs);
+void httpp_req_free(httpp_req_t* req);
-int httpp_parse_header(http_headers_arr_t* hs, char* line);
-int httpp_headers_append(http_headers_arr_t* hs, http_header_t header);
-void http_headers_arr_free(http_headers_arr_t* hs);
-void httpp_req_free(http_req_t* req);
+httpp_res_t* httpp_res_new();
+void httpp_res_free(httpp_res_t* res);
+
+int httpp_res_set_body(httpp_res_t* res, char* body);
+char* httpp_res_to_raw(httpp_res_t* res);
-#define HTTPP_IMPLEMENTATION
#ifdef HTTPP_IMPLEMENTATION
-#define HTTP_DELIMITER "\r\n"
-#define HTTP_DELIMITER_SIZE 2
-#define HTTP_VERSION "HTTP/1.1"
#define trim(str) do { ltrim(str); rtrim(str); } while (0)
@@ -84,11 +115,11 @@ static int chop_until(char c, char** src, char* dest, size_t n)
{
char* pos = strchr(*src, c);
if (!pos)
- return 1;
+ return HTTPP_ERRDEFLT;
size_t chopped_size = pos - *src;
if (chopped_size >= n)
- return 1;
+ return HTTPP_ERRDEFLT;
memcpy(dest, *src, chopped_size);
dest[chopped_size] = '\0';
@@ -115,56 +146,84 @@ static char* dchop_until(char c, char** src)
return out;
}
-const char* httpp_method_to_string(http_method_t m)
+const char* httpp_method_to_string(httpp_method_t m)
{
switch (m) {
- case GET: return "GET";
- case POST: return "POST";
- case DELETE: return "DELETE";
- default: return "UNKNOWN";
+ case GET: return "GET";
+ case POST: return "POST";
+ case DELETE: return "DELETE";
+ default: return "UNKNOWN";
}
}
-http_req_t* httpp_req_new()
+const char* httpp_status_to_string(httpp_status_t s)
{
- http_req_t* out = (http_req_t*) malloc(sizeof(http_req_t));
- if (!out)
- return NULL;
+ switch (s) {
+ case Continue: return "Continue";
+ case Switching_Protocols: return "Switching Protocols";
+
+ case Ok: return "OK";
+ case Created: return "Created";
+ case Accepted: return "Accepted";
+
+ case Bad_Request: return "Bad Request";
+ case Unauthorized: return "Unauthorized";
+ case Payment_Required: return "Payment Required";
+ case Forbiden: return "Forbidden";
+ case Not_Found: return "Not Found";
+
+ case Unspecified:
+ default: return "Unspecified";
+ }
+}
- out->headers = (http_headers_arr_t*) malloc(sizeof(http_headers_arr_t));
- if (!out->headers) {
+
+httpp_headers_arr_t* httpp_headers_arr_new()
+{
+ httpp_headers_arr_t* out = (httpp_headers_arr_t*) malloc(sizeof(httpp_headers_arr_t));
+
+ out->arr = (httpp_header_t*) malloc(sizeof(httpp_header_t) * HTTPP_INITIAL_HEADERS_ARR_CAP);
+ if (!out->arr) {
free(out);
return NULL;
}
- out->headers->arr = (http_header_t*) malloc(sizeof(http_header_t) * HTTPP_INITIAL_HEADERS_ARR_CAP);
- if (!out->headers->arr) {
- free(out->headers);
+ out->capacity = HTTPP_INITIAL_HEADERS_ARR_CAP;
+ out->length = 0;
+ return out;
+}
+
+httpp_req_t* httpp_req_new()
+{
+ httpp_req_t* out = (httpp_req_t*) malloc(sizeof(httpp_req_t));
+ if (!out)
+ return NULL;
+
+ out->headers = httpp_headers_arr_new();
+ if (!out->headers) {
free(out);
return NULL;
}
- out->headers->capacity = HTTPP_INITIAL_HEADERS_ARR_CAP;
- out->headers->length = 0;
out->route = NULL;
out->body = NULL;
-
return out;
}
-void http_headers_arr_free(http_headers_arr_t* hs)
+void httpp_headers_arr_free(httpp_headers_arr_t* hs)
{
if (!hs) return;
+
for (size_t i = 0; i < hs->length; i++) {
free(hs->arr[i].name);
free(hs->arr[i].value);
}
}
-void httpp_req_free(http_req_t* req)
+void httpp_req_free(httpp_req_t* req)
{
if (!req) return;
- http_headers_arr_free(req->headers);
+ httpp_headers_arr_free(req->headers);
free(req->headers->arr);
free(req->headers);
@@ -173,25 +232,41 @@ void httpp_req_free(http_req_t* req)
free(req);
}
-int httpp_headers_append(http_headers_arr_t* hs, http_header_t header)
+int httpp_headers_append(httpp_headers_arr_t* hs, httpp_header_t header)
{
if (hs->length >= hs->capacity) {
size_t new_cap = hs->capacity * 2;
if (new_cap <= hs->capacity) // Doesn't free on failure. Make a note about it
return HTTPP_ERRMEMRY;
- hs->arr = (http_header_t*) realloc(hs->arr, new_cap * sizeof(http_header_t));
+ hs->arr = (httpp_header_t*) realloc(hs->arr, new_cap * sizeof(httpp_header_t));
if (!hs->arr)
return HTTPP_ERRMEMRY;
hs->capacity = new_cap;
}
+ if (!header.name || !header.value)
+ return HTTPP_ERRLOGIC;
+
hs->arr[hs->length++] = header;
return 0;
}
-int httpp_parse_header(http_headers_arr_t* hs, char* line)
+int httpp_headers_add(httpp_headers_arr_t* hs, char* name, char* value)
+{
+ if (!name || !value)
+ return HTTPP_ERRLOGIC;
+
+ httpp_header_t h = {
+ strdup(name),
+ strdup(value)
+ };
+
+ return httpp_headers_append(hs, h);
+}
+
+int httpp_parse_header(httpp_headers_arr_t* hs, char* line)
{
char* delim_pos = strchr(line, ':');
if (!delim_pos)
@@ -216,13 +291,13 @@ int httpp_parse_header(http_headers_arr_t* hs, char* line)
if (!value)
return HTTPP_ERRMEMRY;
- if (httpp_headers_append(hs, (http_header_t){name, value}) != 0)
+ if (httpp_headers_append(hs, (httpp_header_t){name, value}) != 0)
return HTTPP_ERRMEMRY;
return 0;
}
-static int http_parse_start_line(char** itr, http_req_t* dest)
+static int parse_start_line(char** itr, httpp_req_t* dest)
{
char method_buf[HTTPP_MAX_METHOD_LENGTH];
char version_buf[HTTPP_VERSION_BUFSIZE];
@@ -230,50 +305,50 @@ static int http_parse_start_line(char** itr, http_req_t* dest)
ltrim(*itr);
if (chop_until(' ', itr, method_buf, HTTPP_MAX_METHOD_LENGTH) != 0)
- return 1;
+ return HTTPP_ERRDEFLT;
ltrim(*itr); // Route might have extra spaces at the bginning, for our implementation thats fine
if ((route = dchop_until(' ', itr)) == NULL)
- return 1;
+ return HTTPP_ERRDEFLT;
ltrim(*itr);
if (chop_until('\n', itr, version_buf, HTTPP_VERSION_BUFSIZE) != 0) {
free(route);
- return 1;
+ return HTTPP_ERRDEFLT;
}
trim(version_buf);
- if (strcmp(version_buf, HTTP_VERSION) != 0) {
+ if (strcmp(version_buf, HTTPP_SUPPORTED_VERSION) != 0) {
free(route);
- return 1;
+ return HTTPP_ERRDEFLT;
}
- dest->method = (http_method_t) httpp_string_to_method(method_buf);
+ dest->method = (httpp_method_t) httpp_string_to_method(method_buf);
dest->route = route;
return 0;
}
-http_req_t* httpp_parse_request(char* raw)
+httpp_req_t* httpp_parse_request(char* raw)
{
- http_req_t* out = httpp_req_new();
+ httpp_req_t* out = httpp_req_new();
char* itr = raw;
char* end = raw + strlen(raw);
char line[HTTPP_LINE_BUFSIZE];
- if (http_parse_start_line(&itr, out) != 0) {
+ if (parse_start_line(&itr, out) != 0) {
httpp_req_free(out);
return NULL;
}
while (itr < end) {
- char* del_pos = strstr(itr, HTTP_DELIMITER);
+ char* del_pos = strstr(itr, _HTTP_DELIMITER);
if (!del_pos)
break;
size_t line_size = del_pos - itr;
if (line_size == 0) {
- itr = del_pos + HTTP_DELIMITER_SIZE;
+ itr = del_pos + _HTTP_DELIMITER_SIZE;
break;
}
@@ -290,7 +365,7 @@ http_req_t* httpp_parse_request(char* raw)
return NULL;
}
- itr = del_pos + HTTP_DELIMITER_SIZE;
+ itr = del_pos + _HTTP_DELIMITER_SIZE;
}
// TODO, there is a thing called chunked transfer, which is
@@ -303,4 +378,105 @@ http_req_t* httpp_parse_request(char* raw)
return out;
}
+
+// --- Responses part ---
+httpp_res_t* httpp_res_new()
+{
+ httpp_res_t* out = (httpp_res_t*) malloc(sizeof(httpp_res_t));
+ if (!out)
+ return NULL;
+
+ out->headers = httpp_headers_arr_new();
+ if (!out->headers)
+ return NULL;
+
+ out->code = Unspecified;
+ return out;
+}
+
+void httpp_res_free(httpp_res_t* res)
+{
+ if (!res)
+ return;
+
+ httpp_headers_arr_free(res->headers);
+ free(res->body);
+}
+
+int httpp_res_set_body(httpp_res_t* res, char* body)
+{
+ res->body = strdup(body);
+ if (!res->body)
+ return 1;
+
+ return 0;
+}
+
+char* httpp_res_to_raw(httpp_res_t* res)
+{
+ if (res == NULL || res->headers == NULL)
+ return NULL;
+
+ if (res->code == -1)
+ return NULL;
+
+ const char* status_msg = httpp_status_to_string(res->code);
+ size_t out_size =
+ strlen(HTTPP_SUPPORTED_VERSION)
+ + 1 // Space
+ + _HTTP_MAX_STATUS_CODE_SIZE
+ + 1 // Space
+ + strlen(status_msg)
+ + _HTTP_DELIMITER_SIZE;
+
+ for (size_t i = 0; i < res->headers->length; i++) {
+ httpp_header_t header = res->headers->arr[i];
+ if (!header.name || !header.value)
+ continue;
+
+ out_size += strlen(header.name) + 2 // :
+ + strlen(header.value)
+ + _HTTP_DELIMITER_SIZE;
+ }
+
+ out_size += _HTTP_DELIMITER_SIZE;
+ if (res->body)
+ out_size += strlen(res->body);
+
+ out_size += 1; // '\0'
+ char* out = (char*) malloc(out_size);
+ if (!out)
+ return NULL;
+
+ int written = snprintf(out, out_size, "%s %d %s\r\n", HTTPP_SUPPORTED_VERSION, res->code, status_msg);
+ if (written < 0 || (size_t) written >= out_size) {
+ free(out);
+ return NULL;
+ }
+
+ size_t offset = written;
+
+ for (size_t i = 0; i < res->headers->length; i++) {
+ httpp_header_t header = res->headers->arr[i];
+ if (!header.name || !header.value)
+ continue;
+
+ int n = snprintf(out + offset, out_size - offset,
+ "%s: %s\r\n", header.name, header.value);
+
+ if (n < 0 || (size_t) n >= out_size - offset) {
+ free(out);
+ return NULL;
+ }
+
+ offset += n;
+ }
+
+ strcat(out, _HTTP_DELIMITER);
+ if (res->body)
+ strcat(out, res->body);
+
+ return out;
+}
+
#endif // HTTPP_IMPLEMENTATION
\ No newline at end of file
diff --git a/test.c b/test.c
@@ -1,8 +1,9 @@
#include <stdio.h>
+
#define HTTPP_IMPLEMENTATION
#include "httpp.h"
-void print(http_req_t* parsed)
+void print(httpp_req_t* parsed)
{
if (parsed == NULL) {
printf("Couldn't parse!\n");
@@ -35,7 +36,7 @@ int main()
"\r\n"
"{\"name\":\"Widget\",\"quantity\":10,\"price\":9.99}";
- http_req_t* parsed = httpp_parse_request(req1);
+ httpp_req_t* parsed = httpp_parse_request(req1);
print(parsed);
httpp_req_free(parsed);
@@ -67,5 +68,18 @@ int main()
parsed = httpp_parse_request(req3);
print(parsed);
+ httpp_res_t* res = httpp_res_new();
+
+ res->code = 200;
+ httpp_headers_add(res->headers, "Host", "idk.me.com");
+ httpp_headers_add(res->headers, "Home", "pkeofkwekgfwktokwt9wt293430592304");
+ httpp_headers_add(res->headers, "SOmethin", "afkofkeokfoekfo");
+ httpp_res_set_body(res, "{\"hello\": 123}\n");
+
+ char* raw = httpp_res_to_raw(res);
+ printf("-----------\n");
+ printf("%s", raw);
+ printf("-----------\n");
+
return 0;
}
\ No newline at end of file