157 lines
4.6 KiB
C
157 lines
4.6 KiB
C
#include "http.h"
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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,
|
|
};
|
|
}
|