From 21afe9ca133fed2dae2cd6ccaf8973b3cf8de220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BENEDEK=20L=C3=A1szl=C3=B3?= Date: Tue, 5 Dec 2023 22:41:20 +0100 Subject: [PATCH] Cookie and Header parsing --- inc/rum/http/http.h | 8 +- inc/rum/http/method.h | 1 + inc/rum/http/request.h | 11 ++- inc/rum/http/response.h | 17 ++-- inc/rum/http/server.h | 6 +- inc/rum/http/uri.h | 3 + inc/rum/tcp/tcp.h | 6 +- src/rum/http/request.cpp | 4 +- src/rum/http/response.cpp | 175 ++++++++++++++++---------------------- src/rum/http/server.cpp | 43 ++++++++-- src/rum/http/uri.cpp | 8 +- src/server.cpp | 9 +- 12 files changed, 152 insertions(+), 139 deletions(-) diff --git a/inc/rum/http/http.h b/inc/rum/http/http.h index cbae418..d10d4cc 100644 --- a/inc/rum/http/http.h +++ b/inc/rum/http/http.h @@ -1,6 +1,6 @@ #pragma once -#include "method.h" -#include "request.h" -#include "response.h" -#include "server.h" \ No newline at end of file +#include +#include +#include +#include \ No newline at end of file diff --git a/inc/rum/http/method.h b/inc/rum/http/method.h index 35bad52..f3930b4 100644 --- a/inc/rum/http/method.h +++ b/inc/rum/http/method.h @@ -1,6 +1,7 @@ #pragma once #include + namespace Rum::HTTP { enum Method { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE }; diff --git a/inc/rum/http/request.h b/inc/rum/http/request.h index af1530c..1b94676 100644 --- a/inc/rum/http/request.h +++ b/inc/rum/http/request.h @@ -1,9 +1,9 @@ #pragma once +#include +#include #include #include -#include "method.h" -#include "rum/http/uri.h" namespace Rum::HTTP { class Request { @@ -12,12 +12,16 @@ class Request { Request(Method method, const URI& uri) : method(method), uri(uri), body("") {} Method get_method() const { return method; } - const URI& get_uri() const { return uri; } + URI& get_uri() { return uri; } const std::map& get_headers() const { return headers; } std::string get_header(std::string name) const { return headers.at(name); } void set_header(std::string name, std::string value) { headers[name] = value; } + const std::map& get_cookies() const { return cookies; } + std::string get_cookie(std::string name) const { return cookies.at(name); } + void set_cookie(std::string name, std::string value) { cookies[name] = value; } + std::string get_body() const { return body; } void set_body(std::string body) { this->body = body; } @@ -29,6 +33,7 @@ class Request { Method method; URI uri; std::map headers; + std::map cookies; std::string body; }; } // namespace Rum::HTTP \ No newline at end of file diff --git a/inc/rum/http/response.h b/inc/rum/http/response.h index 9172470..b44a916 100644 --- a/inc/rum/http/response.h +++ b/inc/rum/http/response.h @@ -1,22 +1,25 @@ #pragma once +#include #include namespace Rum::HTTP { class Response { private: const int client_sock; - bool sent_header; - bool sent_body; - bool closed; unsigned int code; public: - Response(int client_sock) : client_sock(client_sock), sent_header(false), sent_body(false), closed(false), code(200) {} + Response(int client_sock) : client_sock(client_sock), code(200) {} ~Response(); - void send_header(const std::string& name, const std::string& value); - void send_body(const std::string& value); - void set_code(unsigned int code) { this->code = code; } + void set_code(unsigned int code) { + if (code >= 100 && code < 600) + this->code = code; + } + + std::map cookies; + std::map headers; + std::string body; }; } // namespace Rum::HTTP \ No newline at end of file diff --git a/inc/rum/http/server.h b/inc/rum/http/server.h index 9ef53f6..1cd767c 100644 --- a/inc/rum/http/server.h +++ b/inc/rum/http/server.h @@ -7,9 +7,9 @@ #include #include #include -#include "../tcp/server.h" -#include "request.h" -#include "response.h" +#include +#include +#include #define DEFAULT_WORKER_COUNT 10 #define DEFAULT_BUFFER_SIZE 8196 diff --git a/inc/rum/http/uri.h b/inc/rum/http/uri.h index 7970710..325c398 100644 --- a/inc/rum/http/uri.h +++ b/inc/rum/http/uri.h @@ -29,6 +29,9 @@ class URI { const std::map& get_parameters() const { return parameters; } const std::string& get_fragment() const { return fragment; } + void set_host(const std::string& host) { this->host = host; } + void set_port(const std::string& port) { this->port = port; } + operator std::string() const; }; } // namespace Rum::HTTP \ No newline at end of file diff --git a/inc/rum/tcp/tcp.h b/inc/rum/tcp/tcp.h index 9c0d1d6..baef50f 100644 --- a/inc/rum/tcp/tcp.h +++ b/inc/rum/tcp/tcp.h @@ -1,5 +1,5 @@ #pragma once -#include "error.h" -#include "server.h" -#include "utility.h" \ No newline at end of file +#include +#include +#include \ No newline at end of file diff --git a/src/rum/http/request.cpp b/src/rum/http/request.cpp index bcead53..11d4382 100644 --- a/src/rum/http/request.cpp +++ b/src/rum/http/request.cpp @@ -1,8 +1,8 @@ +#include #include #include #include #include -#include "rum/http/method.h" namespace Rum::HTTP { @@ -16,7 +16,7 @@ Request::operator std::string() const { "\tmethod: {}\n" "\t{}\n" "\theaders: \n{}" - "\tbody: \n'{}'" + "\tbody: '{}'\n" "}}", std::make_format_args(to_string(method), (std::string)uri, headers_string, body)); } diff --git a/src/rum/http/response.cpp b/src/rum/http/response.cpp index 3e67d56..71a2264 100644 --- a/src/rum/http/response.cpp +++ b/src/rum/http/response.cpp @@ -1,72 +1,73 @@ #include #include #include +#include #include #include namespace Rum::HTTP { std::string to_string(int code) { static const std::map codes{{100, "Continue"}, - {101, "Switching Protocols"}, - {102, "Processing"}, - {103, "Early Hints"}, - {200, "OK"}, - {201, "Created"}, - {202, "Accepted"}, - {203, "Non-Authoritative Information"}, - {204, "No Content"}, - {205, "Reset Content"}, - {206, "Partial Content"}, - {207, "Multi-Status"}, - {208, "Already Reported"}, - {226, "IM Used"}, - {300, "Multiple Choices"}, - {301, "Moved Permanently"}, - {302, "Found"}, - {303, "See Other"}, - {304, "Not Modified"}, - {307, "Temporary Redirect"}, - {308, "Permanent Redirect"}, - {400, "Bad Request"}, - {401, "Unauthorized"}, - {402, "Payment Required"}, - {403, "Forbidden"}, - {404, "Not Found"}, - {405, "Method Not Allowed"}, - {406, "Not Acceptable"}, - {407, "Proxy Authentication Required"}, - {408, "Request Timeout"}, - {409, "Conflict"}, - {410, "Gone"}, - {411, "Length Required"}, - {412, "Precondition Failed"}, - {413, "Content Too Large"}, - {414, "URI Too Long"}, - {415, "Unsupported Media Type"}, - {416, "Range Not Satisfiable"}, - {417, "Expectation Failed"}, - {418, "I'm a teapot"}, - {421, "Misdirected Request"}, - {422, "Unprocessable Content"}, - {423, "Locked"}, - {424, "Failed Dependency"}, - {425, "Too Early"}, - {426, "Upgrade Required"}, - {428, "Precondition Required"}, - {429, "Too Many Requests"}, - {431, "Request Header Fields Too Large"}, - {451, "Unavailable For Legal Reasons"}, - {500, "Internal Server Error"}, - {501, "Not Implemented"}, - {502, "Bad Gateway"}, - {503, "Service Unavailable"}, - {504, "Gateway Timeout"}, - {505, "HTTP Version Not Supported"}, - {506, "Variant Also Negotiates"}, - {507, "Insufficient Storage"}, - {508, "Loop Detected"}, - {510, "Not Extended"}, - {511, "Network Authentication Required"}}; + {101, "Switching Protocols"}, + {102, "Processing"}, + {103, "Early Hints"}, + {200, "OK"}, + {201, "Created"}, + {202, "Accepted"}, + {203, "Non-Authoritative Information"}, + {204, "No Content"}, + {205, "Reset Content"}, + {206, "Partial Content"}, + {207, "Multi-Status"}, + {208, "Already Reported"}, + {226, "IM Used"}, + {300, "Multiple Choices"}, + {301, "Moved Permanently"}, + {302, "Found"}, + {303, "See Other"}, + {304, "Not Modified"}, + {307, "Temporary Redirect"}, + {308, "Permanent Redirect"}, + {400, "Bad Request"}, + {401, "Unauthorized"}, + {402, "Payment Required"}, + {403, "Forbidden"}, + {404, "Not Found"}, + {405, "Method Not Allowed"}, + {406, "Not Acceptable"}, + {407, "Proxy Authentication Required"}, + {408, "Request Timeout"}, + {409, "Conflict"}, + {410, "Gone"}, + {411, "Length Required"}, + {412, "Precondition Failed"}, + {413, "Content Too Large"}, + {414, "URI Too Long"}, + {415, "Unsupported Media Type"}, + {416, "Range Not Satisfiable"}, + {417, "Expectation Failed"}, + {418, "I'm a teapot"}, + {421, "Misdirected Request"}, + {422, "Unprocessable Content"}, + {423, "Locked"}, + {424, "Failed Dependency"}, + {425, "Too Early"}, + {426, "Upgrade Required"}, + {428, "Precondition Required"}, + {429, "Too Many Requests"}, + {431, "Request Header Fields Too Large"}, + {451, "Unavailable For Legal Reasons"}, + {500, "Internal Server Error"}, + {501, "Not Implemented"}, + {502, "Bad Gateway"}, + {503, "Service Unavailable"}, + {504, "Gateway Timeout"}, + {505, "HTTP Version Not Supported"}, + {506, "Variant Also Negotiates"}, + {507, "Insufficient Storage"}, + {508, "Loop Detected"}, + {510, "Not Extended"}, + {511, "Network Authentication Required"}}; try { return codes.at(code); @@ -75,47 +76,21 @@ std::string to_string(int code) { } } -void Response::send_header(const std::string& name, const std::string& value) { - if (!sent_header) { - std::string resp("HTTP/1.1 " + std::to_string(code) + " " + to_string(code) + "\r\n"); - if (-1 == send(client_sock, resp.c_str(), resp.size(), 0)) { - closed = true; - throw TCP::Error(TCP::Error::CLOSED); - } - sent_header = true; - } - - std::string header = name + ": " + value + "\r\n"; - if (-1 == send(client_sock, header.c_str(), header.size(), 0)) { - closed = true; - throw TCP::Error(TCP::Error::CLOSED); - } -} - -void Response::send_body(const std::string& value) { - if (!sent_header) { - send_header("Content-Type", "text/html"); - } - - if (!sent_body) { - if (-1 == send(client_sock, "\r\n", 2, 0)) { - closed = true; - throw TCP::Error(TCP::Error::CLOSED); - } - sent_body = true; - } - - if (-1 == send(client_sock, value.c_str(), value.size(), 0)) { - closed = true; - throw TCP::Error(TCP::Error::CLOSED); - } -} - Response::~Response() { - if (!sent_header && !closed) { - std::string resp("HTTP/1.1 " + std::to_string(code) + " " + to_string(code)); - send(client_sock, resp.c_str(), resp.size(), 0); + std::string headers_string; + for (auto header : headers) { + headers_string += header.first + ": " + header.second + "\r\n"; + } + for (auto cookie : cookies) { + headers_string += "Set-cookie: " + cookie.first + "=" + cookie.second + "\r\n"; } -} + std::string message = std::vformat( + "HTTP/1.1 {} {}\r\n" + "{}" + "\r\n" + "{}", + std::make_format_args(std::to_string(code), to_string(code), headers_string, body)); + send(client_sock, message.c_str(), message.size(), 0); +} } // namespace Rum::HTTP \ No newline at end of file diff --git a/src/rum/http/server.cpp b/src/rum/http/server.cpp index 6433c51..09be7bd 100644 --- a/src/rum/http/server.cpp +++ b/src/rum/http/server.cpp @@ -1,6 +1,6 @@ -#include "rum/tcp/server.h" #include #include +#include #include #include #include @@ -101,10 +101,10 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b ssize_t recieved = recv(client_sock, buffer, buffer_size, 0); switch (recieved) { case TCP::Error::CLOSED: - std::cout << address << ": connection closed" << std::endl; + std::cerr << address << ": connection closed" << std::endl; return; case TCP::Error::UNKNOWN: - std::cout << "socket error" << std::endl; + std::cerr << "socket error" << std::endl; return; } @@ -114,8 +114,12 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b std::regex method_regex("(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE) (\\/.*) HTTP.*"); std::smatch match; if (std::regex_match(message.cbegin(), message.cbegin() + message.find("\r\n"), match, method_regex)) { - // create request object - request = Request(string_to_method(match.str(1)), URI("http://0.0.0.0:" + std::to_string(TCP::Server::get_port()) + match.str(2))); + try { + request = Request(string_to_method(match.str(1)), URI("http://0.0.0.0:" + std::to_string(TCP::Server::get_port()) + match.str(2))); + } catch (std::exception&) { + return; + } + message = message.substr(message.find("\r\n")); stage = HEADER; } else { @@ -127,7 +131,29 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b std::regex header_regex("(.*): (.*)"); for (std::sregex_iterator it = std::sregex_iterator(message.cbegin(), message.cbegin() + message.find("\r\n\r\n"), header_regex); it != std::sregex_iterator(); it++) { - request.set_header(it->str(1), it->str(2)); + std::string key(it->str(1)); + std::string value(it->str(2)); + + if (key == "Host") { + if (size_t pos = value.find(':'); pos != value.npos) { + request.get_uri().set_host(value.substr(0, pos)); + request.get_uri().set_port(value.substr(pos + 1)); + } else { + request.get_uri().set_host(value); + } + } else if (key == "Cookie") { + std::stringstream cookies_string(value); + std::string cookie; + while (std::getline(cookies_string, cookie, ';')) { + if (size_t pos = cookie.find('='); pos != cookie.npos) { + request.set_cookie(cookie.substr(0, pos), cookie.substr(pos + 1)); + } else { + return; + } + } + } else { + request.set_header(it->str(1), it->str(2)); + } } message = message.substr(message.find("\r\n\r\n")); @@ -143,10 +169,9 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b } } - std::cout << request << std::endl; - try { Response resp(client_sock); + resp.headers["Content-type"] = "text/html"; bool found = false; @@ -160,7 +185,7 @@ void Server::handler(int client_sock, const sockaddr_in& client_address, char* b if (!found) { resp.set_code(404); - resp.send_body("

