http-parser.c

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

commit 441c9a60a640da25e5c349da8db75dd6d0220a00
parent 6afe6873bc955a82da1a4b112c81b1acef06e0ab
Author: finwo <finwo@pm.me>
Date:   Sun, 10 Nov 2019 18:46:42 +0100

Added response parsing

Diffstat:
Msrc/http-parser.c | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/http-parser.h | 8++++----
Mtest.c | 54+++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 203 insertions(+), 10 deletions(-)

diff --git a/src/http-parser.c b/src/http-parser.c @@ -294,12 +294,30 @@ void http_parser_pair_request_data(struct http_parser_pair *pair, char *data, in } /** + * Pass data into the pair's request + * + * Triggers onRequest if set + */ +void http_parser_pair_response_data(struct http_parser_pair *pair, char *data, int size) { + struct http_parser_event *ev; + http_parser_response_data(pair->request, data, size); + if (pair->request->ready && pair->onResponse) { + ev = calloc(1,sizeof(struct http_parser_event)); + ev->request = pair->request; + ev->response = pair->response; + ev->pair = pair; + ev->udata = pair->udata; + pair->onResponse(ev); + free(ev); + pair->onResponse = NULL; + } +} + +/** * Insert data into a http_message, acting as if it's a request */ void http_parser_request_data(struct http_parser_message *request, char *data, int size) { char *index; - char *colon; - char *buf; char *aContentLength; int iContentLength; char *aChunkSize; @@ -318,7 +336,7 @@ void http_parser_request_data(struct http_parser_message *request, char *data, i switch(request->_state) { case HTTP_PARSER_STATE_PANIC: return; - case HTTP_PARSER_STATE_METHOD: + case HTTP_PARSER_STATE_INIT: // Wait for more data if not line break found index = strstr(request->body, "\r\n"); @@ -373,7 +391,6 @@ void http_parser_request_data(struct http_parser_message *request, char *data, i break; } } - } // Fetch the content length @@ -425,7 +442,131 @@ void http_parser_request_data(struct http_parser_message *request, char *data, i } } -// TODO: http_parser_response_data +/** + * Insert data into a http_message, acting as if it's a request + */ +void http_parser_response_data(struct http_parser_message *response, char *data, int size) { + char *index; + char *aStatus; + int iContentLength; + char *aContentLength; + char *aChunkSize; + int res; + + // Add event data to buffer + if (!response->body) response->body = malloc(1); + response->body = realloc(response->body, response->bodysize + size + 1); + memcpy(response->body + response->bodysize, data, size); + response->bodysize += size; + + // Make string functions not segfault + *(response->body + response->bodysize) = '\0'; + + while(1) { + switch(response->_state) { + case HTTP_PARSER_STATE_PANIC: + return; + case HTTP_PARSER_STATE_INIT: + + // Wait for more data if not line break found + index = strstr(response->body, "\r\n"); + if (!index) return; + *(index) = '\0'; + + // Read version and status + response->version = calloc(1, 4); + response->statusMessage = calloc(1, 8192); + aStatus = calloc(1, 4); + if (sscanf(response->body, "HTTP/%3s %3s %8191c", response->version, aStatus, response->statusMessage) != 3) { + response->_state = HTTP_PARSER_STATE_PANIC; + return; + } + + // Turn the text status into a number + response->status = atoi(aStatus); + free(aStatus); + + // Remove status line + http_parser_message_remove_body_string(response); + + // Signal we're now reading headers + response->_state = HTTP_PARSER_STATE_HEADER; + break; + + case HTTP_PARSER_STATE_HEADER: + if (!http_parser_message_read_header(response)) { + if ( + http_parser_header_get(response, "content-length") || + http_parser_header_get(response, "transfer-encoding") + ) { + response->_state = HTTP_PARSER_STATE_BODY; + } else { + response->_state = HTTP_PARSER_STATE_DONE; + } + } + break; + + case HTTP_PARSER_STATE_BODY: + + // Detect chunked encoding + if (response->chunksize == -1) { + aChunkSize = http_parser_header_get(response, "transfer-encoding"); + if (aChunkSize) { + if (!strcmp(aChunkSize, "chunked")) { + response->_state = HTTP_PARSER_STATE_BODY_CHUNKED; + break; + } + } + } + + // Fetch the content length + aContentLength = http_parser_header_get(response, "content-length"); + if (!aContentLength) { + response->_state = HTTP_PARSER_STATE_DONE; + break; + } + iContentLength = atoi(aContentLength); + + // Not enough data = skip + if (response->bodysize < iContentLength) { + return; + } + + // Change size to indicated size + response->bodysize = iContentLength; + response->_state = HTTP_PARSER_STATE_DONE; + break; + + case HTTP_PARSER_STATE_BODY_CHUNKED: + res = http_parser_message_read_chunked(response); + + if (res == 0) { + // Done + response->_state = HTTP_PARSER_STATE_DONE; + } else if (res == 1) { + // More data needed + return; + } else if (res == 2) { + // Still reading + } + + break; + + case HTTP_PARSER_STATE_DONE: + + // Temporary buffer > direct buffer + if (response->buf) { + free(response->body); + response->body = response->buf; + response->buf = NULL; + } + + // Mark the request as ready + response->ready = 1; + return; + } + } +} #ifdef __cplusplus } // extern "C" diff --git a/src/http-parser.h b/src/http-parser.h @@ -5,12 +5,12 @@ extern "C" { #endif -#define HTTP_PARSER_STATE_METHOD 0 +#define HTTP_PARSER_STATE_INIT 0 #define HTTP_PARSER_STATE_HEADER 1 #define HTTP_PARSER_STATE_BODY 2 #define HTTP_PARSER_STATE_BODY_CHUNKED 3 #define HTTP_PARSER_STATE_DONE 4 -#define HTTP_PARSER_STATE_PANIC 666 +#define HTTP_PARSER_STATE_PANIC 666 struct http_parser_header { void *next; @@ -28,6 +28,7 @@ struct http_parser_event { struct http_parser_message { int ready; int status; + char *statusMessage; char *method; char *path; char *query; @@ -59,8 +60,7 @@ struct http_parser_message * http_parser_request_init(); struct http_parser_message * http_parser_response_init(); void http_parser_request_data(struct http_parser_message *request, char *data, int size); - -void http_parser_response_data(struct http_parser_message *request, char *data, int size); +void http_parser_response_data(struct http_parser_message *response, char *data, int size); void http_parser_pair_request_data(struct http_parser_pair *pair, char *data, int size); void http_parser_pair_response_data(struct http_parser_pair *pair, char *data, int size); diff --git a/test.c b/test.c @@ -49,6 +49,7 @@ char *getMessage = "Host: localhost\r\n" "\r\n" ; + char *postMessage = "POST /foobar HTTP/1.1\r\n" "Host: localhost\r\n" @@ -56,6 +57,7 @@ char *postMessage = "\r\n" "Hello World\r\n" ; + char *postChunkedMessage = "POST /foobar HTTP/1.1\r\n" "Host: localhost\r\n" @@ -67,13 +69,31 @@ char *postChunkedMessage = "llo World\r\n" "0\r\n" ; + char *responseMessage = "HTTP/1.0 200 OK\r\n" - "Content-Length: 11\r\n" + "Content-Length: 13\r\n" "\r\n" "Hello World\r\n" ; +char *responseChunkedMessage = + "HTTP/1.0 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\n" + "He\r\n" + "B\r\n" + "llo World\r\n" + "0\r\n" +; + +char *responseNotFoundMessage = + "HTTP/1.0 404 Not Found\r\n" + "Content-Length: 11\r\n" + "\r\n" + "Not Found\r\n" +; /* // Passing network data into it */ @@ -89,6 +109,8 @@ int main() { ASSERT("request->body is null", request->body == NULL); ASSERT("request->chunksize is -1", request->chunksize == -1); + http_parser_message_free(request); + request = http_parser_request_init(); http_parser_request_data(request, getMessage, strlen(getMessage)); printf("# GET request\n"); @@ -116,5 +138,35 @@ int main() { ASSERT("request->path is /foobar", strcmp(request->path, "/foobar") == 0); ASSERT("request->body is \"Helo World\\r\\n\"", strcmp(request->body, "Hello World\r\n") == 0); + printf("# Pre-loaded response\n"); + ASSERT("response->status = 200", response->status == 200); + + http_parser_message_free(response); + response = http_parser_response_init(); + http_parser_response_data(response, responseMessage, strlen(responseMessage)); + + printf("# 200 OK response\n"); + ASSERT("response->status = 200", response->status == 200); + ASSERT("response->statusmessage = \"OK\"", strcmp(response->statusMessage, "OK") == 0); + ASSERT("response->body = \"Hello World\\r\\n\"", strcmp(response->body, "Hello World\r\n") == 0); + + http_parser_message_free(response); + response = http_parser_response_init(); + http_parser_response_data(response, responseChunkedMessage, strlen(responseChunkedMessage)); + + printf("# 200 OK response (chunked)\n"); + ASSERT("response->status = 200", response->status == 200); + ASSERT("response->statusmessage = \"OK\"", strcmp(response->statusMessage, "OK") == 0); + ASSERT("response->body = \"Hello World\\r\\n\"", strcmp(response->body, "Hello World\r\n") == 0); + + http_parser_message_free(response); + response = http_parser_response_init(); + http_parser_response_data(response, responseNotFoundMessage, strlen(responseNotFoundMessage)); + + printf("# 404 Not Found response\n"); + ASSERT("response->status = 404", response->status == 404); + ASSERT("response->statusmessage = \"Not Found\"", strcmp(response->statusMessage, "Not Found") == 0); + ASSERT("response->body = \"Not Found\\r\\n\"", strcmp(response->body, "Not Found\r\n") == 0); + return err; }