cpp_server/http_parser.hpp
2025-06-12 19:01:53 -05:00

151 lines
3.6 KiB
C++

#pragma once
#include "http_common.hpp"
#include "router.hpp"
#include <string_view>
#include <unordered_map>
#include <string>
using std::string_view;
struct HttpRequest {
HttpMethod method = HttpMethod::UNKNOWN;
string_view path;
string_view query;
string_view version;
string_view body;
std::unordered_map<string_view, string_view> headers;
std::unordered_map<std::string, std::string> params; // URL parameters
size_t content_length = 0;
bool valid = false;
};
class HttpParser {
public:
static HttpRequest parse(string_view data) {
HttpRequest req;
const char* ptr = data.data();
const char* end = ptr + data.size();
// Parse method
const char* method_end = find_char(ptr, end, ' ');
if (!method_end) return req;
req.method = parse_method(string_view(ptr, method_end - ptr));
ptr = method_end + 1;
// Parse path and query
const char* path_end = find_char(ptr, end, ' ');
if (!path_end) return req;
const char* query_start = find_char(ptr, path_end, '?');
if (query_start) {
req.path = string_view(ptr, query_start - ptr);
req.query = string_view(query_start + 1, path_end - query_start - 1);
} else {
req.path = string_view(ptr, path_end - ptr);
}
ptr = path_end + 1;
// Parse version
const char* version_end = find_char(ptr, end, '\r');
if (!version_end || version_end + 1 >= end || *(version_end + 1) != '\n') return req;
req.version = string_view(ptr, version_end - ptr);
ptr = version_end + 2;
// Parse headers
while (ptr < end - 1) {
if (*ptr == '\r' && *(ptr + 1) == '\n') {
// End of headers
ptr += 2;
break;
}
const char* header_end = find_char(ptr, end, '\r');
if (!header_end || header_end + 1 >= end || *(header_end + 1) != '\n') break;
const char* colon = find_char(ptr, header_end, ':');
if (!colon) {
ptr = header_end + 2;
continue;
}
string_view name(ptr, colon - ptr);
const char* value_start = colon + 1;
while (value_start < header_end && *value_start == ' ') value_start++;
string_view value(value_start, header_end - value_start);
req.headers[name] = value;
// Check for Content-Length
if (name.size() == 14 && strncasecmp(name.data(), "content-length", 14) == 0) {
req.content_length = parse_int(value);
}
ptr = header_end + 2;
}
// Body
if (ptr < end) {
req.body = string_view(ptr, end - ptr);
}
req.valid = true;
return req;
}
private:
static const char* find_char(const char* start, const char* end, char c) {
for (const char* p = start; p < end; ++p) {
if (*p == c) return p;
}
return nullptr;
}
static HttpMethod parse_method(string_view method) {
switch (method.size()) {
case 3:
if (method == "GET") return HttpMethod::GET;
if (method == "PUT") return HttpMethod::PUT;
break;
case 4:
if (method == "POST") return HttpMethod::POST;
if (method == "HEAD") return HttpMethod::HEAD;
break;
case 5:
if (method == "PATCH") return HttpMethod::PATCH;
break;
case 6:
if (method == "DELETE") return HttpMethod::DELETE;
break;
case 7:
if (method == "OPTIONS") return HttpMethod::OPTIONS;
break;
}
return HttpMethod::UNKNOWN;
}
static size_t parse_int(string_view str) {
size_t result = 0;
for (char c : str) {
if (c >= '0' && c <= '9') {
result = result * 10 + (c - '0');
} else {
break;
}
}
return result;
}
static int strncasecmp(const char* s1, const char* s2, size_t n) {
for (size_t i = 0; i < n; ++i) {
char c1 = s1[i] >= 'A' && s1[i] <= 'Z' ? s1[i] + 32 : s1[i];
char c2 = s2[i] >= 'A' && s2[i] <= 'Z' ? s2[i] + 32 : s2[i];
if (c1 != c2) return c1 - c2;
}
return 0;
}
};