commit ffafc4f718641a4f729de1af55ba46f4277bff05
parent 1b39ef1371720255248db85f0e47ac2415fe069c
Author: finwo <finwo@pm.me>
Date: Sun, 10 Nov 2019 17:28:48 +0100
Supporting chunked data now
Diffstat:
3 files changed, 168 insertions(+), 15 deletions(-)
diff --git a/src/http-parser.c b/src/http-parser.c
@@ -15,6 +15,37 @@ extern "C" {
#endif
/**
+ * Convert hexidecimal string to int
+ */
+int xtoi(char *str) {
+ char *p = str;
+ int i = 0;
+ int sign = 1;
+ while(*p) {
+ if ( *p == '-' ) sign = -1;
+
+ if ( *p >= '0' && *p <= '9' ) {
+ i *= 16;
+ i += (*p) - '0';
+ }
+
+ if ( *p >= 'a' && *p <= 'f' ) {
+ i *= 16;
+ i += 10 + (*p) - 'a';
+ }
+
+ if ( *p >= 'A' && *p <= 'F' ) {
+ i *= 16;
+ i += 10 + (*p) - 'A';
+ }
+
+ p = p+1;
+ }
+
+ 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) {
@@ -30,9 +61,14 @@ void http_parser_header_free(struct http_parser_header *header) {
*/
char *http_parser_header_get(struct http_parser_message *subject, char *key) {
struct http_parser_header *header = subject->headers;
+ char *value;
while(header) {
if (!strcasecmp(key, header->key)) {
- return header->value;
+ value = header->value;
+ while(*(value) == ' ') {
+ value++;
+ }
+ return value;
}
header = header->next;
}
@@ -59,6 +95,7 @@ void http_parser_message_free(struct http_parser_message *subject) {
if (subject->version) free(subject->version);
if (subject->body) free(subject->body);
if (subject->headers) http_parser_header_free(subject->headers);
+ if (subject->buf) free(subject->buf);
free(subject);
}
@@ -76,6 +113,7 @@ 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;
return message;
}
@@ -99,17 +137,25 @@ struct http_parser_pair * http_parser_pair_init(void *udata) {
return pair;
}
+
+/**
+ * Removed N byte from the beginning of the message body
+ */
+static void http_parser_message_remove_body_bytes(struct http_parser_message *message, int bytes) {
+ int size = message->bodysize - bytes;
+ char *buf = calloc(1, size + 1);
+ memcpy(buf, message->body + bytes, size);
+ free(message->body);
+ message->body = buf;
+ message->bodysize = size;
+}
+
/**
* 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;
+ http_parser_message_remove_body_bytes(message, length + 2);
}
/**
@@ -140,7 +186,6 @@ static int http_parser_message_read_header(struct http_parser_message *message)
// 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;
@@ -189,8 +234,10 @@ void http_parser_pair_request_data(struct http_parser_pair *pair, char *data, in
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;
// Add event data to buffer
if (!request->body) request->body = malloc(1);
@@ -238,7 +285,10 @@ void http_parser_request_data(struct http_parser_message *request, char *data, i
case HTTP_PARSER_STATE_HEADER:
if (!http_parser_message_read_header(request)) {
- if (http_parser_header_get(request, "content-length")) {
+ if (
+ http_parser_header_get(request, "content-length") ||
+ http_parser_header_get(request, "transfer-encoding")
+ ) {
request->_state = HTTP_PARSER_STATE_BODY;
} else {
request->_state = HTTP_PARSER_STATE_RESPONSE;
@@ -248,8 +298,24 @@ void http_parser_request_data(struct http_parser_message *request, char *data, i
case HTTP_PARSER_STATE_BODY:
+ // Detect chunked encoding
+ if (request->chunksize == -1) {
+ aChunkSize = http_parser_header_get(request, "transfer-encoding");
+ if (aChunkSize) {
+ if (!strcmp(aChunkSize, "chunked")) {
+ request->_state = HTTP_PARSER_STATE_BODY_CHUNKED;
+ break;
+ }
+ }
+
+ }
+
// Fetch the content length
aContentLength = http_parser_header_get(request, "content-length");
+ if (!aContentLength) {
+ request->_state = HTTP_PARSER_STATE_RESPONSE;
+ return;
+ }
iContentLength = atoi(aContentLength);
// Not enough data = skip
@@ -262,6 +328,67 @@ void http_parser_request_data(struct http_parser_message *request, char *data, i
request->_state = HTTP_PARSER_STATE_RESPONSE;
break;
+ case HTTP_PARSER_STATE_BODY_CHUNKED:
+
+ // Detect chunk size if none present
+ if (request->chunksize == -1) {
+
+ // Check if we have a line
+ index = strstr(request->body, "\r\n");
+ if (!index) {
+ return;
+ }
+ *(index) = '\0';
+
+ // Empty line = skip
+ if (!strlen(request->body)) {
+ http_parser_message_remove_body_string(request);
+ break;
+ }
+
+ // Read hex chunksize
+ aChunkSize = calloc(1, 17);
+ sscanf(request->body, "%16s", aChunkSize);
+ request->chunksize = xtoi(aChunkSize);
+ free(aChunkSize);
+
+ // Remove chunksize line
+ http_parser_message_remove_body_string(request);
+
+ // 0 = EOF
+ if (request->chunksize == 0) {
+ request->_state = HTTP_PARSER_STATE_RESPONSE;
+ free(request->body);
+ request->body = request->buf;
+ request->buf = NULL;
+ return;
+ }
+
+ }
+
+ // Create buffer if not present yet
+ if (!request->buf) {
+ request->buf = calloc(1,1);
+ request->bufsize = 0;
+ }
+
+ // Ensure if the body has enough data
+ if (request->bodysize < request->chunksize) {
+ return;
+ }
+
+ // Copy data into buffer
+ request->buf = realloc(request->buf, request->bufsize + request->chunksize + 1);
+ memcpy(request->buf + request->bufsize, request->body, request->chunksize );
+ request->bufsize += request->chunksize;
+ *(request->buf + request->bufsize) = '\0';
+
+ // Remove chunk from receiving data and reset chunking
+ http_parser_message_remove_body_bytes(request, request->chunksize);
+ request->chunksize = -1;
+
+ break;
+
case HTTP_PARSER_STATE_RESPONSE:
return;
}
diff --git a/src/http-parser.h b/src/http-parser.h
@@ -5,10 +5,11 @@
extern "C" {
#endif
-#define HTTP_PARSER_STATE_METHOD 0
-#define HTTP_PARSER_STATE_HEADER 1
-#define HTTP_PARSER_STATE_BODY 2
-#define HTTP_PARSER_STATE_RESPONSE 3
+#define HTTP_PARSER_STATE_METHOD 0
+#define HTTP_PARSER_STATE_HEADER 1
+#define HTTP_PARSER_STATE_BODY 2
+#define HTTP_PARSER_STATE_BODY_CHUNKED 3
+#define HTTP_PARSER_STATE_RESPONSE 4
#define HTTP_PARSER_STATE_PANIC 666
struct http_parser_header {
@@ -31,6 +32,9 @@ struct http_parser_message {
char *version;
struct http_parser_header *headers;
char *body;
+ char *buf;
+ int bufsize;
+ int chunksize;
int bodysize;
int _state;
};
diff --git a/test.c b/test.c
@@ -56,6 +56,17 @@ char *postMessage =
"\r\n"
"Hello World\r\n"
;
+char *postChunkedMessage =
+ "POST /foobar HTTP/1.1\r\n"
+ "Host: localhost\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 *responseMessage =
"HTTP/1.0 200 OK\r\n"
"Content-Length: 11\r\n"
@@ -76,10 +87,11 @@ int main() {
printf("# Pre-loaded request\n");
ASSERT("request->method is null", request->method == NULL);
ASSERT("request->body is null", request->body == NULL);
+ ASSERT("request->chunksize is -1", request->chunksize == -1);
http_parser_request_data(request, getMessage, strlen(getMessage));
- printf("# Loaded GET request\n");
+ printf("# 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);
@@ -88,7 +100,17 @@ int main() {
request = http_parser_request_init();
http_parser_request_data(request, postMessage, strlen(postMessage));
- printf("# Loaded POST request\n");
+ printf("# 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);
+
+ http_parser_message_free(request);
+ request = http_parser_request_init();
+ http_parser_request_data(request, postChunkedMessage, strlen(postChunkedMessage));
+
+ printf("# POST request (chunked)\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);