commit 1b39ef1371720255248db85f0e47ac2415fe069c
parent af8fdd7447710492e741ae2cdede201e69345c4a
Author: finwo <finwo@pm.me>
Date: Sun, 10 Nov 2019 16:14:51 +0100
Basic testing added
Diffstat:
6 files changed, 409 insertions(+), 98 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,3 @@
/lib
*.o
+http-parser-test
diff --git a/Makefile b/Makefile
@@ -0,0 +1,7 @@
+
+.PHONY: test
+test: http-parser-test
+ ./$<
+
+http-parser-test: test.c src/http-parser.c
+ $(CC) -o $@ $^
diff --git a/src/http-parser-statusses.h b/src/http-parser-statusses.h
@@ -0,0 +1,105 @@
+#ifndef _HTTP_PARSER_H_
+#define _HTTP_PARSER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct {
+ int status;
+ char *message;
+} http_parser_statusses[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 102, "Processing" },
+ { 103, "Early Hints" },
+ { 200, "OK" },
+ { 201, "Created" },
+ { 202, "Accepted" },
+ { 203, "Non-Authoritive Information" },
+ { 204, "No Content" },
+ { 205, "Reset Content" },
+ { 206, "Partial Content" },
+ { 207, "Multi-Status" },
+ { 208, "Already Reported" },
+ { 218, "This is fine" },
+ { 226, "IM Used" },
+ { 300, "Multiple Choices" },
+ { 301, "Moved Permanently" },
+ { 302, "Found" },
+ { 303, "See Other" },
+ { 304, "Not Modified" },
+ { 305, "Use Proxy" },
+ { 306, "Switch Proxy" },
+ { 307, "Temporary Redirect" },
+ { 308, "Permanent Redirect" },
+ { 400, "Bad Request" },
+ { 401, "Unauthorized" },
+ { 402, "Payment Required" },
+ { 403, "Forbidden" },
+ { 404, "Not Found" },
+ { 405, "Method Not Allowed" },
+ { 406, "Not Acceptable" },
+ { 407, "Proxy Authentication Required" },
+ { 408, "Request Timeout" },
+ { 409, "Conflict" },
+ { 410, "Gone" },
+ { 411, "Length Required" },
+ { 412, "Precondition Failed" },
+ { 413, "Payload Too Large" },
+ { 414, "URI Too Long" },
+ { 415, "Unsupported Media Type" },
+ { 416, "Range Not Satisfiable" },
+ { 417, "Expectation Failed" },
+ { 418, "I'm a teapot" },
+ { 419, "Page Expired" },
+ { 420, "Enhance Your Calm" },
+ { 421, "Misdirected Request" },
+ { 422, "Unprocessable Entity" },
+ { 423, "Locked" },
+ { 424, "Failed Dependency" },
+ { 425, "Too Early" },
+ { 426, "Upgrade Required" },
+ { 428, "Precondition Required" },
+ { 429, "Too Many Requests" },
+ { 431, "Request Header Fields Too Large" },
+ { 440, "Login Time-out" },
+ { 444, "No Response" },
+ { 449, "Retry With" },
+ { 450, "Blocked by Windows Parental Controls" },
+ { 451, "Unavailable For Legal Reasons" },
+ { 494, "Request header too large" },
+ { 495, "SSL Certificate Error" },
+ { 496, "SSL Certificate Required" },
+ { 497, "HTTP Request Send to HTTPS Port" },
+ { 498, "Invalid Token" },
+ { 499, "Token Required" },
+ { 500, "Internal Server Error" },
+ { 501, "Not Implemented" },
+ { 502, "Bad Gateway" },
+ { 503, "Service Unavailable" },
+ { 504, "Gateway Timeout" },
+ { 505, "HTTP Version Not Supported" },
+ { 506, "Variant Also Negotiates" },
+ { 507, "Insufficient Storage" },
+ { 509, "Bandwidth Limit Exceeded" },
+ { 510, "Not Extended" },
+ { 511, "Network Authentication Required" },
+ { 520, "Web Server Returned an Unknown Error" },
+ { 521, "Web Server is Down" },
+ { 522, "Connection Timed Out" },
+ { 523, "Origin is Unreachable" },
+ { 524, "A Timeout Occurred" },
+ { 525, "SSL Handshake Failed" },
+ { 526, "Invalid SSL Certificate" },
+ { 527, "Railgun Error" },
+ { 530, "Site is frozen" },
+ { 598, "Network read timeout error" },
+ { 0,0 },
+};
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // _HTTP_PARSER_H_
diff --git a/src/http-parser.c b/src/http-parser.c
@@ -8,11 +8,15 @@ extern "C" {
#include <strings.h>
#include "http-parser.h"
+#include "http-parser-statusses.h"
#ifndef NULL
#define NULL ((void*)0)
#endif
+/**
+ * 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);
@@ -20,8 +24,12 @@ void http_parser_header_free(struct http_parser_header *header) {
free(header);
}
-char *http_parser_header_get(struct http_parser_request *request, char *key) {
- struct http_parser_header *header = request->headers;
+/**
+ * Searches for the given key in the list of headers
+ * Returns the header's value or NULL if not found
+ */
+char *http_parser_header_get(struct http_parser_message *subject, char *key) {
+ struct http_parser_header *header = subject->headers;
while(header) {
if (!strcasecmp(key, header->key)) {
return header->value;
@@ -31,27 +39,156 @@ char *http_parser_header_get(struct http_parser_request *request, char *key) {
return NULL;
}
-void http_parser_request_free(struct http_parser_request *request) {
- if (request->method) free(request->method);
- if (request->path) free(request->path);
- if (request->version) free(request->version);
- if (request->body) free(request->body);
- if (request->headers) http_parser_header_free(request->headers);
- free(request);
+/**
+ * Write a header into the subject's list of headers
+ */
+void http_parser_header_set(struct http_parser_message *subject, char *key, char *value) {
+ struct http_parser_header *header = malloc(sizeof(struct http_parser_header));
+ header->key = key;
+ header->value = value;
+ header->next = subject->headers;
+ subject->headers = header;
}
-struct http_parser_request * http_parser_request_init() {
- struct http_parser_request *request = calloc(1, sizeof(struct http_parser_request));
- return request;
+/**
+ * Frees everything in a http_message that was malloc'd by http-parser
+ */
+void http_parser_message_free(struct http_parser_message *subject) {
+ if (subject->method) free(subject->method);
+ if (subject->path) free(subject->path);
+ if (subject->version) free(subject->version);
+ if (subject->body) free(subject->body);
+ if (subject->headers) http_parser_header_free(subject->headers);
+ free(subject);
}
-void http_parser_request_data(struct http_parser_request *request, char *data, int size) {
+/**
+ * Frees everything in a http pair that was malloc'd by http-parser
+ */
+void http_parser_pair_free(struct http_parser_pair *pair) {
+ if (pair->request) http_parser_message_free(pair->request);
+ if (pair->response) http_parser_message_free(pair->response);
+ free(pair);
+}
+
+/**
+ * Initializes a http_message as request
+ */
+struct http_parser_message * http_parser_request_init() {
+ struct http_parser_message *message = calloc(1, sizeof(struct http_parser_message));
+ return message;
+}
+
+/**
+ * Initializes a http_message as reponse
+ */
+struct http_parser_message * http_parser_response_init() {
+ struct http_parser_message *message = http_parser_request_init();
+ message->status = 200;
+ return message;
+}
+
+/**
+ * Initialize a http_pair with userdata
+ */
+struct http_parser_pair * http_parser_pair_init(void *udata) {
+ struct http_parser_pair *pair = calloc(1, sizeof(struct http_parser_pair));
+ pair->request = http_parser_request_init();
+ pair->response = http_parser_response_init();
+ pair->udata = udata;
+ return pair;
+}
+
+/**
+ * Removes the first string from a http_message's body
+ */
+static void http_parser_message_remove_body_string(struct http_parser_message *message) {
+ int length = strlen(message->body);
+ int size = message->bodysize - 2 - length;
+ char *buf = calloc(1,size+1);
+ memcpy(buf, message->body + length + 2, size);
+ free(message->body);
+ message->body = buf;
+ message->bodysize = size;
+}
+
+/**
+ * Reads a header from a message's body and removes that lines from the body
+ *
+ * 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
+ index = strstr(message->body, "\r\n");
+ if (!index) return 1;
+ *(index) = '\0';
+
+ // Detect end of headers
+ if (!strlen(message->body)) {
+ http_parser_message_remove_body_string(message);
+ return 0;
+ }
+
+ // Prepare new header
+ header = calloc(1,sizeof(header));
+ header->key = calloc(1,strlen(message->body));
+ header->value = calloc(1,strlen(message->body));
+
+ // Detect colon
+ index = strstr(message->body, ":");
+ if (!index) {
+ printf("NO COLON: %s\n", message->body);
+ http_parser_header_free(header);
+ http_parser_message_remove_body_string(message);
+ return 1;
+ }
+
+ // Copy key & value
+ *(index) = '\0';
+ strcpy(header->key, message->body);
+ strcpy(header->value, index + 1);
+
+ // Assign to the header list
+ header->next = message->headers;
+ message->headers = header;
+
+ // Remove the header line
+ // Twice, because we split the string
+ http_parser_message_remove_body_string(message);
+ http_parser_message_remove_body_string(message);
+ return 2;
+}
+
+/**
+ * Pass data into the pair's request
+ *
+ * Triggers onRequest if set
+ */
+void http_parser_pair_request_data(struct http_parser_pair *pair, char *data, int size) {
struct http_parser_event *ev;
+ http_parser_request_data(pair->request, data, size);
+ if (pair->request->_state == HTTP_PARSER_STATE_RESPONSE) {
+ if (pair->onRequest) {
+ ev = calloc(1,sizeof(struct http_parser_event));
+ ev->request = pair->request;
+ ev->response = pair->response;
+ ev->udata = pair->udata;
+ pair->onRequest(ev);
+ free(ev);
+ pair->onRequest = 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;
- int newsize;
char *colon;
- char *buf;
char *aContentLength;
int iContentLength;
@@ -64,9 +201,8 @@ void http_parser_request_data(struct http_parser_request *request, char *data, i
// Make string functions not segfault
*(request->body + request->bodysize) = '\0';
- int running = 1;
- while(running) {
- switch(request->state) {
+ while(1) {
+ switch(request->_state) {
case HTTP_PARSER_STATE_PANIC:
return;
case HTTP_PARSER_STATE_METHOD:
@@ -81,17 +217,12 @@ void http_parser_request_data(struct http_parser_request *request, char *data, i
request->path = calloc(1, 8192);
request->version = calloc(1, 4);
if (sscanf(request->body, "%6s %8191s HTTP/%3s", request->method, request->path, request->version) != 3) {
- request->state = HTTP_PARSER_STATE_PANIC;
+ request->_state = HTTP_PARSER_STATE_PANIC;
return;
}
- // Remove the method line
- newsize = request->bodysize - 2 - (index - request->body);
- buf = calloc(1,newsize+1);
- memcpy(buf, index + 2, newsize);
- free(request->body);
- request->body = buf;
- request->bodysize = newsize;
+ // Remove method line
+ http_parser_message_remove_body_string(request);
// Detect query
// No need to malloc, already done by sscanf
@@ -102,63 +233,17 @@ void http_parser_request_data(struct http_parser_request *request, char *data, i
}
// Signal we're now reading headers
- request->state = HTTP_PARSER_STATE_HEADER;
+ request->_state = HTTP_PARSER_STATE_HEADER;
break;
case HTTP_PARSER_STATE_HEADER:
-
- // Wait for more data if not line break found
- index = strstr(request->body, "\r\n");
- if (!index) return;
- *(index) = '\0';
-
- // Detect end of headers
- newsize = strlen(request->body);
- if (!newsize) {
-
- // Remove the blank line
- newsize = request->bodysize - 2;
- buf = calloc(1,newsize+1);
- memcpy(buf, index + 2, newsize);
- free(request->body);
- request->body = buf;
- request->bodysize = newsize;
-
- // No content-length = respond
+ if (!http_parser_message_read_header(request)) {
if (http_parser_header_get(request, "content-length")) {
- request->state = HTTP_PARSER_STATE_BODY;
+ request->_state = HTTP_PARSER_STATE_BODY;
} else {
- request->state = HTTP_PARSER_STATE_RESPONSE;
+ request->_state = HTTP_PARSER_STATE_RESPONSE;
}
-
- break;
}
-
- // Prepare new header
- header = calloc(1,sizeof(header));
- header->key = calloc(1,strlen(request->body));
- header->value = calloc(1,strlen(request->body));
-
- // Copy key & value
- colon = strstr(request->body, ":");
- if (colon) {
- *(colon) = '\0';
- strcpy(header->key, request->body);
- strcpy(header->value, colon + 1);
- }
-
- // Assign to the header list
- header->next = request->headers;
- request->headers = header;
-
- // Remove the header line
- newsize = request->bodysize - 2 - (index - request->body);
- buf = calloc(1,newsize+1);
- memcpy(buf, index + 2, newsize);
- free(request->body);
- request->body = buf;
- request->bodysize = newsize;
-
break;
case HTTP_PARSER_STATE_BODY:
@@ -169,30 +254,22 @@ void http_parser_request_data(struct http_parser_request *request, char *data, i
// Not enough data = skip
if (request->bodysize < iContentLength) {
- running = 0;
- break;
+ return;
}
// Change size to indicated size
request->bodysize = iContentLength;
- request->state = HTTP_PARSER_STATE_RESPONSE;
+ request->_state = HTTP_PARSER_STATE_RESPONSE;
break;
case HTTP_PARSER_STATE_RESPONSE:
-
- if (request->onRequest) {
- ev = calloc(1,sizeof(struct http_parser_event));
- ev->request = request;
- request->onRequest(ev);
- request->onRequest = NULL;
- }
-
- running = 0;
- break;
+ return;
}
}
}
+// TODO: http_parser_response_data
+
#ifdef __cplusplus
} // extern "C"
#endif
diff --git a/src/http-parser.h b/src/http-parser.h
@@ -18,26 +18,49 @@ struct http_parser_header {
};
struct http_parser_event {
- struct http_parser_request *request;
+ struct http_parser_message *request;
+ struct http_parser_message *response;
+ void *udata;
};
-struct http_parser_request {
- int bodysize;
- int state;
+struct http_parser_message {
+ int status;
char *method;
char *path;
char *query;
char *version;
struct http_parser_header *headers;
char *body;
- void (*onRequest)(struct http_parser_event*);
+ int bodysize;
+ int _state;
+};
+
+struct http_parser_pair {
+ struct http_parser_message *request;
+ struct http_parser_message *response;
void *udata;
+ void (*onRequest)(struct http_parser_event*);
+ void (*onResponse)(struct http_parser_event*);
};
-char *http_parser_header_get(struct http_parser_request *request, char *key);
-void http_parser_request_free(struct http_parser_request *request);
-struct http_parser_request * http_parser_request_init();
-void http_parser_request_data(struct http_parser_request *request, char *data, int size);
+// Header management
+char *http_parser_header_get(struct http_parser_message *subject, char *key);
+void http_parser_header_set(struct http_parser_message *subject, char *key, char *value);
+char *http_parser_header_del(struct http_parser_message *subject, char *key);
+
+struct http_parser_pair * http_parser_pair_init(void *udata);
+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_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);
+
+void http_parser_pair_free(struct http_parser_pair *pair);
+void http_parser_message_free(struct http_parser_message *subject);
#ifdef __cplusplus
} // extern "C"
diff --git a/test.c b/test.c
@@ -0,0 +1,98 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "src/http-parser.h"
+
+#ifndef NULL
+#define NULL ((void*)0)
+#endif
+
+#define T_RED "\e[31m"
+#define T_LIME "\e[32m"
+#define T_MAGENTA "\e[35m"
+#define T_NORMAL "\e[00m"
+#define T_BOLD "\e[01m"
+
+#define ASSERT(M,c) (printf(((err|=!(c),(c)) ? (T_LIME " PASS " T_NORMAL " %s\n") : (T_RED " FAIL " T_NORMAL " %s\n")),M))
+
+
+/* static void onRequest(struct http_parser_event *ev) { */
+/* // The request has been received */
+/* // Answer the request directly or pass it to a route handler of sorts */
+
+/* // Fetching the request */
+/* // Has been wrapped in http_parser_event to support more features in the future */
+/* struct http_parser_request *request = ev->request; */
+
+/* // Basic http request data */
+/* printf("Method: %s\n", request->method); */
+
+/* // Reading headers are case-insensitive due to non-compliant clients/servers */
+/* printf("Host: %s\n", http_parser_header_get(request, "host")); */
+
+/* // Once you're done with the request, you'll have to free it */
+/* http_parser_request_free(request); */
+/* } */
+
+/* // Initialize a request */
+/* struct http_parser_request *request = http_parser_init(); */
+
+/* // Assign userdata into the request */
+/* // Use it to track whatever you need */
+/* request->udata = (void*)...; */
+
+/* // Attach a function */
+
+// Stored http messages
+char *getMessage =
+ "GET /foobar HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "\r\n"
+;
+char *postMessage =
+ "POST /foobar HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Content-Length: 13\r\n"
+ "\r\n"
+ "Hello World\r\n"
+;
+char *responseMessage =
+ "HTTP/1.0 200 OK\r\n"
+ "Content-Length: 11\r\n"
+ "\r\n"
+ "Hello World\r\n"
+;
+
+
+
+/* // Passing network data into it */
+/* http_parser_request_data(request, message, strlen(message)); */
+
+int main() {
+ struct http_parser_message *request = http_parser_request_init();
+ struct http_parser_message *response = http_parser_response_init();
+ int err = 0;
+
+ printf("# Pre-loaded request\n");
+ ASSERT("request->method is null", request->method == NULL);
+ ASSERT("request->body is null", request->body == NULL);
+
+ http_parser_request_data(request, getMessage, strlen(getMessage));
+
+ printf("# Loaded GET request\n");
+ ASSERT("request->version is 1.1", strcmp(request->version, "1.1") == 0);
+ ASSERT("request->method is GET", strcmp(request->method, "GET") == 0);
+ ASSERT("request->path is /foobar", strcmp(request->path, "/foobar") == 0);
+
+ http_parser_message_free(request);
+ request = http_parser_request_init();
+ http_parser_request_data(request, postMessage, strlen(postMessage));
+
+ printf("# Loaded POST request\n");
+ ASSERT("request->version is 1.1", strcmp(request->version, "1.1") == 0);
+ ASSERT("request->method is POST", strcmp(request->method, "POST") == 0);
+ 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);
+
+ return err;
+}