185 lines
4.4 KiB
C++
185 lines
4.4 KiB
C++
#pragma once
|
|
|
|
#include "common.hpp"
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
using std::string_view;
|
|
|
|
struct Cookie {
|
|
string_view name;
|
|
string_view value;
|
|
|
|
Cookie(string_view n, string_view v) : name(n), value(v) {}
|
|
};
|
|
|
|
class CookieParser {
|
|
public:
|
|
static std::vector<Cookie> parse(string_view cookie_header) {
|
|
std::vector<Cookie> cookies;
|
|
cookies.reserve(8); // Most requests have few cookies
|
|
|
|
const char* ptr = cookie_header.data();
|
|
const char* end = ptr + cookie_header.size();
|
|
|
|
while (likely(ptr < end)) {
|
|
// Skip whitespace and semicolons
|
|
ptr = skip_separators(ptr, end);
|
|
if (unlikely(ptr >= end)) break;
|
|
|
|
// Find name end (=)
|
|
const char* name_start = ptr;
|
|
ptr = find_char(ptr, end, '=');
|
|
if (unlikely(!ptr)) break;
|
|
|
|
string_view name(name_start, ptr - name_start);
|
|
ptr++; // Skip '='
|
|
|
|
// Find value end (; or end)
|
|
const char* value_start = ptr;
|
|
const char* value_end = find_char(ptr, end, ';');
|
|
if (!value_end) value_end = end;
|
|
|
|
string_view value(value_start, value_end - value_start);
|
|
ptr = value_end;
|
|
|
|
// Trim whitespace efficiently
|
|
name = trim_fast(name);
|
|
value = trim_fast(value);
|
|
|
|
if (likely(!name.empty())) {
|
|
cookies.emplace_back(name, value);
|
|
}
|
|
}
|
|
|
|
return cookies;
|
|
}
|
|
|
|
private:
|
|
static const char* skip_separators(const char* ptr, const char* end) {
|
|
// Unrolled loop for common case
|
|
while (likely(ptr < end - 3)) {
|
|
if (*ptr != ' ' && *ptr != ';') break;
|
|
++ptr;
|
|
if (*ptr != ' ' && *ptr != ';') break;
|
|
++ptr;
|
|
if (*ptr != ' ' && *ptr != ';') break;
|
|
++ptr;
|
|
if (*ptr != ' ' && *ptr != ';') break;
|
|
++ptr;
|
|
}
|
|
while (ptr < end && (*ptr == ' ' || *ptr == ';')) ++ptr;
|
|
return ptr;
|
|
}
|
|
|
|
static const char* find_char(const char* start, const char* end, char target) {
|
|
// Optimized character search
|
|
while (likely(start < end - 7)) {
|
|
if (start[0] == target) return start;
|
|
if (start[1] == target) return start + 1;
|
|
if (start[2] == target) return start + 2;
|
|
if (start[3] == target) return start + 3;
|
|
if (start[4] == target) return start + 4;
|
|
if (start[5] == target) return start + 5;
|
|
if (start[6] == target) return start + 6;
|
|
if (start[7] == target) return start + 7;
|
|
start += 8;
|
|
}
|
|
while (start < end && *start != target) ++start;
|
|
return start < end ? start : nullptr;
|
|
}
|
|
|
|
static string_view trim_fast(string_view str) {
|
|
if (unlikely(str.empty())) return str;
|
|
|
|
const char* start = str.data();
|
|
const char* end = start + str.size();
|
|
|
|
// Trim leading - unrolled
|
|
while (likely(start < end - 3) && *start == ' ') {
|
|
if (start[1] != ' ') { start += 1; break; }
|
|
if (start[2] != ' ') { start += 2; break; }
|
|
if (start[3] != ' ') { start += 3; break; }
|
|
start += 4;
|
|
}
|
|
while (start < end && *start == ' ') ++start;
|
|
|
|
// Trim trailing - unrolled
|
|
while (likely(end > start + 3) && *(end - 1) == ' ') {
|
|
if (*(end - 2) != ' ') { end -= 1; break; }
|
|
if (*(end - 3) != ' ') { end -= 2; break; }
|
|
if (*(end - 4) != ' ') { end -= 3; break; }
|
|
end -= 4;
|
|
}
|
|
while (end > start && *(end - 1) == ' ') --end;
|
|
|
|
return string_view(start, end - start);
|
|
}
|
|
};
|
|
|
|
class CookieHelpers {
|
|
public:
|
|
static string_view get_cookie(const std::vector<Cookie>& cookies, string_view name) {
|
|
// Optimized linear search with early termination
|
|
for (const auto& cookie : cookies) {
|
|
if (likely(cookie.name.size() == name.size()) && cookie.name == name) {
|
|
return cookie.value;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static std::string build_set_cookie(string_view name, string_view value,
|
|
int max_age = -1, string_view path = "", string_view domain = "",
|
|
bool secure = false, bool http_only = false) {
|
|
|
|
std::string result;
|
|
result.reserve(name.size() + value.size() + 128); // Estimate size
|
|
|
|
result += name;
|
|
result += "=";
|
|
result += value;
|
|
|
|
if (unlikely(max_age >= 0)) {
|
|
result += "; Max-Age=";
|
|
result += std::to_string(max_age);
|
|
}
|
|
|
|
if (unlikely(!path.empty())) {
|
|
result += "; Path=";
|
|
result += path;
|
|
}
|
|
|
|
if (unlikely(!domain.empty())) {
|
|
result += "; Domain=";
|
|
result += domain;
|
|
}
|
|
|
|
if (unlikely(secure)) {
|
|
result += "; Secure";
|
|
}
|
|
|
|
if (unlikely(http_only)) {
|
|
result += "; HttpOnly";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static std::string build_delete_cookie(string_view name, string_view path = "") {
|
|
std::string result;
|
|
result.reserve(name.size() + 64);
|
|
|
|
result += name;
|
|
result += "=; Max-Age=0";
|
|
|
|
if (unlikely(!path.empty())) {
|
|
result += "; Path=";
|
|
result += path;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|