Case sensitive method, trim trailing whitespace (correctly), host header may be empty

This commit is contained in:
Alex Hultman
2022-10-31 03:47:08 +01:00
parent cca4610d5d
commit 2b850f76d3
4 changed files with 94 additions and 50 deletions
+10 -10
View File
@@ -379,7 +379,7 @@ public:
/* Calculate idleTimeoutCompnents */
webSocketContext->getExt()->calculateIdleTimeoutCompnents(behavior.idleTimeout);
httpContext->onHttp("get", pattern, [webSocketContext, behavior = std::move(behavior)](auto *res, auto *req) mutable {
httpContext->onHttp("GET", pattern, [webSocketContext, behavior = std::move(behavior)](auto *res, auto *req) mutable {
/* If we have this header set, it's a websocket */
std::string_view secWebSocketKey = req->getHeader("sec-websocket-key");
@@ -438,63 +438,63 @@ public:
TemplatedApp &&get(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("get", pattern, std::move(handler));
httpContext->onHttp("GET", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&post(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("post", pattern, std::move(handler));
httpContext->onHttp("POST", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&options(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("options", pattern, std::move(handler));
httpContext->onHttp("OPTIONS", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&del(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("delete", pattern, std::move(handler));
httpContext->onHttp("DELETE", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&patch(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("patch", pattern, std::move(handler));
httpContext->onHttp("PATCH", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&put(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("put", pattern, std::move(handler));
httpContext->onHttp("PUT", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&head(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("head", pattern, std::move(handler));
httpContext->onHttp("HEAD", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&connect(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("connect", pattern, std::move(handler));
httpContext->onHttp("CONNECT", pattern, std::move(handler));
}
return std::move(*this);
}
TemplatedApp &&trace(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("trace", pattern, std::move(handler));
httpContext->onHttp("TRACE", pattern, std::move(handler));
}
return std::move(*this);
}
+2 -2
View File
@@ -170,7 +170,7 @@ private:
/* Route the method and URL */
selectedRouter->getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
if (!selectedRouter->route(httpRequest->getMethod(), httpRequest->getUrl())) {
if (!selectedRouter->route(httpRequest->getCaseSensitiveMethod(), httpRequest->getUrl())) {
/* We have to force close this socket as we have no handler for it */
us_socket_close(SSL, (us_socket_t *) s, 0, nullptr);
return nullptr;
@@ -423,7 +423,7 @@ public:
/* Todo: This is ugly, fix */
std::vector<std::string> methods;
if (method == "*") {
methods = httpContextData->currentRouter->methods;
methods = httpContextData->currentRouter->upperCasedMethods;
} else {
methods = {method};
}
+81 -36
View File
@@ -117,7 +117,17 @@ public:
return std::string_view(headers->value.data(), headers->value.length());
}
/* Hack: this should be getMethod */
std::string_view getCaseSensitiveMethod() {
return std::string_view(headers->key.data(), headers->key.length());
}
std::string_view getMethod() {
/* Compatibility hack: lower case method (todo: remove when major version bumps) */
for (unsigned int i = 0; i < headers->key.length(); i++) {
((char *) headers->key.data())[i] |= 32;
}
return std::string_view(headers->key.data(), headers->key.length());
}
@@ -208,11 +218,11 @@ private:
}
static inline uint64_t hasMore(uint64_t x, uint64_t n) {
return (((x)+~0UL/255*(127-(n))|(x))&~0UL/255*128);
return (( ((x)+~0UL/255*(127-(n))) |(x))&~0UL/255*128);
}
static inline uint64_t hasBetween(uint64_t x, uint64_t m, uint64_t n) {
return ((~0UL/255*(127+(n))-((x)&~0UL/255*127)&~(x)&((x)&~0UL/255*127)+~0UL/255*(127-(m)))&~0UL/255*128);
return (( (~0UL/255*(127+(n))-((x)&~0UL/255*127)) &~(x)& (((x)&~0UL/255*127)+~0UL/255*(127-(m))) )&~0UL/255*128);
}
static inline bool notFieldNameWord(uint64_t x) {
@@ -235,6 +245,33 @@ private:
}
}
/* Puts method as key, target as value and returns non-null (or nullptr on error). */
static inline char *consumeRequestLine(char *data, HttpRequest::Header &header) {
/* Scan until single SP, assume next is / (origin request) */
char *start = data;
/* This catches the post padded CR and fails */
while (data[0] > 32) data++;
if (data[0] == 32 && data[1] == '/') {
header.key = {start, (size_t) (data - start)};
data++;
/* Scan for less than 33 (catches post padded CR and fails) */
start = data;
for (; true; data += 8) {
if (hasLess(*(uint64_t *)data, 33)) {
while (*(unsigned char *)data > 32) data++;
/* Now we stand on space */
header.value = {start, (size_t) (data - start)};
/* Check that the following is http 1.1 */
if (memcmp(" HTTP/1.1\r\n", data, 11) == 0) {
return data + 11;
}
return nullptr;
}
}
}
return nullptr;
}
/* RFC 9110: 5.5 Field Values (TLDR; anything above 31 is allowed; htab (9) is also allowed)
* Field values are usually constrained to the range of US-ASCII characters [...]
* Field values containing CR, LF, or NUL characters are invalid and dangerous [...]
@@ -249,7 +286,7 @@ private:
}
/* End is only used for the proxy parser. The HTTP parser recognizes "\ra" as invalid "\r\n" scan and breaks. */
static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers, void *reserved) {
static unsigned int getHeaders(char *postPaddedBuffer, struct HttpRequest::Header *headers, void *reserved) {
char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer;
#ifdef UWS_WITH_PROXY
@@ -277,35 +314,40 @@ private:
* for PROXY means we can end up succeeding, yet leaving bytes in the fallback buffer
* which is then removed, and our counters to flip due to overflow and we end up with a crash */
for (unsigned int i = 0; i < HttpRequest::MAX_HEADERS - 1; i++) {
/* The request line is different from the field names / field values */
if (!(postPaddedBuffer = consumeRequestLine(postPaddedBuffer, headers[0]))) {
/* Error - invalid request line */
return 0;
}
headers++;
for (unsigned int i = 1; i < HttpRequest::MAX_HEADERS - 1; i++) {
/* Lower case and consume the field name */
preliminaryKey = postPaddedBuffer;
postPaddedBuffer = (char *) consumeFieldName(postPaddedBuffer);
headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey));
/* Assume colon, space follows (this is fine as we have at least 2 bytes past) */
if (postPaddedBuffer[0] == ':' && postPaddedBuffer[1] == ' ') {
postPaddedBuffer += 2;
} else {
/* We should not accept whitespace between key and colon (unless on request line) */
if (i && postPaddedBuffer[0] != ':') {
return 0;
}
/* Trim until value starts */
for (; (*postPaddedBuffer == ':' || *(unsigned char *)postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++);
/* We should not accept whitespace between key and colon, so colon must foloow immediately */
if (postPaddedBuffer[0] != ':') {
/* Error: invalid chars in field name */
return 0;
}
preliminaryValue = postPaddedBuffer;
/* The goal of this call is to find next "\r\n", or any invalid field value chars, fast */
retry:
postPaddedBuffer = (char *) tryConsumeFieldValue(postPaddedBuffer);
/* If this is not CR then we caught some stinky invalid char on the way */
if (postPaddedBuffer[0] != '\r') {
/* If TAB then keep searching */
if (postPaddedBuffer[0] == '\t') {
postPaddedBuffer++;
goto retry;
while (true) {
postPaddedBuffer = (char *) tryConsumeFieldValue(postPaddedBuffer);
/* If this is not CR then we caught some stinky invalid char on the way */
if (postPaddedBuffer[0] != '\r') {
/* If TAB then keep searching */
if (postPaddedBuffer[0] == '\t') {
postPaddedBuffer++;
continue;
}
/* Error - invalid chars in field value */
return 0;
}
return 0;
break;
}
/* We fence end[0] with \r, followed by end[1] being something that is "not \n", to signify "not found".
* This way we can have this one single check to see if we found \r\n WITHIN our allowed search space. */
@@ -313,6 +355,17 @@ private:
/* Store this header, it is valid */
headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue));
postPaddedBuffer += 2;
/* Trim trailing whitespace (SP, HTAB) */
while (headers->value.length() && headers->value.back() < 33) {
headers->value.remove_suffix(1);
}
/* Trim initial whitespace (SP, HTAB) */
while (headers->value.length() && headers->value.front() < 33) {
headers->value.remove_prefix(1);
}
headers++;
/* We definitely have at least one header (or request line), so check if we are done */
@@ -350,21 +403,13 @@ private:
data[length] = '\r';
data[length + 1] = 'a'; /* Anything that is not \n, to trigger "invalid request" */
for (unsigned int consumed; length && (consumed = getHeaders(data, data + length, req->headers, reserved)); ) {
for (unsigned int consumed; length && (consumed = getHeaders(data, req->headers, reserved)); ) {
data += consumed;
length -= consumed;
consumedTotal += consumed;
/* Store HTTP version (ancient 1.0 or 1.1) */
req->ancientHttp = req->headers->value.length() && (req->headers->value[req->headers->value.length() - 1] == '0');
/* We do not support ancient HTTP versions! */
if (req->isAncient()) {
return {0, FULLPTR};
}
/* Strip away tail of first "header value" aka URL */
req->headers->value = std::string_view(req->headers->value.data(), (size_t) std::max<int>(0, (int) req->headers->value.length() - 9));
req->ancientHttp = false;
/* Add all headers to bloom filter */
req->bf.reset();
@@ -372,8 +417,8 @@ private:
req->bf.add(h->key);
}
/* Break if no host header */
if (!req->getHeader("host").length()) {
/* Break if no host header (but we can have empty string which is different from nullptr) */
if (!req->getHeader("host").data()) {
return {0, FULLPTR};
}
+1 -2
View File
@@ -36,7 +36,6 @@ namespace uWS {
template <class USERDATA>
struct HttpRouter {
/* These are public for now */
std::vector<std::string> methods = {"get", "post", "head", "put", "delete", "connect", "options", "trace", "patch"};
std::vector<std::string> upperCasedMethods = {"GET", "POST", "HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"};
static const uint32_t HIGH_PRIORITY = 0xd0000000, MEDIUM_PRIORITY = 0xe0000000, LOW_PRIORITY = 0xf0000000;
@@ -247,7 +246,7 @@ private:
public:
HttpRouter() {
int p = 0;
for (std::string &method : methods) {
for (std::string &method : upperCasedMethods) {
priority[method] = p++;
}
}