Skip to content
Draft
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
16 changes: 14 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,23 @@ if ( DEFINED JAVA_BACKEND )
endif()

# set g++ compiler flags
# USE_LEGACY_CPP11: when ON the http library is compiled with -std=c++11 (legacy parser);
# the rest of the project also uses c++11. Default is c++23.
# Enable with: cmake -DUSE_LEGACY_CPP11=ON ...
option(USE_LEGACY_CPP11 "Build using the legacy C++11 HTTP parser implementation" OFF)
if(USE_LEGACY_CPP11)
set(HTTP_CXX_STD "-std=c++11")
add_compile_definitions(USE_LEGACY_CPP11)
message(STATUS "USE_LEGACY_CPP11=ON: compiling with -std=c++11 (legacy HTTP parser)")
else()
set(HTTP_CXX_STD "-std=c++23")
endif()

if ( DEFINED DEBUG_BUILD )
add_compile_definitions(DEBUG_BUILD)
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread -std=c++20 -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -fwrapv -rdynamic -fno-elide-constructors")
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread ${HTTP_CXX_STD} -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -fwrapv -rdynamic -fno-elide-constructors")
else()
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread -O3 -fstack-protector-strong -std=c++20 -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -DNDEBUG -fwrapv -fno-elide-constructors")
set(CMAKE_CXX_FLAGS "-g -fPIC -Wall -pthread -O3 -fstack-protector-strong ${HTTP_CXX_STD} -Wno-unused-result -Wsign-compare -Wformat -Werror=format-security -DNDEBUG -fwrapv -fno-elide-constructors")
endif()

# set boost specs
Expand Down
17 changes: 15 additions & 2 deletions lib/http/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# add source dir
aux_source_directory(. SRC_LIST_LIBHTTP)
# compile-time option: build with legacy C++11 HTTP parser instead of C++23
# Enable with: cmake -DUSE_LEGACY_CPP11=ON ...
option(USE_LEGACY_CPP11 "Build using the legacy C++11 HTTP parser implementation" OFF)

if(USE_LEGACY_CPP11)
# C++11 sources: legacy destructive-split / no string_view implementation
set(SRC_LIST_LIBHTTP httpparser-cpp11.cpp httpgenerator.cpp)
else()
# C++23 sources: optimised ispanstream / string_view / transparent-map implementation
set(SRC_LIST_LIBHTTP httpparser.cpp httpgenerator.cpp)
endif()

# add library
add_library(httpparser STATIC ${SRC_LIST_LIBHTTP})
Expand All @@ -9,6 +18,7 @@ add_custom_target(
httpparserheader
SOURCES
./httpparser.hpp
./httpparser-cpp11.hpp
./httpgenerator.hpp
)

