Move HttpParser into own module
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<SSL> *) s)->onData(data, length, appData->onHttpRequest);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
#ifndef HTTPPARSER_H
|
||||
#define HTTPPARSER_H
|
||||
|
||||
// this whole header needs fixing and testing as a separate module, fuzzing testing
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
|
||||
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<int>(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<limit or not>(data, length, httpData, onHttpRequest)
|
||||
template <int LIMIT_TO_ONE_REQUEST>
|
||||
int fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, std::function<void(void *, HttpRequest *)> &requestHandler, std::function<void(void *, std::string_view)> &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<void(void *, HttpRequest *)> requestHandler, std::function<void(void *, std::string_view)> dataHandler, std::function<void(void *)> 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<true>(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<false>(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
|
||||
@@ -1,94 +0,0 @@
|
||||
#ifndef HTTPREQUEST_H
|
||||
#define HTTPREQUEST_H
|
||||
|
||||
#include <string.h>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
// 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<int>(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
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef HTTPROUTER_HPP
|
||||
#define HTTPROUTER_HPP
|
||||
|
||||
// this header also needs testing and fixing as a separate module
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
+10
-111
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "libusockets.h"
|
||||
#include "Loop.h"
|
||||
#include "HttpRequest.h"
|
||||
#include "HttpParser.h"
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
@@ -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<void(std::string_view)> inStream;
|
||||
|
||||
// out streaming (.end should be a wrapper of this!)
|
||||
@@ -177,102 +164,14 @@ struct HttpSocket {
|
||||
void onData(char *data, int length, std::function<void(HttpSocket<SSL> *, 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<SSL> *) 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) {
|
||||
|
||||
Reference in New Issue
Block a user