commit 35c7a58d92cfa79a848def69671ce95b79a5a874
parent 4b03072593e015c1eef76988a7383db9fa5f2a5c
Author: cebem1nt <mineewarik@gmail.com>
Date: Fri, 21 Nov 2025 22:16:57 -0300
Moving basic parser to the header, small error handling improvements, etc
Diffstat:
| M | httpp.h | | | 240 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| D | imp.c | | | 226 | ------------------------------------------------------------------------------- |
| A | test.c | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 298 insertions(+), 227 deletions(-)
diff --git a/httpp.h b/httpp.h
@@ -4,6 +4,26 @@
*/
#include <stddef.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define HTTP_DELIMETER "\r\n"
+#define HTTP_DELIMETER_SIZE 2
+#define HTTP_VERSION "HTTP/1.1"
+
+#define _HTTPP_LINE_BUFSIZE 4096
+#define _HTTPP_INITIAL_HEADERS_ARR_CAP 20
+
+#define _HTTPP_MAX_METHOD_LENGTH (10 + 1)
+#define _HTTPP_VERISON_BUFSIZE (10 + 1)
+
+#define nomem() do { fprintf(stderr, "No memory, see ya!\n"); exit(1); } while (0)
+#define http_string_to_method(s) (strcmp(s, "GET") == 0 ? 0 : \
+ strcmp(s, "POST") == 0 ? 1 : \
+ strcmp(s, "DELETE") == 0 ? 2 : -1)
typedef enum {
GET,
@@ -29,9 +49,227 @@ typedef struct {
char* body;
} http_req_t;
-http_req_t* http_parse_request(char* raw);
+const char* httpp_method_to_string(http_method_t m);
+http_req_t* httpp_req_new();
+http_req_t* httpp_parse_request(char* raw);
+
+int httpp_parse_header(http_headers_arr_t* hs, char* line);
+void httpp_headers_append(http_headers_arr_t* hs, http_header_t header);
+void httpp_req_free(http_req_t* req);
+
#ifdef LIBPTTH_IMPLEMENTATION
+#define ltrim(str) \
+ while(*(str) && isspace(*(str))) { \
+ (str)++; \
+ }
+
+#define rtrim(str) do { \
+ char* end = (str) + strlen(str) - 1; \
+ while (end >= (str) && isspace(*end)) { \
+ *end = '\0'; \
+ --end; \
+ } \
+ } while (0)
+
+static inline void* emalloc(size_t size)
+{
+ void* ptr = malloc(size);
+ if (!ptr)
+ nomem();
+
+ return ptr;
+}
+
+static inline void* erealloc(void* ptr, size_t size)
+{
+ void* new_ptr = realloc(ptr, size);
+ if (!new_ptr)
+ nomem();
+
+ return ptr;
+}
+
+static int chop_until(char c, char** src, char* dest, size_t n)
+{
+ char* pos = strchr(*src, c);
+ if (!pos)
+ return 1;
+
+ size_t chopped_size = pos - *src;
+ if (chopped_size >= n)
+ return 1;
+
+ memcpy(dest, *src, chopped_size);
+ dest[chopped_size] = '\0';
+
+ *src = pos + 1;
+ return 0;
+}
+
+static char* dchop_until(char c, char** src)
+{
+ char* pos = strchr(*src, c);
+ if (!pos)
+ return NULL;
+
+ size_t chopped_size = pos - *src;
+ char* out = (char*) emalloc(chopped_size + 1);
+
+ memcpy(out, *src, chopped_size);
+ out[chopped_size] = '\0';
+
+ *src = pos + 1;
+ return out;
+}
+
+const char* httpp_method_to_string(http_method_t m)
+{
+ switch (m) {
+ case GET: return "GET";
+ case POST: return "POST";
+ case DELETE: return "DELETE";
+ default: return "UNKNOWN";
+ }
+}
+
+http_req_t* httpp_req_new()
+{
+ http_req_t* out = (http_req_t*) emalloc(sizeof(http_req_t));
+
+ out->headers = (http_headers_arr_t*) emalloc(sizeof(http_headers_arr_t));
+ out->headers->arr = (http_header_t*) emalloc(sizeof(http_header_t) * _HTTPP_INITIAL_HEADERS_ARR_CAP);
+ out->headers->capacity = _HTTPP_INITIAL_HEADERS_ARR_CAP;
+ out->headers->length = 0;
+
+ return out;
+}
+
+void httpp_req_free(http_req_t* req)
+{
+ if (!req) return;
+
+ for (size_t i = 0; i < req->headers->length; i++) {
+ free(req->headers->arr[i].name);
+ free(req->headers->arr[i].value);
+ }
+
+ free(req->headers->arr);
+ free(req->route);
+ free(req->body);
+ free(req);
+}
+
+void httpp_headers_append(http_headers_arr_t* hs, http_header_t header)
+{
+ if (hs->length >= hs->capacity) {
+ size_t new_cap = hs->capacity * 2;
+ hs->arr = (http_header_t*) erealloc(hs->arr, new_cap * sizeof(http_header_t));
+ hs->capacity = new_cap;
+ }
+
+ hs->arr[hs->length++] = header;
+}
+
+int httpp_parse_header(http_headers_arr_t* hs, char* line)
+{
+ char* delim_pos = strchr(line, ':');
+ if (!delim_pos)
+ return 1;
+
+ size_t name_len = (delim_pos - line);
+ if (name_len == 0)
+ return 2;
+
+ char* name = (char*) emalloc(name_len + 1);
+ memcpy(name, line, name_len);
+ name[name_len] = '\0';
+
+ char* value_start = delim_pos + 1;
+ ltrim(value_start);
+
+ char* value = strdup(value_start);
+ if (!value)
+ nomem();
+
+ httpp_headers_append(hs, (http_header_t){name, value});
+ return 0;
+}
+
+static int http_parse_start_line(char** itr, http_req_t* dest)
+{
+ char method_buf[_HTTPP_MAX_METHOD_LENGTH];
+ char version_buf[_HTTPP_VERISON_BUFSIZE];
+
+ ltrim(*itr);
+
+ if (chop_until(' ', itr, method_buf, _HTTPP_MAX_METHOD_LENGTH) != 0)
+ return 1;
+
+ ltrim(*itr) // Route might have extra spaces at the bginning, for our implementation thats fine
+ char* route = dchop_until(' ', itr);
+
+ ltrim(*itr);
+ if (chop_until('\n', itr, version_buf, _HTTPP_VERISON_BUFSIZE) != 0) {
+ free(route);
+ return 1;
+ }
+
+ rtrim(version_buf);
+
+ dest->method = (http_method_t) http_string_to_method(method_buf);
+ dest->route = route;
+
+ if (strcmp(version_buf, HTTP_VERSION) != 0) {
+ free(route);
+ return 1;
+ }
+
+ return 0;
+}
+
+http_req_t* httpp_parse_request(char* raw)
+{
+ http_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)
+ return NULL;
+
+ while (itr < end) {
+ char* del_pos = strstr(itr, HTTP_DELIMETER);
+ if (!del_pos)
+ break;
+
+ size_t line_size = del_pos - itr;
+ if (line_size == 0) {
+ itr = del_pos + HTTP_DELIMETER_SIZE;
+ break;
+ }
+
+ if (line_size >= _HTTPP_LINE_BUFSIZE) {
+ free(out);
+ return NULL;
+ }
+
+ memcpy(line, itr, line_size);
+ line[line_size] = '\0';
+
+ if (httpp_parse_header(out->headers, line) != 0)
+ return NULL;
+
+ itr = del_pos + HTTP_DELIMETER_SIZE;
+ }
+ // TODO, there is a thing called chunked transfer, which is
+ // definitely not handled well by this parser
+ out->body = strdup(itr);
+ if (!out->body)
+ return NULL;
+ return out;
+}
#endif // LIBPTTH_IMPLEMENTATION
\ No newline at end of file
diff --git a/imp.c b/imp.c
@@ -1,225 +0,0 @@
-#include <ctype.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "httpp.h"
-
-#define HTTP_DELIMETER "\r\n"
-#define HTTP_DELIMETER_SIZE 2
-#define HTTP_VERSION "HTTP/1.1"
-
-#define LINE_BUFSIZE 4096
-#define INITIAL_HEADERS_ARR_CAP 20
-
-#define MAX_METHOD_LENGTH (10 + 1)
-#define VERISON_BUFSIZE (10 + 1)
-
-#define nomem() do { fprintf(stderr, "No memory, see ya!\n"); exit(1); } while (0)
-#define http_string_to_method(s) (strcmp(s, "GET") == 0 ? 0 : \
- strcmp(s, "POST") == 0 ? 1 : \
- strcmp(s, "DELETE") == 0 ? 2 : -1)
-
-#define ltrim(str) \
- while(*(str) && isspace(*(str))) { \
- (str)++; \
- }
-
-#define rtrim(str) do { \
- char* end = (str) + strlen(str) - 1; \
- while (end >= (str) && isspace(*end)) { \
- *end = '\0'; \
- --end; \
- } \
- } while (0)
-
-static inline void* emalloc(size_t size)
-{
- void* ptr = malloc(size);
- if (!ptr)
- nomem();
-
- return ptr;
-}
-
-static inline void* erealloc(void* ptr, size_t size)
-{
- void* new_ptr = realloc(ptr, size);
- if (!new_ptr)
- nomem();
-
- return ptr;
-}
-
-static void chop_until(char c, char** src, char* dest, size_t n)
-{
- char* pos = strchr(*src, c);
- if (!pos)
- return;
-
- size_t chopped_size = pos - *src;
- if (chopped_size >= n)
- return;
-
- memcpy(dest, *src, chopped_size);
- dest[chopped_size] = '\0';
-
- *src = pos + 1;
-}
-
-static char* dchop_until(char c, char** src)
-{
- char* pos = strchr(*src, c);
- if (!pos)
- return NULL;
-
- size_t chopped_size = pos - *src;
- char* dest = emalloc(chopped_size + 1);
-
- memcpy(dest, *src, chopped_size);
- dest[chopped_size] = '\0';
-
- *src = pos + 1;
- return dest;
-}
-
-const char* http_method_to_string(http_method_t m)
-{
- switch (m) {
- case GET: return "GET";
- case POST: return "POST";
- case DELETE: return "DELETE";
- default: return "UNKNOWN";
- }
-}
-
-http_req_t* http_req_new()
-{
- http_req_t* new = emalloc(sizeof(http_req_t));
-
- new->headers = emalloc(sizeof(http_headers_arr_t));
- new->headers->arr = emalloc(sizeof(http_header_t) * INITIAL_HEADERS_ARR_CAP);
- new->headers->capacity = INITIAL_HEADERS_ARR_CAP;
- new->headers->length = 0;
-
- return new;
-}
-
-void http_headers_append(http_headers_arr_t* hs, http_header_t header)
-{
- if (hs->length >= hs->capacity) {
- size_t new_cap = hs->capacity * 2;
- hs->arr = erealloc(hs->arr, new_cap * sizeof(http_header_t));
- hs->capacity = new_cap;
- }
-
- hs->arr[hs->length++] = header;
-}
-
-void http_parse_header(http_headers_arr_t* hs, char* line)
-{
- char* delim_pos = strchr(line, ':');
- if (!delim_pos)
- return;
-
- size_t name_len = (delim_pos - line);
- if (name_len == 0)
- return;
-
- char* name = emalloc(name_len + 1);
- memcpy(name, line, name_len);
- name[name_len] = '\0';
-
- char* value_start = delim_pos + 1;
- ltrim(value_start);
-
- char* value = strdup(value_start);
- if (!value)
- nomem();
-
- http_headers_append(hs, (http_header_t){name, value});
-}
-
-void http_parse_start_line(char** itr, http_req_t* dest)
-{
- char method_buf[MAX_METHOD_LENGTH];
- char version_buf[VERISON_BUFSIZE];
-
- ltrim(*itr);
-
- chop_until(' ', itr, method_buf, MAX_METHOD_LENGTH);
- char* route = dchop_until(' ', itr);
-
- ltrim(*itr) // Route might have extra spaces at the bginning, for our implementation thats fine
- chop_until('\n', itr, version_buf, VERISON_BUFSIZE);
- rtrim(version_buf);
-
- dest->method = http_string_to_method(method_buf);
- dest->route = route;
-
- if (strcmp(version_buf, HTTP_VERSION) != 0) {
- fprintf(stderr, "Unsupported protocol version\n");
- exit(1);
- }
-}
-
-http_req_t* http_parse_request(char* raw)
-{
- http_req_t* out = http_req_new();
-
- char* itr = raw;
- char* end = raw + strlen(raw);
- char line[LINE_BUFSIZE];
-
- http_parse_start_line(&itr, out);
-
- while (itr < end) {
- char* del_pos = strstr(itr, HTTP_DELIMETER);
- if (!del_pos)
- break;
-
- size_t line_size = del_pos - itr;
- if (line_size >= LINE_BUFSIZE)
- return NULL;
-
- memcpy(line, itr, line_size);
- line[line_size] = '\0';
-
- http_parse_header(out->headers, line);
- itr = del_pos + HTTP_DELIMETER_SIZE;
- }
-
- out->body = strdup(itr);
- return out;
-}
-
-int main()
-{
- char* req =
- "POST /api/items HTTP/1.1\r\n"
- "Host: api.example.com\r\n"
- "User-Agent: MyClient/1.0\r\n"
- "Content-Type: application/json\r\n"
- "Content-Length: 48\r\n"
- "\r\n"
- "{\"name\":\"Widget\",\"quantity\":10,\"price\":9.99}";
-
- http_req_t* parsed = http_parse_request(req);
-
- printf("Method: %i (%s)\n", parsed->method, http_method_to_string(parsed->method));
- printf("Route: %s\n", parsed->route);
-
- printf("Parsed headers length: %lu\n", parsed->headers->length);
- for (size_t i = 0; i < parsed->headers->length; i++) {
- printf("Header %lu:\n", i);
- printf(" Name: :%s:\n", parsed->headers->arr[i].name);
- printf(" Value: :%s:\n", parsed->headers->arr[i].value);
- }
-
- printf("\n--- Parsed body: ---\n");
- printf("%s\n", parsed->body);
- printf("----------------------\n");
-
- return 0;
-}
-\ No newline at end of file
diff --git a/test.c b/test.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+
+#define LIBPTTH_IMPLEMENTATION
+#include "httpp.h"
+
+void print(http_req_t* parsed)
+{
+ if (parsed == NULL) {
+ printf("Couldn't parse!\n");
+ return;
+ }
+
+ printf("Method: %i (%s)\n", parsed->method, httpp_method_to_string(parsed->method));
+ printf("Route: %s\n", parsed->route);
+
+ printf("Parsed headers length: %lu\n", parsed->headers->length);
+ for (size_t i = 0; i < parsed->headers->length; i++) {
+ printf("Header %lu:\n", i);
+ printf(" Name: :%s:\n", parsed->headers->arr[i].name);
+ printf(" Value: :%s:\n", parsed->headers->arr[i].value);
+ }
+
+ printf("\n---- Parsed body: ----\n");
+ printf("%s\n", parsed->body);
+ printf("-------- (%lu) --------\n", strlen(parsed->body));
+}
+
+int main()
+{
+ char* req1 =
+ "POST /api/items HTTP/1.1 \r\n"
+ "Host: api.example.com\r\n"
+ "User-Agent: MyClient/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 48\r\n"
+ "\r\n"
+ "{\"name\":\"Widget\",\"quantity\":10,\"price\":9.99}";
+
+ http_req_t* parsed = httpp_parse_request(req1);
+ print(parsed);
+ httpp_req_free(parsed);
+
+ char* req2 =
+ "POST /api/items HTTP/1.1\r\n"
+ "Host: api.example.com\r\n"
+ "User-Agent: MyClient/1.0\r\n"
+ "Content-Type: application/json; charset=utf-8\r\n"
+ "Content-Length: 106\r\n"
+ "X-Trace-ID: ;;--TRACE--;;\r\n"
+ "X-Feature-Flags: ,enable-new, ,\r\n"
+ "\r\n"
+ "{\"name\":\"SpicyWidget\",\"quantity\":-1,\"price\":9.9900,\"tags\":[\"hot\",\"ßpecial\",\"null\",null],\"meta\":{\"note\":\"line1\\nline2\\r\\nline3\",\"empty\":\"\",\"unicode\":\"🔥🚀\",\"quote_test\":\"She said: \\\"Spicy!\\\"\"}}";
+
+ parsed = httpp_parse_request(req2);
+ print(parsed);
+
+ return 0;
+}
+\ No newline at end of file