#ifndef HTTPPARSER_H #define HTTPPARSER_H #include #include #include class HttpRequest { friend class HttpParser; private: const static int MAX_HEADERS = 50; struct Header { std::string_view key, value; } headers[MAX_HEADERS]; int querySeparator; public: 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 std::string_view(nullptr, 0); } // todo: implement this /*int getHeader(std::string_view header) { return 0; }*/ std::string_view getUrl() { return std::string_view(headers->value.data(), querySeparator); } std::string_view getQuery() { return std::string_view(headers->value.data() + querySeparator, headers->value.length() - querySeparator); } }; class HttpParser { private: std::string fallback; int remainingStreamingBytes = 0; const size_t MAX_FALLBACK_SIZE = 1024 * 4; static unsigned int toUnsignedInteger(std::string_view str) { int unsignedIntegerValue = 0; for (unsigned char c : str) { unsignedIntegerValue = unsignedIntegerValue * 10 + (c - '0'); } return unsignedIntegerValue; } static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers) { char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer; for (unsigned int i = 0; i < HttpRequest::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->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; } } } return 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'; for (int consumed; length && (consumed = getHeaders(data, data + length, req->headers)); ) { data += consumed; length -= consumed; consumedTotal += consumed; req->headers->value = std::string_view(req->headers->value.data(), std::max(0, req->headers->value.length() - 9)); // querySeparator is untested, todo: go through this const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length()); req->querySeparator = (querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data(); requestHandler(user, req); std::string_view contentLengthString = req->getHeader("content-length"); if (contentLengthString.length()) { remainingStreamingBytes = toUnsignedInteger(contentLengthString); 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 (CONSUME_MINIMALLY) { break; } } 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 void consumePostPadded(char *data, int length, void *user, std::function &&requestHandler, std::function &&dataHandler, std::function &&errorHandler) { HttpRequest req; if (remainingStreamingBytes) { if (remainingStreamingBytes >= length) { dataHandler(user, std::string_view(data, length)); remainingStreamingBytes -= length; return; } else { dataHandler(user, std::string_view(data, remainingStreamingBytes)); data += remainingStreamingBytes; length -= remainingStreamingBytes; remainingStreamingBytes = 0; } } 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); int consumed = fenceAndConsumePostPadded(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler); if (consumed) { fallback.clear(); data += consumed - had; length -= consumed - had; // this is exactly the same as above! if (remainingStreamingBytes) { if (remainingStreamingBytes >= length) { dataHandler(user, std::string_view(data, length)); remainingStreamingBytes -= length; return; } else { dataHandler(user, std::string_view(data, remainingStreamingBytes)); data += remainingStreamingBytes; length -= remainingStreamingBytes; remainingStreamingBytes = 0; } } } else { if (fallback.length() == MAX_FALLBACK_SIZE) { errorHandler(user); } 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 { errorHandler(user); } } } }; #endif // HTTPPARSER_H