diff --git a/src/HttpParser.h b/src/HttpParser.h index 8249dd7..61759c4 100644 --- a/src/HttpParser.h +++ b/src/HttpParser.h @@ -1,45 +1,37 @@ #ifndef HTTPPARSER_H #define HTTPPARSER_H -// this whole header needs fixing and testing as a separate module, fuzzing testing - #include #include #include struct HttpRequest { + const static int MAX_HEADERS = 50; struct Header { - char *key, *value; - unsigned int keyLength, valueLength; + std::string_view key, value; + } headers[MAX_HEADERS]; - operator bool() { - return key; - } - }; + static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct Header *headers) { + char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer; - #define MAX_HEADERS 50 - Header headers[MAX_HEADERS]; - - // UNSAFETY NOTE: assumes *end == '\r' (might unref end pointer) - static char *getHeaders(char *buffer, char *end, struct Header *headers, size_t maxHeaders) { - for (unsigned int i = 0; i < maxHeaders; i++) { - for (headers->key = buffer; (*buffer != ':') & (*buffer > 32); *(buffer++) |= 32); - if (*buffer == '\r') { - if ((buffer != end) & (buffer[1] == '\n') & (i > 0)) { - headers->key = 0; - return buffer + 2; + for (unsigned int i = 0; i < MAX_HEADERS; i++) { + for (preliminaryKey = postPaddedBuffer; (*postPaddedBuffer != ':') & (*postPaddedBuffer > 32); *(postPaddedBuffer++) |= 32); + if (*postPaddedBuffer == '\r') { + if ((postPaddedBuffer != end) & (postPaddedBuffer[1] == '\n') & (i > 0)) { + headers->key = std::string_view(nullptr, 0); + return (postPaddedBuffer + 2) - start; } else { return 0; } } else { - headers->keyLength = (unsigned int) (buffer - headers->key); - for (buffer++; (*buffer == ':' || *buffer < 33) && *buffer != '\r'; buffer++); - headers->value = buffer; - buffer = (char *) memchr(buffer, '\r', end - buffer); //for (; *buffer != '\r'; buffer++); - if (buffer /*!= end*/ && buffer[1] == '\n') { - headers->valueLength = (unsigned int) (buffer - headers->value); - buffer += 2; + headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey)); + for (postPaddedBuffer++; (*postPaddedBuffer == ':' || *postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++); + preliminaryValue = postPaddedBuffer; + postPaddedBuffer = (char *) memchr(postPaddedBuffer, '\r', end - postPaddedBuffer); + if (postPaddedBuffer && postPaddedBuffer[1] == '\n') { + headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue)); + postPaddedBuffer += 2; headers++; } else { return 0; @@ -49,121 +41,85 @@ struct HttpRequest { return 0; } - - int consumePostPadded(char *data, int length) { - char *cursor = data; - - if (cursor = getHeaders(data, data + length, headers, MAX_HEADERS)) { - // strip out the initial stuff - headers->valueLength = std::max(0, headers->valueLength - 9); - } - - if (cursor == nullptr) { - return 0; - } - - return cursor - data; - } - - void fenceRegion(char *data, int length) { - data[length] = '\r'; - } - - Header getHeader(const char *key, size_t length) { - if (headers) { - for (Header *h = headers; *++h; ) { - if (h->keyLength == length && !strncmp(h->key, key, length)) { - return *h; - } + std::string_view getHeader(std::string_view header) { + for (Header *h = headers; (++h)->key.length(); ) { + if (h->key.length() == header.length() && !strncmp(h->key.data(), header.data(), header.length())) { + return h->value; } } - return {nullptr, nullptr, 0, 0}; - } - - std::string_view getHeader(std::string_view header) { - Header h = getHeader(header.data(), header.length()); - - if (h.key) { - return std::string_view(h.value, h.valueLength); - } - return std::string_view(nullptr, 0); } - /*struct HttpRequest { - // std::string_view getUrl() - // std::string_view getHeader(std::string_view) - };*/ - std::string_view getUrl() { - return std::string_view(headers[0].value, headers[0].valueLength); + return headers->value; } }; class HttpParser { -public://private: + +private: std::string fallback; int remainingStreamingBytes = 0; const size_t MAX_FALLBACK_SIZE = 1024 * 4; -public: - - int str2int(const char *str, int len) { - int i; - int ret = 0; - for (i = 0; i < len; i++) { - ret = ret * 10 + (str[i] - '0'); + unsigned int toUnsignedInteger(std::string_view str) { + int unsignedIntegerValue = 0; + for (unsigned char c : str) { + unsignedIntegerValue = unsignedIntegerValue * 10 + (c - '0'); } - return ret; + return unsignedIntegerValue; } - // think about better interface for this one. HttpParser::consumePostPadded(data, length, httpData, onHttpRequest) - template - inline int fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, std::function &requestHandler, std::function &dataHandler) { - int ret = 0; + // the only caller of getHeaders + template + int fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, std::function &requestHandler, std::function &dataHandler) { + int consumedTotal = 0; + data[length] = '\r'; - int consumed = 0; - req->fenceRegion(data, length); - while (length && (consumed = req->consumePostPadded(data, length))) { + for (int consumed; length && (consumed = HttpRequest::getHeaders(data, data + length, req->headers)); ) { data += consumed; length -= consumed; + consumedTotal += consumed; - ret += consumed; + req->headers->value = std::string_view(req->headers->value.data(), std::max(0, req->headers->value.length() - 9)); requestHandler(user, req); - if (std::string_view contentLengthString = req->getHeader("content-length"); contentLengthString.length()) { - remainingStreamingBytes = str2int(contentLengthString.data(), contentLengthString.length()); - int emittable = std::min(remainingStreamingBytes, length); - dataHandler(user, std::string_view(data, emittable)); - remainingStreamingBytes -= emittable; - data += emittable; // denna var buggen? - length -= emittable; + std::string_view contentLengthString = req->getHeader("content-length"); + if (contentLengthString.length()) { + remainingStreamingBytes = toUnsignedInteger(contentLengthString); - ret += emittable; + if (!CONSUME_MINIMALLY) { + int emittable = std::min(remainingStreamingBytes, length); + dataHandler(user, std::string_view(data, emittable)); + remainingStreamingBytes -= emittable; + + data += emittable; + length -= emittable; + consumedTotal += emittable; + } } - if (LIMIT_TO_ONE_REQUEST) { + if (CONSUME_MINIMALLY) { break; } } - return ret; + return consumedTotal; } +public: + // todo: what can we do with the socket inside the handlers? we need to check on return from any handler if we closed or terminated or upgraded the socket inline void consumePostPadded(char *data, int length, void *user, std::function &&requestHandler, std::function &&dataHandler, std::function &&errorHandler) { HttpRequest req; if (remainingStreamingBytes) { - // at this point we reset the timeout timer, we are streaming and we got a chunk - if (remainingStreamingBytes >= length) { dataHandler(user, std::string_view(data, length)); remainingStreamingBytes -= length; - // no change to the socket here! we read all data in the buffer, return return; } else { dataHandler(user, std::string_view(data, remainingStreamingBytes)); @@ -172,8 +128,6 @@ public: length -= remainingStreamingBytes; remainingStreamingBytes = 0; - - // okay we are done with that, let's parse some more } } else if (fallback.length()) { int had = fallback.length(); @@ -183,26 +137,19 @@ public: fallback.reserve(maxCopyDistance + 32); // padding should be same as libus fallback.append(data, maxCopyDistance); - // helst ska denna inte emitta någon data alls, vi gör det efteråt! - if (int consumed = fenceAndConsumePostPadded(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler); consumed) { + int consumed = fenceAndConsumePostPadded(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler); + if (consumed) { - // I guess? fallback.clear(); data += consumed - had; length -= consumed - had; - // ska vi inte tömma fallback här? - - - // exakt samma if-sats som ovan! + // this is exactly the same as above! if (remainingStreamingBytes) { - // at this point we reset the timeout timer, we are streaming and we got a chunk - if (remainingStreamingBytes >= length) { dataHandler(user, std::string_view(data, length)); remainingStreamingBytes -= length; - // no change to the socket here! we read all data in the buffer, return return; } else { dataHandler(user, std::string_view(data, remainingStreamingBytes)); @@ -211,17 +158,13 @@ public: length -= remainingStreamingBytes; remainingStreamingBytes = 0; - - // okay we are done with that, let's parse some more } } } else { if (fallback.length() == MAX_FALLBACK_SIZE) { - // here we failed to parse any header in the 4kb we were given! - std::cout << "INVALID HTTP! no more chances!" << std::endl; + errorHandler(user); } - // no change in socket, or a closed socket! return; } } @@ -235,12 +178,9 @@ public: if (length < MAX_FALLBACK_SIZE) { fallback.append(data, length); } else { - std::cout << "tail is invalid http!" << std::endl; - exit(-1); + errorHandler(user); } } - - // vore najs om invalid http kunde hamna i samma ställe av koden! } }; diff --git a/src/Tests.h b/src/Tests.h index eaf956c..c125ee2 100644 --- a/src/Tests.h +++ b/src/Tests.h @@ -23,15 +23,13 @@ void testHttpParserPerformance() { char *data = (char *) malloc(requestLength * 10); int length = requestLength * 10; - //int currentOffset = 0; - int maxChunkSize = 10000; char *paddedBuffer = (char *) malloc(maxChunkSize + 32); // dela upp dessa 10 i 5 segment HttpParser httpParser; - int validRequests = 0; + int validRequests = 0, numDataEmits = 0, numChunks = 0; size_t dataBytes = 0; for (int i = 0; i < 10; i++) { @@ -46,17 +44,18 @@ void testHttpParserPerformance() { chunkSize = length - currentOffset; } - // kpiera skiten memcpy(paddedBuffer, data + currentOffset, chunkSize); - httpParser.consumePostPadded(/*data + currentOffset*/ paddedBuffer, chunkSize, nullptr, [&validRequests](void *user, HttpRequest *req) { - + httpParser.consumePostPadded(paddedBuffer, chunkSize, nullptr, [&validRequests](void *user, HttpRequest *req) { validRequests++; - std::cout << "validRequests: " << validRequests << std::endl; + if (req->getUrl() != "/hello.htm") { + std::cout << "WRONG URL!" << std::endl; + exit(-1); + } - }, [&dataBytes](void *, std::string_view data) { - //std::cout << "data bytes: " << data.length() << std::endl; + }, [&dataBytes, &numDataEmits](void *, std::string_view data) { + numDataEmits++; dataBytes += data.length(); }, [](void *) { @@ -64,15 +63,22 @@ void testHttpParserPerformance() { return; }); + numChunks++; + currentOffset += chunkSize; } } + std::cout << "validRequests: " << validRequests << std::endl; std::cout << "Data bytes: " << dataBytes << std::endl; + std::cout << "Data emits: " << numDataEmits << std::endl; + std::cout << "Chunks parsed: " << numChunks << std::endl; - /*auto start = std::chrono::high_resolution_clock::now(); + validRequests = 0; + + auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < 10000000; i++) { - httpParser.consumePostPadded(data, sizeof(data) - 2, nullptr, [&validRequests](void *user, HttpRequest *req) { + httpParser.consumePostPadded(request, requestLength, nullptr, [&validRequests](void *user, HttpRequest *req) { validRequests++; }, [](void *, std::string_view data) { @@ -80,9 +86,9 @@ void testHttpParserPerformance() { }); } - auto stop = std::chrono::high_resolution_clock::now();*/ + auto stop = std::chrono::high_resolution_clock::now(); - //std::cout << "Parsed " << validRequests << " in " << std::chrono::duration_cast(stop - start).count() << "ms" << std::endl; + std::cout << "Parsed " << validRequests << " in " << std::chrono::duration_cast(stop - start).count() << "ms" << std::endl; } #endif // TESTS_H