http-parser.c

Small C library to parse HTTP requests
Log | Files | Refs | README | LICENSE

commit 5729f8a16f39c55bb7fa0772689e6c0dd4056a86
parent 9f67ed0c4a5dec754fc68e0b7903cf413bada14a
Author: finwo <finwo@pm.me>
Date:   Sun, 18 Feb 2024 22:03:10 +0100

Replaced linked-list headers with mindex tags for flexibility

Diffstat:
Mpackage.ini | 1+
Msrc/http-parser.c | 208++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/http-parser.h | 15+++++++--------
Mtest.c | 10+++++++++-
4 files changed, 134 insertions(+), 100 deletions(-)

diff --git a/package.ini b/package.ini @@ -1,5 +1,6 @@ [dependencies] finwo/asprintf=https://github.com/finwo/c-asprintf/archive/refs/tags/edge.tar.gz +finwo/mindex=https://github.com/finwo/c-mindex/archive/refs/tags/edge.tar.gz finwo/str_extra=https://github.com/finwo/c-strextra/archive/refs/tags/edge.tar.gz tidwall/buf=https://raw.githubusercontent.com/finwo/dep-repository/main/tidwall/buf/package.ini [export] diff --git a/src/http-parser.c b/src/http-parser.c @@ -1,3 +1,5 @@ +// vim:fdm=marker:fdl=0 + #ifdef __cplusplus extern "C" { #endif @@ -24,6 +26,7 @@ extern "C" { #endif #endif +// xtoi {{{ /** * Convert hexidecimal string to int */ @@ -54,29 +57,68 @@ int xtoi(char *str) { return sign * i; } +// }}} -/** - * Frees everything in a header that was malloc'd by http-parser - */ -void http_parser_header_free(struct http_parser_header *header) { - if (header->next) http_parser_header_free(header->next); - if (header->key) free(header->key); - if (header->value) free(header->value); - free(header); +// non-exported structs {{{ +struct http_parser_tag { + char *key; + char *value; +}; +// }}} + +// Tag management {{{ + +static int fn_tag_cmp(const void *a, const void *b, void *udata) { + const struct http_parser_tag *ta = (struct http_parser_tag *)a; + const struct http_parser_tag *tb = (struct http_parser_tag *)b; + return strcasecmp(ta->key, tb->key); } -/** - * Internal, returns the pointer to a header entry that matches the key - */ -struct http_parser_header * _http_parser_header_get(struct http_parser_message *subject, const char *key) { - struct http_parser_header *header = subject->headers; - while(header) { - if (!strcasecmp(header->key, key)) { - return header; - } - header = header->next; - } - return NULL; +static void fn_tag_purge(void *item, void *udata) { + struct http_parser_tag *subject = (struct http_parser_tag *)item; + free(subject->key); + free(subject->value); + free(subject); +} + +struct http_parser_tag * _http_parser_tag_get(struct http_parser_message *subject, const char *key) { + struct http_parser_tag pattern = { .key = (char*)key }; + return mindex_get(subject->meta, &pattern); +} +const char * http_parser_tag_get(struct http_parser_message *subject, const char *key) { + struct http_parser_tag *found = _http_parser_tag_get(subject, key); + if (!found) return NULL; + return found->value; +} + +void _http_parser_tag_set(struct http_parser_message *subject, const char *key, const char *value) { + struct http_parser_tag *tag = calloc(1, sizeof(struct http_parser_tag)); + tag->key = strdup(key); + tag->value = strdup(value); + mindex_set(subject->meta, tag); +} +void http_parser_tag_set(struct http_parser_message *subject, const char *key, const char *value) { + _http_parser_tag_set(subject, key, value); +} + +void _http_parser_tag_del(struct http_parser_message *subject, const char *key) { + struct http_parser_tag pattern = { .key = (char*)key }; + mindex_delete(subject->meta, &pattern); +} +void http_parser_tag_del(struct http_parser_message *subject, const char *key) { + _http_parser_tag_del(subject, key); +} +// }}} + +// Header management {{{ + +struct http_parser_tag * _http_parser_header_get(struct http_parser_message *subject, const char *key) { + char *tag = calloc(strlen(key) + 8, sizeof(char)); + strcat(tag, "header:"); + strcat(tag, key); + struct http_parser_tag *result = _http_parser_tag_get(subject, tag); + free(tag); + return result; } /** @@ -84,64 +126,40 @@ struct http_parser_header * _http_parser_header_get(struct http_parser_message * * Returns the header's value or NULL if not found */ const char *http_parser_header_get(struct http_parser_message *subject, const char *key) { - struct http_parser_header *header = _http_parser_header_get(subject, key); + struct http_parser_tag *header = _http_parser_header_get(subject, key); if (!header) return NULL; - char *value = header->value; - while(*value == ' ') value++; - return value; + return header->value; +} + +void _http_parser_header_set(struct http_parser_message *subject, const char *key, const char *value) { + char *tag = calloc(strlen(key) + 8, sizeof(char)); + strcat(tag, "header:"); + strcat(tag, key); + _http_parser_tag_set(subject, tag, value); + free(tag); } /** * Write a header into the subject's list of headers */ void http_parser_header_set(struct http_parser_message *subject, const char *key, const char *value) { - struct http_parser_header *header = _http_parser_header_get(subject, key); - if (header) { - free(header->value); - header->value = strdup(value); - } else { - http_parser_header_add(subject, key, value); - } + return _http_parser_header_set(subject, key, value); } -/** - * Add a header into the subject's list of headers - */ -void http_parser_header_add(struct http_parser_message *subject, const char *key, const char *value) { - struct http_parser_header *header = malloc(sizeof(struct http_parser_header)); - header->key = strdup(key); - header->value = strdup(value); - header->next = subject->headers; - subject->headers = header; +void _http_parser_header_del(struct http_parser_message *subject, const char *key) { + char *tag = calloc(strlen(key) + 8, sizeof(char)); + strcat(tag, "header:"); + strcat(tag, key); + _http_parser_tag_del(subject, tag); + free(tag); } -/** - * Write a header into the subject's list of headers - */ void http_parser_header_del(struct http_parser_message *subject, const char *key) { - struct http_parser_header *header_prev = NULL; - struct http_parser_header *header_cur = subject->headers; - while(header_cur) { - if (strcasecmp(header_cur->key, key) == 0) { - if (header_prev) { - header_prev->next = header_cur->next; - } else { - subject->headers = header_cur->next; - } - header_cur->next = NULL; - http_parser_header_free(header_cur); - if (header_prev) { - header_cur = header_prev->next; - } else { - header_cur = subject->headers; - } - continue; - } - header_prev = header_cur; - header_cur = header_cur->next; - } + _http_parser_header_del(subject, key); } +// }}} + /** * Frees everything in a http_message that was malloc'd by http-parser */ @@ -150,7 +168,7 @@ void http_parser_message_free(struct http_parser_message *subject) { if (subject->path ) free(subject->path); if (subject->version) free(subject->version); if (subject->body ) { buf_clear(subject->body); free(subject->body); } - if (subject->headers) http_parser_header_free(subject->headers); + if (subject->meta ) mindex_free(subject->meta); if (subject->buf ) free(subject->buf); free(subject); } @@ -170,6 +188,11 @@ void http_parser_pair_free(struct http_parser_pair *pair) { struct http_parser_message * http_parser_request_init() { struct http_parser_message *message = calloc(1, sizeof(struct http_parser_message)); message->chunksize = -1; + message->meta = mindex_init( + fn_tag_cmp, + fn_tag_purge, + NULL + ); return message; } @@ -228,7 +251,6 @@ static void http_parser_message_remove_body_string(struct http_parser_message *m * Caution: does not support multi-line headers yet */ static int http_parser_message_read_header(struct http_parser_message *message) { - struct http_parser_header *header; char *index; // Require more data if no line break found @@ -242,29 +264,22 @@ static int http_parser_message_read_header(struct http_parser_message *message) return 0; } - // Prepare new header - header = calloc(1,sizeof(struct http_parser_message)); - header->key = calloc(1,strlen(message->body->data)); // Using strlen, due to possible \r\n replacement - header->value = calloc(1,strlen(message->body->data)); // Using strlen, due to possible \r\n replacement - // Detect colon index = strstr(message->body->data, ": "); if (!index) { - http_parser_header_free(header); http_parser_message_remove_body_string(message); return 1; } - // Copy key & value + // Split by the found colon & skip leading whitespace *(index) = '\0'; - strcpy(header->key, message->body->data); - strcpy(header->value, index + 2); + index++; + while(*(index) == ' ') index++; - // Assign to the header list - header->next = message->headers; - message->headers = header; + // Insert the header in our map + _http_parser_header_set(message, message->body->data, index); - // Remove the header line + // Remove the header remainder // Twice, because we split the string http_parser_message_remove_body_string(message); http_parser_message_remove_body_string(message); @@ -365,14 +380,6 @@ struct buf * http_parser_sprint_pair_request(struct http_parser_pair *pair) { return http_parser_sprint_request(pair->request); } -void _http_parser_sprint_headers(char *target, struct http_parser_header *header) { - if (header->next) _http_parser_sprint_headers(target, header->next); - strcat(target, header->key); - strcat(target, ": "); - strcat(target, header->value); - strcat(target, "\r\n"); -} - // Caution: headers should fit in 64k struct buf * http_parser_sprint_response(struct http_parser_message *response) { struct buf *result = calloc(1, sizeof(struct buf)); @@ -387,8 +394,18 @@ struct buf * http_parser_sprint_response(struct http_parser_message *response) { , response->statusMessage ? response->statusMessage : http_parser_status_message(response->status) ); - if (response->headers) { - _http_parser_sprint_headers(result->data, response->headers); + // Headers + int i; + struct http_parser_tag *tag; + if (response->meta) { + for(i=0; i<mindex_length(response->meta); i++) { + tag = mindex_nth(response->meta, i); + if (strncmp("header:", tag->key, 7)) continue; + strcat(result->data, (tag->key + 7)); + strcat(result->data, ": "); + strcat(result->data, tag->value); + strcat(result->data, "\r\n"); + } } strcat(result->data, "\r\n"); @@ -439,8 +456,17 @@ struct buf * http_parser_sprint_request(struct http_parser_message *request) { strcat(result->data, "\r\n"); // Headers - if (request->headers) { - _http_parser_sprint_headers(result->data, request->headers); + int i; + struct http_parser_tag *tag; + if (request->meta) { + for(i=0; i<mindex_length(request->meta); i++) { + tag = mindex_nth(request->meta, i); + if (strncmp("header:", tag->key, 7)) continue; + strcat(result->data, (tag->key + 7)); + strcat(result->data, ": "); + strcat(result->data, tag->value); + strcat(result->data, "\r\n"); + } } strcat(result->data, "\r\n"); diff --git a/src/http-parser.h b/src/http-parser.h @@ -12,14 +12,9 @@ extern "C" { #define HTTP_PARSER_STATE_DONE 4 #define HTTP_PARSER_STATE_PANIC 666 +#include "finwo/mindex.h" #include "tidwall/buf.h" -struct http_parser_header { - void *next; - char *key; - char *value; -}; - struct http_parser_event { struct http_parser_message *request; struct http_parser_message *response; @@ -36,7 +31,7 @@ struct http_parser_message { char *path; char *query; char *version; - struct http_parser_header *headers; + struct mindex_t *meta; struct buf *body; struct buf *buf; int chunksize; @@ -53,10 +48,14 @@ struct http_parser_pair { void (*onResponse)(struct http_parser_event*); }; +// Tag management +const char * http_parser_tag_get(struct http_parser_message *subject, const char *key); +void http_parser_tag_set(struct http_parser_message *subject, const char *key, const char *value); +void http_parser_tag_del(struct http_parser_message *subject, const char *key); + // Header management const char * http_parser_header_get(struct http_parser_message *subject, const char *key); void http_parser_header_set(struct http_parser_message *subject ,const char *key, const char *value); -void http_parser_header_add(struct http_parser_message *subject ,const char *key, const char *value); void http_parser_header_del(struct http_parser_message *subject, const char *key); struct http_parser_pair * http_parser_pair_init(void *udata); diff --git a/test.c b/test.c @@ -64,6 +64,14 @@ char *postMessage = "Hello World\r\n" ; +char *postOrderedMessage = + "POST /foobar HTTP/1.1\r\n" + "Content-Length: 13\r\n" + "Host: localhost\r\n" + "\r\n" + "Hello World\r\n" +; + char *postChunkedMessage = "POST /foobar?token=pizza HTTP/1.1\r\n" "Host: localhost\r\n" @@ -154,7 +162,7 @@ int main() { ASSERT("request->path is /foobar", strcmp(request->path, "/foobar") == 0); ASSERT("request->body is \"Hello World\\r\\n\"", strcmp(request->body->data, "Hello World\r\n") == 0); msgbuf = http_parser_sprint_request(request); - ASSERT("request->toString matches", strcmp(postMessage, msgbuf->data) == 0); + ASSERT("request->toString matches", strcmp(postOrderedMessage, msgbuf->data) == 0); http_parser_message_free(request); request = http_parser_request_init();