Mercurial > hg > orthanc
changeset 6659:81d6130cf13a machine-spirits
added configuration option "MaximumRequestBodySizeMB"
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Fri, 20 Mar 2026 17:31:36 +0100 |
| parents | d0a3f15d2793 |
| children | 5d0d0d41e00a |
| files | NEWS OrthancFramework/Sources/HttpServer/HttpServer.cpp OrthancFramework/Sources/HttpServer/HttpServer.h OrthancServer/Resources/Configuration.json OrthancServer/Sources/main.cpp |
| diffstat | 5 files changed, 95 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Fri Mar 20 17:09:51 2026 +0100 +++ b/NEWS Fri Mar 20 17:31:36 2026 +0100 @@ -4,6 +4,7 @@ General ------- +* New configuration option "MaximumRequestBodySizeMB" to limit the maximum body size in HTTP requests * New experimental configuration "PatientLevelEnabled" (TODO: work in progree) REST API
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp Fri Mar 20 17:09:51 2026 +0100 +++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp Fri Mar 20 17:31:36 2026 +0100 @@ -496,8 +496,11 @@ static PostDataStatus ReadBodyUsingFile(std::string& body, struct mg_connection *connection, - size_t maxSize /* "0" means no limit */) + bool hasMaxBodySize, + size_t maxBodySize) { + assert(!hasMaxBodySize || maxBodySize > 0); + // Store the individual chunks in a temporary file, then read it // back into the memory buffer "body" FileBuffer buffer; @@ -521,8 +524,8 @@ readSoFar += r; if (readSoFar > std::numeric_limits<size_t>::max() || - (maxSize != 0 && - readSoFar > maxSize)) + (hasMaxBodySize && + readSoFar > maxBodySize)) { return PostDataStatus_RequestEntityTooLarge; } @@ -539,10 +542,14 @@ static PostDataStatus ReadBodyWithContentLength(std::string& body, struct mg_connection *connection, - const std::string& contentLength) + const std::string& contentLength, + bool hasMaxBodySize, + size_t maxBodySize) { static const size_t MAXIMUM_BODY_SIZE_IN_MEMORY = 10 * 1024 * 1024; // 10MB + assert(!hasMaxBodySize || maxBodySize > 0); + size_t length; try { @@ -563,6 +570,12 @@ return PostDataStatus_NoLength; } + if (hasMaxBodySize && + length > maxBodySize) + { + return PostDataStatus_RequestEntityTooLarge; + } + if (length < MAXIMUM_BODY_SIZE_IN_MEMORY) { /** @@ -596,7 +609,8 @@ * "Content-Length" without sending any actual data. **/ - PostDataStatus status = ReadBodyUsingFile(body, connection, length); + PostDataStatus status = ReadBodyUsingFile(body, connection, + true /* do not read after "Content-Length" */, length); if (status == PostDataStatus_Success) { @@ -613,34 +627,40 @@ static PostDataStatus ReadBodyWithoutContentLength(std::string& body, - struct mg_connection *connection) + struct mg_connection *connection, + bool hasMaxBodySize, + size_t maxBodySize) { - return ReadBodyUsingFile(body, connection, 0 /* TODO - no bound */); + return ReadBodyUsingFile(body, connection, hasMaxBodySize, maxBodySize); } static PostDataStatus ReadBodyToString(std::string& body, struct mg_connection *connection, - const HttpToolbox::Arguments& headers) + const HttpToolbox::Arguments& headers, + bool hasMaxBodySize, + size_t maxBodySize) { HttpToolbox::Arguments::const_iterator contentLength = headers.find("content-length"); if (contentLength != headers.end()) { // "Content-Length" is available - return ReadBodyWithContentLength(body, connection, contentLength->second); + return ReadBodyWithContentLength(body, connection, contentLength->second, hasMaxBodySize, maxBodySize); } else { // No Content-Length - return ReadBodyWithoutContentLength(body, connection); + return ReadBodyWithoutContentLength(body, connection, hasMaxBodySize, maxBodySize); } } static PostDataStatus ReadBodyToStream(IHttpHandler::IChunkedRequestReader& stream, struct mg_connection *connection, - const HttpToolbox::Arguments& headers) + const HttpToolbox::Arguments& headers, + bool hasMaxBodySize, + size_t maxBodySize) { HttpToolbox::Arguments::const_iterator contentLength = headers.find("content-length"); @@ -648,7 +668,7 @@ { // "Content-Length" is available std::string body; - PostDataStatus status = ReadBodyWithContentLength(body, connection, contentLength->second); + PostDataStatus status = ReadBodyWithContentLength(body, connection, contentLength->second, hasMaxBodySize, maxBodySize); if (status == PostDataStatus_Success && !body.empty()) @@ -884,7 +904,9 @@ const std::string& method, const HttpToolbox::Arguments& headers, const std::string& uri, - struct mg_connection *connection /* to read the PUT body if need be */) + struct mg_connection *connection /* to read the PUT body if need be */, + bool hasMaxBodySize, + size_t maxBodySize) { if (buckets.empty()) { @@ -1094,7 +1116,7 @@ { #if CIVETWEB_HAS_WEBDAV_WRITING == 1 std::string body; - if (ReadBodyToString(body, connection, headers) == PostDataStatus_Success) + if (ReadBodyToString(body, connection, headers, hasMaxBodySize, maxBodySize) == PostDataStatus_Success) { if (bucket->second->StoreFile(body, path)) { @@ -1461,7 +1483,7 @@ #if ORTHANC_ENABLE_PUGIXML == 1 if (HandleWebDav(output, server.GetWebDavBuckets(), request->request_method, - headers, requestUri, connection)) + headers, requestUri, connection, server.HasMaxBodySize(), server.GetMaxBodySize())) { return; } @@ -1501,7 +1523,7 @@ **/ isMultipartForm = true; - postStatus = ReadBodyToString(body, connection, headers); + postStatus = ReadBodyToString(body, connection, headers, server.HasMaxBodySize(), server.GetMaxBodySize()); if (postStatus == PostDataStatus_Success) { server.ProcessMultipartFormData(remoteIp, username, uri, headers, body, boundary, authenticationPayload); @@ -1527,7 +1549,7 @@ throw OrthancException(ErrorCode_InternalError); } - postStatus = ReadBodyToStream(*stream, connection, headers); + postStatus = ReadBodyToStream(*stream, connection, headers, server.HasMaxBodySize(), server.GetMaxBodySize()); if (postStatus == PostDataStatus_Success) { @@ -1536,7 +1558,7 @@ } else { - postStatus = ReadBodyToString(body, connection, headers); + postStatus = ReadBodyToString(body, connection, headers, server.HasMaxBodySize(), server.GetMaxBodySize()); } } @@ -1743,7 +1765,9 @@ threadsCount_(50), // Default value in mongoose/civetweb tcpNoDelay_(true), requestTimeout_(30), // Default value in mongoose/civetweb (30 seconds) - threadCounter_(0) + threadCounter_(0), + hasMaxBodySize_(false), + maxBodySize_(0) { #if ORTHANC_ENABLE_MONGOOSE == 1 CLOG(INFO, HTTP) << "This Orthanc server uses Mongoose as its embedded HTTP server"; @@ -2447,4 +2471,21 @@ Logging::SetCurrentThreadName(std::string("HTTP-") + boost::lexical_cast<std::string>(threadCounter_++)); } } + + + void HttpServer::SetMaxBodySize(uint64_t size) + { + Stop(); + + if (size == 0 || + static_cast<uint64_t>(static_cast<size_t>(size)) != size) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + hasMaxBodySize_ = true; + maxBodySize_ = static_cast<size_t>(size); + } + } }
--- a/OrthancFramework/Sources/HttpServer/HttpServer.h Fri Mar 20 17:09:51 2026 +0100 +++ b/OrthancFramework/Sources/HttpServer/HttpServer.h Fri Mar 20 17:31:36 2026 +0100 @@ -123,6 +123,10 @@ boost::mutex threadCounterMutex_; // New in Orthanc 1.12.9 uint16_t threadCounter_; // Introduced as a global, static variable in Orthanc 1.12.2 + // New in Orthanc 1.12.11 + bool hasMaxBodySize_; + size_t maxBodySize_; + #if ORTHANC_ENABLE_PUGIXML == 1 WebDavBuckets webDavBuckets_; #endif @@ -246,5 +250,17 @@ MetricsRegistry::AvailableResourcesDecounter* CreateAvailableHttpThreadsDecounter(); void UpdateCurrentThreadName(); + + void SetMaxBodySize(uint64_t size); + + bool HasMaxBodySize() const + { + return hasMaxBodySize_; + } + + size_t GetMaxBodySize() const + { + return maxBodySize_; + } }; }
--- a/OrthancServer/Resources/Configuration.json Fri Mar 20 17:09:51 2026 +0100 +++ b/OrthancServer/Resources/Configuration.json Fri Mar 20 17:31:36 2026 +0100 @@ -1108,6 +1108,11 @@ // "/tools/create-archives" routes. (new in Orthanc 1.12.11) "ZipUseUtf8" : false, + // Maximum body size allowed in a HTTP request (POST or PUT) to prevent + // exhaustion of resources, expressed in MB. A value of "0" indicates + // no limit on the body size. (new in Orthanc 1.12.11) + "MaximumRequestBodySizeMB" : 2048, + // When set to false, this option disables all /patients routes and // disables patients related sanity checks when performing resource // modification. This is required e.g when your Orthanc stores
--- a/OrthancServer/Sources/main.cpp Fri Mar 20 17:09:51 2026 +0100 +++ b/OrthancServer/Sources/main.cpp Fri Mar 20 17:31:36 2026 +0100 @@ -1126,6 +1126,19 @@ httpServer.SetTcpNoDelay(lock.GetConfiguration().GetBooleanParameter("TcpNoDelay", true)); httpServer.SetRequestTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpRequestTimeout", 30)); + // New in Orthanc 1.12.11 + const unsigned int maxBodySize = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumRequestBodySizeMB", 2048); + if (maxBodySize != 0) + { + LOG(WARNING) << "Limiting the maximum body size in HTTP requests to " << maxBodySize << "MB"; + httpServer.SetMaxBodySize(static_cast<uint64_t>(maxBodySize) * + static_cast<uint64_t>(1024 * 1024)); + } + else + { + LOG(WARNING) << "No limit on the maximum body size in HTTP requests"; + } + // Let's assume that the HTTP server is secure context.SetHttpServerSecure(true);
