#include "http.h" #include #include #include #include #include typedef struct { const char* message; size_t message_size; size_t i; } HttpRequestParser; #define PARSER_PANIC(message) \ (printf("error: http parser: %s\n at %s:%d in '%s()'\n", message, __FILE__, __LINE__, \ __func__), \ exit(1)) HttpMethod parse_method(HttpRequestParser* parser) { if (parser->i + 3 < parser->message_size && strncmp(&parser->message[0], "GET", 3) == 0) { parser->i += 3; return HttpMethodGet; } else if (parser->i + 4 < parser->message_size && strncmp(&parser->message[0], "POST", 4) == 0) { parser->i += 4; return HttpMethodPost; } else { PARSER_PANIC("failed to parse http method"); } } void skip_char(HttpRequestParser* parser, char value) { if (parser->i >= parser->message_size) PARSER_PANIC("unexpected end"); if (parser->message[parser->i] != value) PARSER_PANIC("unexpected character, expected ' '"); parser->i += 1; } typedef struct { size_t index, length; } PathSpan; PathSpan parse_path(HttpRequestParser* parser) { size_t index = parser->i; while (parser->i < parser->message_size && parser->message[parser->i] != ' ' && parser->message[parser->i] != '\r') { parser->i += 1; } if (parser->i >= parser->message_size) PARSER_PANIC("unexpected end"); else if (parser->message[parser->i] != ' ') PARSER_PANIC("unexpected char, expected ' '"); return (PathSpan) { .index = index, .length = parser->i - index, }; } void skip_newline(HttpRequestParser* parser) { skip_char(parser, '\r'); skip_char(parser, '\n'); } void skip_http_version_tag(HttpRequestParser* parser) { skip_char(parser, 'H'); skip_char(parser, 'T'); skip_char(parser, 'T'); skip_char(parser, 'P'); skip_char(parser, '/'); skip_char(parser, '1'); skip_char(parser, '.'); skip_char(parser, '1'); skip_newline(parser); } typedef struct { size_t key_index, key_length; size_t value_index, value_length; } HeaderPair; HeaderPair parse_header_pair(HttpRequestParser* parser) { size_t key_begin = parser->i; while (parser->i < parser->message_size && parser->message[parser->i] != ':' && parser->message[parser->i] != '\r') { parser->i += 1; } if (parser->i >= parser->message_size) PARSER_PANIC("unexpected end"); size_t key_end = parser->i; skip_char(parser, ':'); skip_char(parser, ' '); size_t value_begin = parser->i; while (parser->i < parser->message_size && parser->message[parser->i] != '\r') { parser->i += 1; } if (parser->i >= parser->message_size) PARSER_PANIC("unexpected end"); return (HeaderPair) { .key_index = key_begin, .key_length = key_end - key_begin, .value_index = value_begin, .value_length = parser->i - value_begin, }; } bool is_content_length_header(HttpRequestParser* parser, HeaderPair pair) { return strncmp(&parser->message[pair.key_index], "Content-Length", pair.key_length) == 0; } size_t extract_content_length_value(HttpRequestParser* parser, HeaderPair pair) { char string_value[21] = { 0 }; strncpy(string_value, &parser->message[pair.value_index], pair.value_length); int64_t int_value = atoll(string_value); if (int_value < 0) PARSER_PANIC("Content-Length < 0"); return (size_t)int_value; } HttpRequestHeader parse_http_request_header(const char* message, size_t message_size) { HttpRequestParser parser = (HttpRequestParser) { .message = message, .message_size = message_size, .i = 0, }; HttpMethod method = parse_method(&parser); skip_char(&parser, ' '); PathSpan path_span = parse_path(&parser); skip_char(&parser, ' '); skip_http_version_tag(&parser); size_t content_length = 0; while (parser.i < message_size && message[parser.i] != '\r') { HeaderPair pair = parse_header_pair(&parser); if (is_content_length_header(&parser, pair)) { content_length = extract_content_length_value(&parser, pair); } skip_newline(&parser); } skip_newline(&parser); return (HttpRequestHeader) { .method = method, .path_index = path_span.index, .path_length = path_span.length, .content_length = content_length, .body_index = parser.i, }; }