From a85c64ac690720330382b6e4f721fc4895d062de Mon Sep 17 00:00:00 2001 From: antao Date: Tue, 7 May 2019 14:04:37 +0800 Subject: [PATCH] Add two configuration options: the client_max_body_size and the client_max_websocket_message_size --- config.example.json | 10 +++- drogon_ctl/templates/config.csp | 10 +++- get_version.sh | 2 +- lib/inc/drogon/HttpAppFramework.h | 14 +++++ lib/src/ConfigLoader.cc | 88 +++++++++++++++++++++++++++++- lib/src/HttpAppFrameworkImpl.h | 6 ++ lib/src/HttpRequestParser.cc | 26 +++++++-- lib/src/WebSocketConnectionImpl.cc | 9 ++- 8 files changed, 153 insertions(+), 12 deletions(-) diff --git a/config.example.json b/config.example.json index 73a361f2..1139cb57 100644 --- a/config.example.json +++ b/config.example.json @@ -21,7 +21,7 @@ "port": 443, "https": true, //cert,key: Cert file path and key file path, empty by default, - //if empty, use global setting + //if empty, use the global setting "cert": "", "key": "" } @@ -151,7 +151,13 @@ //gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed //file with the extension ".gz" in the same path and send the compressed file to the client. //The default value of gzip_static is true. - "gzip_static": true + "gzip_static": true, + //client_max_body_size: Set the max body size of HTTP requests received by drogon. The default value is "1M". + //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit. + "client_max_body_size": "1M", + //client_max_websocket_message_size: Set the max size of messages sent by WebSocket client. The default value is "128K". + //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit. + "client_max_websocket_message_size": "128K" }, //plugins: Define all plugins running in the application "plugins": [{ diff --git a/drogon_ctl/templates/config.csp b/drogon_ctl/templates/config.csp index 73a361f2..1139cb57 100644 --- a/drogon_ctl/templates/config.csp +++ b/drogon_ctl/templates/config.csp @@ -21,7 +21,7 @@ "port": 443, "https": true, //cert,key: Cert file path and key file path, empty by default, - //if empty, use global setting + //if empty, use the global setting "cert": "", "key": "" } @@ -151,7 +151,13 @@ //gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed //file with the extension ".gz" in the same path and send the compressed file to the client. //The default value of gzip_static is true. - "gzip_static": true + "gzip_static": true, + //client_max_body_size: Set the max body size of HTTP requests received by drogon. The default value is "1M". + //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit. + "client_max_body_size": "1M", + //client_max_websocket_message_size: Set the max size of messages sent by WebSocket client. The default value is "128K". + //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit. + "client_max_websocket_message_size": "128K" }, //plugins: Define all plugins running in the application "plugins": [{ diff --git a/get_version.sh b/get_version.sh index 5e9574f4..9961822c 100755 --- a/get_version.sh +++ b/get_version.sh @@ -3,7 +3,7 @@ GIT_VER=$(git log|grep ^commit|wc -l|sed -e "s/^ *//") MD5=$(git log|head -1|awk '{printf $2}') TMP_FILE=/tmp/version -echo "#define VERSION \"0.9.33.$GIT_VER\"" > ${TMP_FILE} +echo "#define VERSION \"0.9.34.$GIT_VER\"" > ${TMP_FILE} echo "#define VERSION_MD5 \"$MD5\"" >> ${TMP_FILE} if [ ! -f $1 ];then mv -f ${TMP_FILE} $1 diff --git a/lib/inc/drogon/HttpAppFramework.h b/lib/inc/drogon/HttpAppFramework.h index ffa72b51..c30d232e 100755 --- a/lib/inc/drogon/HttpAppFramework.h +++ b/lib/inc/drogon/HttpAppFramework.h @@ -652,6 +652,20 @@ public: */ virtual void setGzipStatic(bool useGzipStatic) = 0; + ///Set the max body size of the requests received by drogon. The default value is 1M. + /** + * NOTE: + * This operation can be performed by an option in the configuration file. + */ + virtual void setClientMaxBodySize(size_t maxSize) = 0; + + ///Set the max size of messages sent by WebSocket client. The default value is 128K. + /** + * NOTE: + * This operation can be performed by an option in the configuration file. + */ + virtual void setClientMaxWebSocketMessageSize(size_t maxSize) = 0; + #if USE_ORM ///Get a database client by @param name /** diff --git a/lib/src/ConfigLoader.cc b/lib/src/ConfigLoader.cc index 7130a905..8ebdfa1d 100644 --- a/lib/src/ConfigLoader.cc +++ b/lib/src/ConfigLoader.cc @@ -20,9 +20,76 @@ #include #include #include +#include using namespace drogon; - +static bool bytesSize(std::string &sizeStr, size_t &size) +{ + if (sizeStr.empty()) + { + size = -1; + return true; + } + else + { + size = 1; + switch (sizeStr[sizeStr.length() - 1]) + { + case 'k': + case 'K': + size = 1024; + sizeStr.resize(sizeStr.length() - 1); + break; + case 'M': + case 'm': + size = (1024 * 1024); + sizeStr.resize(sizeStr.length() - 1); + break; + case 'g': + case 'G': + size = (1024 * 1024 * 1024); + sizeStr.resize(sizeStr.length() - 1); + break; +#if ((ULONG_MAX) != (UINT_MAX)) + //64bit system + case 't': + case 'T': + size = (1024 * 1024 * 1024 * 1024); + sizeStr.resize(sizeStr.length() - 1); + break; +#endif + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '7': + case '8': + case '9': + break; + default: + std::cerr << "Invalid value of client_max_body_size: " << sizeStr << std::endl; + return false; + break; + } + std::istringstream iss(sizeStr); + size_t tmpSize; + iss >> tmpSize; + if (iss.fail()) + { + std::cerr << "Invalid value of client_max_body_size: " << sizeStr << std::endl; + exit(-1); + } + if ((size_t(-1) / tmpSize) >= size) + size *= tmpSize; + else + { + size = -1; + } + return true; + } +} ConfigLoader::ConfigLoader(const std::string &configFile) { if (access(configFile.c_str(), 0) != 0) @@ -237,6 +304,25 @@ static void loadApp(const Json::Value &app) drogon::app().setPipeliningRequestsNumber(pipeliningReqs); auto useGzipStatic = app.get("gzip_static", true).asBool(); drogon::app().setGzipStatic(useGzipStatic); + auto maxBodySize = app.get("client_max_body_size", "1M").asString(); + size_t size; + if (bytesSize(maxBodySize, size)) + { + drogon::app().setClientMaxBodySize(size); + } + else + { + exit(-1); + } + auto maxWsMsgSize = app.get("client_max_websocket_message_size", "128K").asString(); + if (bytesSize(maxWsMsgSize, size)) + { + drogon::app().setClientMaxWebSocketMessageSize(size); + } + else + { + exit(-1); + } } static void loadDbClients(const Json::Value &dbClients) { diff --git a/lib/src/HttpAppFrameworkImpl.h b/lib/src/HttpAppFrameworkImpl.h index 7a826ce7..b8b14f06 100644 --- a/lib/src/HttpAppFrameworkImpl.h +++ b/lib/src/HttpAppFrameworkImpl.h @@ -171,6 +171,10 @@ public: virtual void setKeepaliveRequestsNumber(const size_t number) override { _keepaliveRequestsNumber = number; } virtual void setPipeliningRequestsNumber(const size_t number) override { _pipeliningRequestsNumber = number; } virtual void setGzipStatic(bool useGzipStatic) override { _gzipStaticFlag = useGzipStatic; } + virtual void setClientMaxBodySize(size_t maxSize) override { _clientMaxBodySize = maxSize; } + virtual void setClientMaxWebSocketMessageSize(size_t maxSize) override { _clientMaxWebSocketMessageSize = maxSize; } + size_t getClientMaxBodySize() { return _clientMaxBodySize; } + size_t getClientMaxWebSocketMessageSize() { return _clientMaxWebSocketMessageSize; } virtual std::vector> getHandlersInfo() const override; size_t keepaliveRequestsNumber() const { return _keepaliveRequestsNumber; } @@ -295,6 +299,8 @@ private: bool _useSendfile = true; bool _useGzip = true; bool _gzipStaticFlag = true; + size_t _clientMaxBodySize = 1024 * 1024; + size_t _clientMaxWebSocketMessageSize = 128 * 1024; int _staticFilesCacheTime = 5; std::unordered_map> _staticFilesCache; std::mutex _staticFilesCacheMutex; diff --git a/lib/src/HttpRequestParser.cc b/lib/src/HttpRequestParser.cc index a938c55e..4ec45b97 100755 --- a/lib/src/HttpRequestParser.cc +++ b/lib/src/HttpRequestParser.cc @@ -15,6 +15,7 @@ #include #include #include +#include "HttpAppFrameworkImpl.h" #include "HttpRequestParser.h" #include "HttpResponseImpl.h" #include "HttpUtils.h" @@ -177,14 +178,23 @@ bool HttpRequestParser::parseRequest(MsgBuffer *buf) return false; } //rfc2616-8.2.3 - //TODO: here we can add content-length limitation auto connPtr = _conn.lock(); if (connPtr) { auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k100Continue); - auto httpString = std::dynamic_pointer_cast(resp)->renderToString(); - connPtr->send(httpString); + if (_request->_contentLen > HttpAppFrameworkImpl::instance().getClientMaxBodySize()) + { + resp->setStatusCode(k413RequestEntityTooLarge); + auto httpString = std::dynamic_pointer_cast(resp)->renderToString(); + reset(); + connPtr->send(httpString); + } + else + { + resp->setStatusCode(k100Continue); + auto httpString = std::dynamic_pointer_cast(resp)->renderToString(); + connPtr->send(httpString); + } } } else if (!expect.empty()) @@ -198,6 +208,12 @@ bool HttpRequestParser::parseRequest(MsgBuffer *buf) return false; } } + else if (_request->_contentLen > HttpAppFrameworkImpl::instance().getClientMaxBodySize()) + { + buf->retrieveAll(); + shutdownConnection(k413RequestEntityTooLarge); + return false; + } } else { @@ -314,7 +330,7 @@ void HttpRequestParser::popFirstRequest() } void HttpRequestParser::pushResponseToPipelining(const HttpRequestPtr &req, - const HttpResponsePtr &resp) + const HttpResponsePtr &resp) { #ifndef NDEBUG auto conn = _conn.lock(); diff --git a/lib/src/WebSocketConnectionImpl.cc b/lib/src/WebSocketConnectionImpl.cc index 22b35eb5..20ca5419 100644 --- a/lib/src/WebSocketConnectionImpl.cc +++ b/lib/src/WebSocketConnectionImpl.cc @@ -12,6 +12,7 @@ * */ +#include "HttpAppFrameworkImpl.h" #include "WebSocketConnectionImpl.h" #include #include @@ -175,7 +176,6 @@ void WebSocketConnectionImpl::setPingMessage(const std::string &message, const s }); } - bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer) { //According to the rfc6455 @@ -271,6 +271,13 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer) } if (isMasked != 0) { + //The message is sent by the client, check the length + if (length > HttpAppFrameworkImpl::instance().getClientMaxWebSocketMessageSize()) + { + LOG_ERROR << "The size of the WebSocket message is too large!"; + buffer->retrieveAll(); + return false; + } if (buffer->readableBytes() >= (indexFirstMask + 4 + length)) { auto masks = buffer->peek() + indexFirstMask;