146 lines
3.5 KiB
C++
146 lines
3.5 KiB
C++
#pragma once
|
|
|
|
#include "http_common.hpp"
|
|
#include "http_request.hpp"
|
|
#include "router.hpp"
|
|
#include "cookie.hpp"
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using std::string_view;
|
|
|
|
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);
|
|
}
|
|
// Check for Cookie header
|
|
else if (name.size() == 6 && strncasecmp(name.data(), "cookie", 6) == 0) {
|
|
req.cookies = CookieParser::parse(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;
|
|
if (c1 == 0) break;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|