Expand All @@ -22,6 +32,9 @@ install(
install(
FILES
./httpparser.hpp
./httpparser-cpp11.hpp
./httpgenerator.hpp
./httpconstants.hpp
./httpconstants-cpp11.hpp
DESTINATION /usr/local/include
)
38 changes: 38 additions & 0 deletions lib/http/httpconstants-cpp11.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

//- Legacy C++11 compatible constants header.
//- Functionally identical to httpconstants.hpp; usable with -std=c++11 or later.

#include <string>
#include <vector>
#include <stdint.h>

//- constant expressions
constexpr uint16_t HTTP_VERSION_UNKNOWN = 0;
constexpr uint16_t HTTP_VERSION_1_1 = 1;

constexpr uint16_t HTTP_METHOD_OTHER = 0;
constexpr uint16_t HTTP_METHOD_GET = 1;
constexpr uint16_t HTTP_METHOD_POST = 2;

constexpr uint16_t HTTP_POST_MAX_CONTENT_LENGTH = 2048;

constexpr uint16_t URL_PARAM_NOT_FOUND_ERROR = 10;

//- constant expressions (error)
constexpr uint16_t HTTP_ERROR_PARSE_BUFFER_EXCEEDED = 10; //- currently not implemented
constexpr uint16_t HTTP_ERROR_BAD_REQUEST = 400;

//- constant strings
static const std::string HTTP_1_1_END_MARKER("\r\n\r\n");
static const std::string HTTP_HEADER_CONTENT_LENGTH("Content-Length");

//- constant multidimensional
static const std::vector<std::string> HTTPHeaderAllowed //- currently not implemented
{
"Host",
"Transfer-Encoding",
"If-None-Match",
"Content-Type",
HTTP_HEADER_CONTENT_LENGTH
};
249 changes: 249 additions & 0 deletions lib/http/httpparser-cpp11.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#include "httpparser-cpp11.hpp"
#include "httpconstants-cpp11.hpp"

using namespace std;


HTTPParser::HTTPParser(const uint16_t BufferSize) :
_RequestCountGet(0),
_RequestCountPost(0),
_RequestParseError(0),
_POSTWaitContentLength(false),
_POSTContentLength(0),
_HTTPRequestBuffer("")
{
_HTTPRequestBuffer.reserve(BufferSize);
_HTTPRequestBufferMax = BufferSize;
}

HTTPParser::~HTTPParser()
{
}

void HTTPParser::appendBuffer(const char* BufferRef, const uint16_t RecvBytes)
{
if (_HTTPRequestBuffer.length()+RecvBytes > _HTTPRequestBufferMax) {
return;
}

_HTTPRequestBuffer.append(BufferRef, RecvBytes);

//- on incomplete (single) POST request
if (_POSTWaitContentLength == true && _HTTPRequestBuffer.length() >= _POSTContentLength) {
_RequestProperties.Payload = _HTTPRequestBuffer.substr(0, _POSTContentLength);
_HTTPRequestBuffer.erase(0, _POSTContentLength);

_Requests.push_back(_RequestProperties);

_POSTWaitContentLength = false;
}
else {
//- reset _SplittedRequests vector
_SplittedRequests.clear();

//- only process on minimum of 1 http request (end marker found)
const size_t EndMarkerFound = _HTTPRequestBuffer.find(HTTP_1_1_END_MARKER);

if (EndMarkerFound != string::npos) {
_processRequests();
}
}
}

const RequestsMap_t& HTTPParser::getRequests() const
{
return _Requests;
}

RequestsMapPtr_t HTTPParser::getRequestsPtr()
{
return &_Requests;
}

inline void HTTPParser::_processRequests()
{
//- split requests into _SplittedRequests vector
StringHelper::split(_HTTPRequestBuffer, HTTP_1_1_END_MARKER, _SplittedRequests);

//- iterate over splitted requests
for(size_t i=0; i<_SplittedRequests.size(); ++i) {
if (_processRequestProperties(i) == false) {
_RequestParseError = HTTP_ERROR_BAD_REQUEST;
break;
}
}
}

inline bool HTTPParser::_processRequestProperties(const size_t Index)
{
//- get request ref at vector index
string &Request = _SplittedRequests.at(Index);

//- on empty request return
if (Request.empty()) { return false; }

//- init unparsed base properties with default values (C++11 compatible)
_RequestProperties.HTTPVersion = HTTP_VERSION_UNKNOWN;
_RequestProperties.HTTPMethod = HTTP_METHOD_OTHER;
_RequestProperties.URL = "/";
_RequestProperties.Payload.clear();
_RequestProperties.RequestHeaders.clear();
_RequestProperties.URLParams.clear();

//- parse base properties
if (_parseRequestProperties(Request, _RequestProperties) == false) { return false; }

//- only process HTTP/1.1 requests
if (_RequestProperties.HTTPVersion != HTTP_VERSION_1_1) { return false; }

//- if not GET || POST method, return
if (_RequestProperties.HTTPMethod == HTTP_METHOD_OTHER) { return false; }

//- parse request headers
_parseRequestHeaders(Request, _RequestProperties.RequestHeaders);

//- GET request
if (_RequestProperties.HTTPMethod == HTTP_METHOD_GET) {
//- parse GET parameters
_parseGETParameter(_RequestProperties.URL, _RequestProperties.URLParams);

//- add request to requests map
_Requests.push_back(_RequestProperties);
}

//- POST request
else if (_RequestProperties.HTTPMethod == HTTP_METHOD_POST) {

//- if request does not contain content-length header
if (_RequestProperties.RequestHeaders.find(HTTP_HEADER_CONTENT_LENGTH) == _RequestProperties.RequestHeaders.end()) {
return false;
}

//- if content-length header contains non-digits
if (StringHelper::is_digits(_RequestProperties.RequestHeaders.at(HTTP_HEADER_CONTENT_LENGTH)) == false) {
return false;
}

_POSTContentLength = stoi(_RequestProperties.RequestHeaders.at(HTTP_HEADER_CONTENT_LENGTH));

//- if content-length exeeds maximum
if (_POSTContentLength > HTTP_POST_MAX_CONTENT_LENGTH) {
return false;
}

//- on single HTTP POST: payload after endmarker in _HTTPRequestBuffer
if (_SplittedRequests.size() == 1) {
//- content length bytes already in buffer
if (_HTTPRequestBuffer.length() >= _POSTContentLength) {
_RequestProperties.Payload = _HTTPRequestBuffer.substr(0, _POSTContentLength);
_HTTPRequestBuffer.erase(0, _POSTContentLength);

//- add request to requests map
_Requests.push_back(_RequestProperties);

}
else {
//- set flag to wait until content-length reached
_POSTWaitContentLength = true;
}
}
//- otherwise payload in next splitted message
else if (_SplittedRequests.size() > Index) {
string &NextRequest = _SplittedRequests.at(Index+1);
if (NextRequest.length() >= _POSTContentLength) {
_RequestProperties.Payload = NextRequest.substr(0, _POSTContentLength);
NextRequest.erase(0, _POSTContentLength);
//- add request to requests map
_Requests.push_back(_RequestProperties);
}
else {
return false;
}
}
}
return true;
}

inline bool HTTPParser::_parseRequestProperties(const string& Request, const RequestPropertiesRef_t ResultBaseProps)
{
//- find first line endline
size_t StartPos = Request.find("\r\n");

//-> if no headers (no \r\n), set start pos to end of string
if (StartPos == string::npos) {
StartPos = Request.length();
}

//- get base request properties
vector<string> SplitResult;
StringHelper::rsplit(Request, StartPos, " ", SplitResult);

//- on invalid vector size (!=3) abort
if (SplitResult.size() != 3) { return false; }

if (SplitResult.at(0).find("HTTP/1.1") != string::npos) {
ResultBaseProps.HTTPVersion = HTTP_VERSION_1_1;
}

if (SplitResult.at(2).find("GET") != string::npos) {
ResultBaseProps.HTTPMethod = HTTP_METHOD_GET;
}
else if (SplitResult.at(2).find("POST") != string::npos) {
ResultBaseProps.HTTPMethod = HTTP_METHOD_POST;
}

ResultBaseProps.URL = SplitResult.at(1);

return true;
}

inline void HTTPParser::_parseRequestHeaders(const string& RequestIn, RequestHeaderRef_t ResultRef)
{
//- copy request to allow destructive split
string Request = RequestIn;

//- split header section into individual lines
vector<string> Lines;
StringHelper::split(Request, "\r\n", Lines);
Lines.push_back(Request); //- push remainder after last \r\n

for (auto& Line : Lines) {
vector<string> HeaderPair;
StringHelper::rsplit(Line, Line.length(), ": ", HeaderPair);
if (HeaderPair.size() == 2 &&
!HeaderPair[0].empty() &&
!HeaderPair[1].empty())
{
ResultRef.emplace(HeaderPair[1], HeaderPair[0]); //- key, value
}
}
}

inline void HTTPParser::_parseGETParameter(const string& RequestURL, URLParamMapRef_t ResultRef)
{
//- only process on init character "?" found
const size_t URLParamsStartPos = RequestURL.find("?");

if (URLParamsStartPos != string::npos && RequestURL.length() > URLParamsStartPos) {

//- copy params substring (no string_view in C++11)
string URLParamsPart = RequestURL.substr(URLParamsStartPos + 1);

vector<string> ParamValuePairs;
StringHelper::split(URLParamsPart, "&", ParamValuePairs);
if (!URLParamsPart.empty()) ParamValuePairs.push_back(URLParamsPart);

//- loop over param-value pairs
for (auto& ParamValuePair : ParamValuePairs) {

const size_t PVPDelimiterPos = ParamValuePair.find("=");

if (PVPDelimiterPos != string::npos && PVPDelimiterPos != 0 && ParamValuePair.length() > PVPDelimiterPos) {
ResultRef.emplace(
ParamValuePair.substr(0, PVPDelimiterPos),
ParamValuePair.substr(PVPDelimiterPos + 1)
);
}
}
}
}
Loading
Loading