Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/http_request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct arguments_accumulator {
};

void http_request::set_method(const std::string& method) {
this->method = string_utilities::to_upper_copy(method);
this->method = method;
}

#ifdef HAVE_DAUTH
Expand Down
38 changes: 22 additions & 16 deletions src/http_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,19 +218,15 @@ std::vector<std::string> http_utils::tokenize_url(const std::string& str, const
}

std::string http_utils::standardize_url(const std::string& url) {
std::string n_url = url;
if (url.empty()) return url;

std::string::iterator new_end = std::unique(n_url.begin(), n_url.end(), [](char a, char b) { return (a == b) && (a == '/'); });
n_url.erase(new_end, n_url.end());
std::string result = url;

std::string::size_type n_url_length = n_url.length();
auto new_end = std::unique(result.begin(), result.end(), [](char a, char b) { return (a == b) && (a == '/'); });
result.erase(new_end, result.end());

std::string result;

if (n_url_length > 1 && n_url[n_url_length - 1] == '/') {
result = n_url.substr(0, n_url_length - 1);
} else {
result = n_url;
if (result.length() > 1 && result.back() == '/') {
result.pop_back();
}

return result;
Expand Down Expand Up @@ -302,13 +298,19 @@ uint16_t get_port(const struct sockaddr* sa) {
}
}

static inline int hex_digit_value(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}

size_t http_unescape(std::string* val) {
if (val->empty()) return 0;

unsigned int rpos = 0;
unsigned int wpos = 0;

unsigned int num;
unsigned int size = val->size();

while (rpos < size && (*val)[rpos] != '\0') {
Expand All @@ -319,11 +321,15 @@ size_t http_unescape(std::string* val) {
rpos++;
break;
case '%':
if (size > rpos + 2 && ((1 == sscanf(val->substr(rpos + 1, 2).c_str(), "%2x", &num)) || (1 == sscanf(val->substr(rpos + 1, 2).c_str(), "%2X", &num)))) {
(*val)[wpos] = (unsigned char) num;
wpos++;
rpos += 3;
break;
if (size > rpos + 2) {
int hi = hex_digit_value((*val)[rpos + 1]);
int lo = hex_digit_value((*val)[rpos + 2]);
if (hi >= 0 && lo >= 0) {
(*val)[wpos] = static_cast<unsigned char>((hi << 4) | lo);
wpos++;
rpos += 3;
break;
}
}
// intentional fall through!
default:
Expand Down
4 changes: 2 additions & 2 deletions src/httpserver/details/modded_request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ namespace details {

struct modded_request {
struct MHD_PostProcessor *pp = nullptr;
std::unique_ptr<std::string> complete_uri;
std::unique_ptr<std::string> standardized_url;
std::string complete_uri;
std::string standardized_url;
webserver* ws = nullptr;

std::shared_ptr<http_response> (httpserver::http_resource::*callback)(const httpserver::http_request&);
Expand Down
24 changes: 19 additions & 5 deletions src/httpserver/http_request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ class http_request {
* @return a vector of strings containing all pieces
**/
const std::vector<std::string> get_path_pieces() const {
return http::http_utils::tokenize_url(path);
ensure_path_pieces_cached();
return cache->path_pieces;
}

/**
Expand All @@ -102,9 +103,9 @@ class http_request {
* @return the selected piece in form of string
**/
const std::string get_path_piece(int index) const {
std::vector<std::string> post_path = get_path_pieces();
if (static_cast<int>(post_path.size()) > index) {
return post_path[index];
ensure_path_pieces_cached();
if (static_cast<int>(cache->path_pieces.size()) > index) {
return cache->path_pieces[index];
}
return EMPTY;
}
Expand Down Expand Up @@ -426,7 +427,11 @@ class http_request {
std::string_view get_connection_value(std::string_view key, enum MHD_ValueKind kind) const;
const http::header_view_map get_headerlike_values(enum MHD_ValueKind kind) const;

// Cache certain data items on demand so we can consistently return views
// http_request objects are owned by a single connection and are not
// shared across threads. Lazy caching (path_pieces, args, etc.) is
// safe without synchronization under this invariant.

// Cache certain data items on demand so we can consistently return views
// over the data. Some things we transform before returning to the user for
// simplicity (e.g. query_str, requestor), others out of necessity (arg unescaping).
// Others (username, password, digested_user) MHD returns as char* that we need
Expand All @@ -440,10 +445,19 @@ class http_request {
std::string digested_user;
#endif // HAVE_DAUTH
std::map<std::string, std::vector<std::string>, http::arg_comparator> unescaped_args;
std::vector<std::string> path_pieces;

bool args_populated = false;
bool path_pieces_cached = false;
};
std::unique_ptr<http_request_data_cache> cache = std::make_unique<http_request_data_cache>();
void ensure_path_pieces_cached() const {
if (!cache->path_pieces_cached) {
cache->path_pieces = http::http_utils::tokenize_url(path);
cache->path_pieces_cached = true;
}
}

// Populate the data cache unescaped_args
void populate_args() const;

Expand Down
15 changes: 15 additions & 0 deletions src/httpserver/webserver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
#include <sys/socket.h>
#endif

#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>

#ifdef HAVE_GNUTLS
Expand Down Expand Up @@ -188,6 +191,16 @@ class webserver {
std::shared_mutex registered_resources_mutex;
std::map<details::http_endpoint, http_resource*> registered_resources;
std::map<std::string, http_resource*> registered_resources_str;
std::map<details::http_endpoint, http_resource*> registered_resources_regex;

struct route_cache_entry {
details::http_endpoint matched_endpoint;
http_resource* resource;
};
static constexpr size_t ROUTE_CACHE_MAX_SIZE = 256;
std::mutex route_cache_mutex;
std::list<std::pair<std::string, route_cache_entry>> route_cache_list;
std::unordered_map<std::string, std::list<std::pair<std::string, route_cache_entry>>::iterator> route_cache_map;

std::shared_mutex bans_mutex;
std::set<http::ip_representation> bans;
Expand Down Expand Up @@ -226,6 +239,8 @@ class webserver {

MHD_Result complete_request(MHD_Connection* connection, struct details::modded_request* mr, const char* version, const char* method);

void invalidate_route_cache();

#ifdef HAVE_GNUTLS
// MHD_PskServerCredentialsCallback signature
static int psk_cred_handler_func(void* cls,
Expand Down
26 changes: 21 additions & 5 deletions src/string_utilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@

#include <algorithm>
#include <cctype>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

namespace httpserver {
Expand All @@ -45,13 +45,29 @@ const std::string to_lower_copy(const std::string& str) {

const std::vector<std::string> string_split(const std::string& s, char sep, bool collapse) {
std::vector<std::string> result;
if (s.empty()) return result;

std::istringstream buf(s);
for (std::string token; getline(buf, token, sep); ) {
if ((collapse && token != "") || !collapse) {
result.push_back(token);
std::string::size_type start = 0;
std::string::size_type end;

while ((end = s.find(sep, start)) != std::string::npos) {
std::string token = s.substr(start, end - start);
if (!collapse || !token.empty()) {
result.push_back(std::move(token));
}
start = end + 1;
}

// Handle the last token (after the final separator)
// Only add if there's content or if not collapsing
// Note: match istringstream behavior which does not emit trailing empty token
if (start < s.size()) {
std::string token = s.substr(start);
if (!collapse || !token.empty()) {
result.push_back(std::move(token));
}
}

return result;
}

Expand Down
Loading
Loading