404: Page not found :C

"); + resp.body = "

404: Page not found :C

"; } } catch (std::out_of_range) { } catch (TCP::Error) { diff --git a/src/rum/http/uri.cpp b/src/rum/http/uri.cpp index 5989ff2..4a18eb5 100644 --- a/src/rum/http/uri.cpp +++ b/src/rum/http/uri.cpp @@ -4,10 +4,6 @@ #include #include -#define MATCH(var, i) \ - if (match[i].matched) \ - var = match.str(i); - namespace Rum::HTTP { URI::operator std::string() const { @@ -38,6 +34,10 @@ URI::URI(const std::string& uri) { std::cout << uri << std::endl; if (std::regex_match(uri.cbegin(), uri.cend(), match, uri_regex)) { +#define MATCH(var, i) \ + if (match[i].matched) \ + var = match.str(i); + MATCH(scheme, 2) MATCH(user, 4) MATCH(password, 6) diff --git a/src/server.cpp b/src/server.cpp index 36cfb2e..3dd336a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -10,7 +10,7 @@ int main(int argc, char** argv) { Flags::Parser parser; int* port = parser.add("port", "tcp port number", false, 8080); unsigned long int* workers = parser.add("workers", "number of worker threads", false, 10); - + bool* help = parser.add("help", "print help", false, false); if (!parser.parse(argc, argv) || *help) { @@ -29,14 +29,15 @@ int main(int argc, char** argv) { server->end(); }); - server->add_path("/asd", [](const Rum::HTTP::Request& req, Rum::HTTP::Response& resp) { + server->add_path("/asd(/?|/.*)", [](const Rum::HTTP::Request& req, Rum::HTTP::Response& resp) { std::cout << "request accepted" << std::endl; - resp.send_body("

asd

" + (std::string)req + "
"); + resp.headers["Server"] = "Rum Barrel"; + resp.body = "

asd

" + (std::string)req + "
"; }); server->add_path("/.*", [](const Rum::HTTP::Request& req, Rum::HTTP::Response& resp) { std::cout << "request accepted" << std::endl; - resp.send_body("

Hello World

" + (std::string)req + "
"); + resp.body = "

Hello World

" + (std::string)req + "
"; }); server->listen();