diff --git a/15.pro b/15.pro index e273920..16c568c 100644 --- a/15.pro +++ b/15.pro @@ -17,8 +17,8 @@ HEADERS += \ src/HttpRouter.h \ src/Loop.h \ src/App.h \ - src/HttpRequest.h \ - src/HttpSocket.h + src/HttpSocket.h \ + src/HttpParser.h INCLUDEPATH += uSockets/src src #QMAKE_CXXFLAGS += -fsanitize=address diff --git a/src/App.h b/src/App.h index dd3343c..09e9165 100644 --- a/src/App.h +++ b/src/App.h @@ -6,7 +6,6 @@ #include "Loop.h" #include "HttpSocket.h" #include "HttpRouter.h" -#include "HttpRequest.h" namespace uWS { @@ -86,6 +85,8 @@ protected: static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(httpServerContext, [](auto *s, char *data, int length) { Data *appData = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s)); + + // onHttpRequest should probably be hard-coded to HttpRouter ((HttpSocket *) s)->onData(data, length, appData->onHttpRequest); }); diff --git a/src/HttpParser.h b/src/HttpParser.h new file mode 100644 index 0000000..b0dd3de --- /dev/null +++ b/src/HttpParser.h @@ -0,0 +1,210 @@ +#ifndef HTTPPARSER_H +#define HTTPPARSER_H + +// this whole header needs fixing and testing as a separate module, fuzzing testing + +#include +#include +#include + +struct HttpRequest { + + struct Header { + char *key, *value; + unsigned int keyLength, valueLength; + + operator bool() { + return key; + } + }; + + #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; + } 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++; + } else { + return 0; + } + } + } + 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); + } + + 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; + } + } + } + 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); + } + +}; + +class HttpParser { +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'); + } + return ret; + } + + // think about better interface for this one. HttpParser::consumePostPadded(data, length, httpData, onHttpRequest) + template + int fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, std::function &requestHandler, std::function &dataHandler) { + int ret = 0; + + int consumed = 0; + req->fenceRegion(data, length); + while (length && (consumed = req->consumePostPadded(data, length))) { + data += consumed; + length -= consumed; + + ret += consumed; + + 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); + /*httpData->inStream*/dataHandler(user, std::string_view(data, emittable)); + remainingStreamingBytes -= emittable; + length -= emittable; + + ret += emittable; + } + + if (LIMIT_TO_ONE_REQUEST) { + break; + } + } + return ret; + } + + + 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)); + + data += remainingStreamingBytes; + length -= remainingStreamingBytes; + + remainingStreamingBytes = 0; + + // okay we are done with that, let's parse some more + } + } else if (fallback.length()) { + int had = fallback.length(); + + int maxCopyDistance = std::min(MAX_FALLBACK_SIZE - fallback.length(), (size_t) length); + + fallback.reserve(maxCopyDistance + 32); // padding should be same as libus + fallback.append(data, maxCopyDistance); + + if (int consumed = fenceAndConsumePostPadded(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler); consumed) { + data += consumed - had; + length -= consumed - had; + } 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; + } + // no change in socket, or a closed socket! + return; + } + } + + int consumed = fenceAndConsumePostPadded(data, length, user, &req, requestHandler, dataHandler); + data += consumed; + length -= consumed; + + if (length) { + if (length < MAX_FALLBACK_SIZE) { + fallback.append(data, length); + } else { + // invalid http! + std::cout << "invalid http! fuck off!" << std::endl; + } + } + + // vore najs om invalid http kunde hamna i samma ställe av koden! + } +}; + +#endif // HTTPPARSER_H diff --git a/src/HttpRequest.h b/src/HttpRequest.h deleted file mode 100644 index f7eb134..0000000 --- a/src/HttpRequest.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef HTTPREQUEST_H -#define HTTPREQUEST_H - -#include -#include -#include - -// holds the header pointers and wrappers -struct HttpRequest { - - struct Header { - char *key, *value; - unsigned int keyLength, valueLength; - - operator bool() { - return key; - } - }; - - #define MAX_HEADERS 100 - 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; - } 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++; - } else { - return 0; - } - } - } - 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); - } - - 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; - } - } - } - 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); - } - - std::string_view getUrl() { - return std::string_view(headers[0].value, headers[0].valueLength); - } - -}; - -#endif // HTTPREQUEST_H diff --git a/src/HttpRouter.h b/src/HttpRouter.h index 8541cac..b233fe6 100644 --- a/src/HttpRouter.h +++ b/src/HttpRouter.h @@ -1,6 +1,8 @@ #ifndef HTTPROUTER_HPP #define HTTPROUTER_HPP +// this header also needs testing and fixing as a separate module + #include #include #include diff --git a/src/HttpSocket.h b/src/HttpSocket.h index 29fff1e..b79ccac 100644 --- a/src/HttpSocket.h +++ b/src/HttpSocket.h @@ -3,7 +3,7 @@ #include "libusockets.h" #include "Loop.h" -#include "HttpRequest.h" +#include "HttpParser.h" #include #include #include @@ -42,27 +42,14 @@ struct HttpSocket { return ret; } - int str2int(const char *str, int len) { - int i; - int ret = 0; - for (i = 0; i < len; i++) { - ret = ret * 10 + (str[i] - '0'); - } - return ret; - } - // chunked response will be tricky with this buffering scheme // if we do not fit, we can always use the header buffer for this (both in and out!) // put first 8kb chunk in the http buffer, then from there it's the stream's job! // httpheaders should only have 1 stream in and 1 stream out, but we can have helper wrappers - // data is stored in ext struct Data { - // fallback buffering - std::string fallback; + HttpParser httpParser; - // these two control input streaming - int contentLength = 0; std::function inStream; // out streaming (.end should be a wrapper of this!) @@ -177,102 +164,14 @@ struct HttpSocket { void onData(char *data, int length, std::function *, HttpRequest *)> &onHttpRequest) { Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); - //std::cout << std::string_view(data, length) << std::endl; - - HttpRequest req; - - req.fenceRegion(data, length); - - if (httpData->contentLength) { - // at this point we reset the timeout timer - if (httpData->contentLength >= length) { - httpData->inStream(std::string_view(data, length)); - httpData->contentLength -= length; - // no change to the socket here! - return; - } else { - httpData->inStream(std::string_view(data, httpData->contentLength)); - - data += httpData->contentLength; - length -= httpData->contentLength; - - httpData->contentLength = 0; - } - } else if (httpData->fallback.length()) { - int maxCopyDistance = std::min(MAX_FALLBACK_SIZE - httpData->fallback.length(), (size_t) length); - - httpData->fallback.reserve(maxCopyDistance + 32); // padding should be same as libus - httpData->fallback.append(data, maxCopyDistance); - - - if (int consumed = req.consumePostPadded(httpData->fallback.data(), httpData->fallback.length()); consumed) { - httpData->fallback.clear(); - - data += consumed; - length -= consumed; - - onHttpRequest(this, &req); - - // see if we can read any posted data here (do we have contentLength header set?) - } else { - if (httpData->fallback.length() < MAX_FALLBACK_SIZE) { - std::cout << "Http headers coming in chunks I see, fine I'll pass!" << std::endl; - } else { - // here we failed to parse any header in the 4kb we were given! - std::cout << "INVALID HTTP! no more chances!" << std::endl; - } - // no change in socket, or a closed socket! - return; - } - } - - for (int consumed = 0; length && (consumed = req.consumePostPadded(data, length)); ) { - - //std::cout << "Parsing now <" << std::string_view(data, length) << ">" << std::endl; - - data += consumed; - length -= consumed; - - // first emit the request - onHttpRequest(this, &req); - - // then consume and stream any data! - if (std::string_view contentLength = req.getHeader("content-length"); contentLength.length()) { - // can we read everything off right now? - - httpData->contentLength = str2int(contentLength.data(), contentLength.length()); - - //std::cout << "content length!" << std::endl; - - - int emittable = std::min(httpData->contentLength, length); - - - //std::cout << "Emittable: " << emittable << std::endl; - - httpData->inStream(std::string_view(data, emittable)); - - - httpData->contentLength -= emittable; - - - length -= emittable; - - // otherwise, enter contentLength state! - } else { - //std::cout << "We don't have content-length!" << std::endl; - } - - } - - if (length) { - if (length < MAX_FALLBACK_SIZE) { - // buffer up for next - } else { - // invalid http! - std::cout << "invalid http! fuck off!" << std::endl; - } - } + // todo: this is where the HttpSocket binds together HttpParser and HttpRouter into one + httpData->httpParser.consumePostPadded(data, length, this, [&onHttpRequest](void *user, HttpRequest *httpRequest) { + onHttpRequest((HttpSocket *) user, httpRequest); + }, [httpData](void *user, std::string_view data) { + httpData->inStream(data); + }, [](void *user) { + std::cout << "INVALID HTTP!" << std::endl; + }); } void read(decltype(Data::inStream) stream) {