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);