diff --git a/src/BloomFilter.h b/src/BloomFilter.h new file mode 100644 index 0000000..2547fd6 --- /dev/null +++ b/src/BloomFilter.h @@ -0,0 +1,65 @@ +/* + * Authored by Alex Hultman, 2018-2019. + * Intellectual property of third-party. + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UWS_BLOOMFILTER_H +#define UWS_BLOOMFILTER_H + +/* This filter has a decently low amount of false positives for the + * standard and non-standard common request headers */ + +#include +#include + +namespace uWS { + +struct BloomFilter { +private: + std::bitset<512> filter; + + unsigned int hash1(std::string_view key) { + return ((unsigned int)key[key.length() - 1] - (key.length() << 3)) & 511; + } + + unsigned int hash2(std::string_view key) { + return (((unsigned int)key[0] + (key.length() << 4)) & 511); + } + + unsigned int hash3(std::string_view key) { + return ((unsigned int)key[key.length() - 2] - 97 - (key.length() << 5)) & 511; + } + +public: + bool mightHave(std::string_view key) { + return filter.test(hash1(key)) && filter.test(hash2(key)) && (key.length() < 2 || filter.test(hash3(key))); + } + + void add(std::string_view key) { + filter.set(hash1(key)); + filter.set(hash2(key)); + if (key.length() >= 2) { + filter.set(hash3(key)); + } + } + + void reset() { + filter.reset(); + } +}; + +} + +#endif // UWS_BLOOMFILTER_H \ No newline at end of file diff --git a/src/HttpContext.h b/src/HttpContext.h index 201d45d..c7571e7 100644 --- a/src/HttpContext.h +++ b/src/HttpContext.h @@ -363,6 +363,13 @@ public: auto user = r->getUserData(); user.httpRequest->setYield(false); user.httpRequest->setParameters(r->getParameters()); + + /* Middleware? Automatically respond to expectations */ + std::string_view expect = user.httpRequest->getHeader("expect"); + if (expect.length() && expect == "100-continue") { + user.httpResponse->writeContinue(); + } + handler(user.httpResponse, user.httpRequest); /* If any handler yielded, the router will keep looking for a suitable handler. */ diff --git a/src/HttpParser.h b/src/HttpParser.h index a1c21e7..36ba3a3 100644 --- a/src/HttpParser.h +++ b/src/HttpParser.h @@ -27,6 +27,8 @@ #include #include "f2/function2.hpp" +#include "BloomFilter.h" + namespace uWS { /* We require at least this much post padding */ @@ -43,7 +45,7 @@ private: } headers[MAX_HEADERS]; int querySeparator; bool didYield; - + BloomFilter bf; std::pair currentParameters; public: @@ -87,9 +89,11 @@ public: } std::string_view getHeader(std::string_view lowerCasedHeader) { - for (Header *h = headers; (++h)->key.length(); ) { - if (h->key.length() == lowerCasedHeader.length() && !strncmp(h->key.data(), lowerCasedHeader.data(), lowerCasedHeader.length())) { - return h->value; + if (bf.mightHave(lowerCasedHeader)) { + for (Header *h = headers; (++h)->key.length(); ) { + if (h->key.length() == lowerCasedHeader.length() && !strncmp(h->key.data(), lowerCasedHeader.data(), lowerCasedHeader.length())) { + return h->value; + } } } return std::string_view(nullptr, 0); @@ -142,7 +146,7 @@ private: return unsignedIntegerValue; } - static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers) { + static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers, BloomFilter *bf) { char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer; for (unsigned int i = 0; i < HttpRequest::MAX_HEADERS; i++) { @@ -177,13 +181,19 @@ private: int consumedTotal = 0; data[length] = '\r'; - for (int consumed; length && (consumed = getHeaders(data, data + length, req->headers)); ) { + for (int consumed; length && (consumed = getHeaders(data, data + length, req->headers, &req->bf)); ) { data += consumed; length -= consumed; consumedTotal += consumed; + /* Strip away tail of first "header value" aka URL */ req->headers->value = std::string_view(req->headers->value.data(), std::max(0, (int) req->headers->value.length() - 9)); + /* Add all headers to bloom filter */ + for (HttpRequest::Header *h = req->headers; (++h)->key.length(); ) { + req->bf.add(h->key); + } + /* Parse query */ const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length()); req->querySeparator = (int) ((querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data()); diff --git a/src/HttpResponse.h b/src/HttpResponse.h index 05a0352..b04da93 100644 --- a/src/HttpResponse.h +++ b/src/HttpResponse.h @@ -174,6 +174,12 @@ public: /* Note: Headers are not checked in regards to timeout. * We only check when you actively push data or end the request */ + /* Write 100 Continue, can be done any amount of times */ + HttpResponse *writeContinue() { + Super::write("HTTP/1.1 100 Continue\r\n\r\n", 25); + return this; + } + /* Write the HTTP status */ HttpResponse *writeStatus(std::string_view status) { HttpResponseData *httpResponseData = getHttpResponseData();