Description: Fix CVE-2026-5437, CVE-2026-5438, CVE-2026-5439, CVE-2026-5440, CVE-2026-5441, CVE-2026-5442, CVE-2026-5443, CVE-2026-5444, and CVE-2026-5445
Author: Sebastien Jodogne <s.jodogne@orthanc-labs.com>
Forwarded: https://orthanc.uclouvain.be/hg/orthanc/rev/5ce108190752
---
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
Index: Orthanc-1.12.10/OrthancFramework/Sources/Compatibility.h
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Compatibility.h
+++ Orthanc-1.12.10/OrthancFramework/Sources/Compatibility.h
@@ -72,10 +72,12 @@
 // The override keyword (C++11) is enabled
 #  define ORTHANC_OVERRIDE  override 
 #  define ORTHANC_FINAL     final
+#  define ORTHANC_NOEXCEPT  noexcept
 #else
 // The override keyword (C++11) is not available
 #  define ORTHANC_OVERRIDE
 #  define ORTHANC_FINAL
+#  define ORTHANC_NOEXCEPT
 #endif
 
 
Index: Orthanc-1.12.10/OrthancFramework/Sources/Compression/GzipCompressor.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Compression/GzipCompressor.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/Compression/GzipCompressor.cpp
@@ -29,8 +29,16 @@
 #include <string.h>
 #include <zlib.h>
 
-#include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
 #include "../Logging.h"
+#include "../MultiThreading/ReaderWriterLock.h"
+#include "../OrthancException.h"
+
+
+static Orthanc::ReaderWriterLock maximumUncompressedFileSizeMutex_;
+static bool hasMaximumUncompressedFileSize_ = false;
+static size_t maximumUncompressedFileSize_ = 0;
+
 
 namespace Orthanc
 {
@@ -177,100 +185,149 @@ namespace Orthanc
   }
 
 
+  namespace
+  {
+    class GzipRaii : public boost::noncopyable
+    {
+    private:
+      z_stream&  stream_;
+
+    public:
+      GzipRaii(z_stream& stream) :
+        stream_(stream)
+      {
+        int error = inflateInit2(&stream_,
+                                 MAX_WBITS + 16);  // this is a gzip input
+
+        if (error != Z_OK)
+        {
+          throw OrthancException(ErrorCode_InternalError, "Cannot initialize zlib");
+        }
+      }
+
+      ~GzipRaii()
+      {
+        inflateEnd(&stream_);
+      }
+    };
+  }
+
+
   void GzipCompressor::Uncompress(std::string& uncompressed,
                                   const void* compressed,
                                   size_t compressedSize)
   {
-    uint64_t uncompressedSize;
     const uint8_t* source = reinterpret_cast<const uint8_t*>(compressed);
 
+    bool hasMaximumSize = false;
+    size_t maximumSize = 0;
+
     if (HasPrefixWithUncompressedSize())
     {
-      uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize);
+      hasMaximumSize = true;
+      maximumSize = ReadUncompressedSizePrefix(compressed, compressedSize);
       source += sizeof(uint64_t);
       compressedSize -= sizeof(uint64_t);
     }
     else
     {
-      uncompressedSize = GuessUncompressedSize(compressed, compressedSize);
-    }
+      ReaderWriterLock::ReadLock lock(maximumUncompressedFileSizeMutex_);
 
-    try
-    {
-      uncompressed.resize(static_cast<size_t>(uncompressedSize));
-    }
-    catch (...)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
+      if (hasMaximumUncompressedFileSize_)
+      {
+        hasMaximumSize = true;
+        maximumSize = maximumUncompressedFileSize_;
+      }
     }
 
     z_stream stream;
     memset(&stream, 0, sizeof(stream));
 
-    char dummy = '\0';  // zlib does not like NULL output buffers (even if the uncompressed data is empty)
     stream.next_in = const_cast<Bytef*>(source);
-    stream.next_out = reinterpret_cast<Bytef*>(uncompressedSize == 0 ? &dummy : &uncompressed[0]);
-
     stream.avail_in = static_cast<uInt>(compressedSize);
-    stream.avail_out = static_cast<uInt>(uncompressedSize);
 
-    // Ensure no overflow (if the buffer is too large for the current archicture)
-    if (static_cast<size_t>(stream.avail_in) != compressedSize ||
-        static_cast<size_t>(stream.avail_out) != uncompressedSize)
+    // Ensure no overflow (if the buffer is too large for the current zlib archicture)
+    if (static_cast<size_t>(stream.avail_in) != compressedSize)
     {
       throw OrthancException(ErrorCode_NotEnoughMemory);
     }
 
-    // Initialize the compression engine
-    int error = inflateInit2(&stream, 
-                             MAX_WBITS + 16);  // this is a gzip input
+    ChunkedBuffer buffer;
 
-    if (error != Z_OK)
     {
-      // Cannot initialize zlib
-      uncompressed.clear();
-      throw OrthancException(ErrorCode_InternalError);
-    }
+      GzipRaii raii(stream);
 
-    // Uncompress the input buffer
-    error = inflate(&stream, Z_FINISH);
+      std::string chunk;
+      chunk.resize(10 * 1024 * 1024); // Read by chunks of 10MB
 
-    if (error != Z_STREAM_END)
-    {
-      inflateEnd(&stream);
-      uncompressed.clear();
+      int ret = Z_OK;
 
-      switch (error)
+      while (ret != Z_STREAM_END)
       {
+        stream.next_out  = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(chunk.data()));
+        stream.avail_out = static_cast<uInt>(chunk.size());
+
+        ret = inflate(&stream, Z_NO_FLUSH);
+
+        switch (ret)
+        {
+        case Z_STREAM_END:
+        case Z_OK:
+          break; // Normal
+
+        case Z_NEED_DICT:
+        case Z_DATA_ERROR:
+        case Z_STREAM_ERROR:
+          throw OrthancException(ErrorCode_BadFileFormat);
+
         case Z_MEM_ERROR:
           throw OrthancException(ErrorCode_NotEnoughMemory);
-          
+
         case Z_BUF_ERROR:
-        case Z_NEED_DICT:
-          throw OrthancException(ErrorCode_BadFileFormat);
-          
+          // Not fatal: means no progress was possible this round.
+          // If avail_in is also 0 here, the input is truncated.
+          if (stream.avail_in == 0)
+          {
+            throw OrthancException(ErrorCode_BadFileFormat, "Truncated gzip input");
+          }
+          break;
+
         default:
-          throw OrthancException(ErrorCode_InternalError);
+          throw OrthancException(ErrorCode_InternalError); // Unknown error
+        }
+
+        const size_t produced = chunk.size() - stream.avail_out;
+
+        if (hasMaximumSize &&
+            buffer.GetNumBytes() + produced > maximumSize)
+        {
+          char s[32];
+          sprintf(s, "%0.1f", static_cast<float>(maximumSize) / (1024.0f * 1024.0f));
+          throw OrthancException(ErrorCode_BadFileFormat, "Uncompressed size exceeds limit (" + std::string(s) + "MB)");
+        }
+        else
+        {
+          // OK, add the chunk
+          buffer.AddChunk(&chunk[0], produced);
+        }
       }
     }
 
-    size_t size = stream.total_out;
+    buffer.Flatten(uncompressed);
+  }
 
-    if (inflateEnd(&stream) != Z_OK)
+
+  void GzipCompressor::SetMaximumUncompressedFileSize(uint64_t size)
+  {
+    if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
     {
-      uncompressed.clear();
-      throw OrthancException(ErrorCode_InternalError);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
     }
-
-    if (size != uncompressedSize)
+    else
     {
-      uncompressed.clear();
-
-      // The uncompressed size was not that properly guess, presumably
-      // because of a file size over 4GB. Should fallback to
-      // stream-based decompression.
-      throw OrthancException(ErrorCode_NotImplemented,
-                             "The uncompressed size of a gzip-encoded buffer was not properly guessed");
+      ReaderWriterLock::WriteLock lock(maximumUncompressedFileSizeMutex_);
+      hasMaximumUncompressedFileSize_ = true;
+      maximumUncompressedFileSize_ = size;
     }
   }
 }
Index: Orthanc-1.12.10/OrthancFramework/Sources/Compression/GzipCompressor.h
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Compression/GzipCompressor.h
+++ Orthanc-1.12.10/OrthancFramework/Sources/Compression/GzipCompressor.h
@@ -45,5 +45,7 @@ namespace Orthanc
     virtual void Uncompress(std::string& uncompressed,
                             const void* compressed,
                             size_t compressedSize) ORTHANC_OVERRIDE;
+
+    static void SetMaximumUncompressedFileSize(uint64_t size);
   };
 }
Index: Orthanc-1.12.10/OrthancFramework/Sources/Compression/ZipReader.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Compression/ZipReader.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/Compression/ZipReader.cpp
@@ -30,8 +30,10 @@
 
 #include "ZipReader.h"
 
+#include "../ChunkedBuffer.h"
 #include "../OrthancException.h"
-#include "../../Resources/ThirdParty/minizip/unzip.h"
+#include "../MultiThreading/ReaderWriterLock.h"
+#include <minizip/unzip.h>
 
 #if ORTHANC_SANDBOXED != 1
 #  include "../SystemToolbox.h"
@@ -56,6 +58,11 @@ typedef ssize_t SSIZE_T;
 #include <string.h>
 
 
+static Orthanc::ReaderWriterLock maximumUncompressedFileSizeMutex_;
+static bool hasMaximumUncompressedFileSize_ = false;
+static size_t maximumUncompressedFileSize_ = 0;
+
+
 namespace Orthanc
 {
   // ZPOS64_T corresponds to "uint64_t"
@@ -313,6 +320,62 @@ namespace Orthanc
   }
 
 
+  static bool ReadInternal(std::string& content,
+                           unzFile& unzip,
+                           uint64_t size) ORTHANC_NOEXCEPT
+  {
+#if 0
+    /**
+     * This was the code using in Orthanc <= 1.12.10
+     **/
+    content.resize(static_cast<size_t>(size));
+    return (unzReadCurrentFile(unzip, &content[0], static_cast<uLong>(content.size())) != 0);
+
+#else
+    /**
+     * Read chunk by chunk to disarm ZIP bombs
+     **/
+    ChunkedBuffer buffer;
+
+    std::string chunk;
+    chunk.resize(10 * 1024 * 1024); // Read by chunks of 10MB
+
+    for (;;)
+    {
+      int r = unzReadCurrentFile(unzip, &chunk[0], chunk.size());
+
+      if (r == 0)
+      {
+        // We're done
+        if (buffer.GetNumBytes() == size)
+        {
+          buffer.Flatten(content);
+          return true;
+        }
+        else
+        {
+          return false;  // Presumably a malicious ZIP file
+        }
+      }
+      else if (r < 0)
+      {
+        // Error (might come from a malicious ZIP file)
+        return false;
+      }
+      else if (static_cast<uint64_t>(buffer.GetNumBytes()) + r > size)
+      {
+        // Presumably a ZIP bomb
+        return false;
+      }
+      else
+      {
+        buffer.AddChunk(&chunk[0], r);
+      }
+    }
+#endif
+  }
+
+
   bool ZipReader::ReadNextFile(std::string& filename,
                                std::string& content)
   {
@@ -330,6 +393,12 @@ namespace Orthanc
         throw OrthancException(ErrorCode_BadFileFormat);
       }
 
+      if (static_cast<uint64_t>(static_cast<size_t>(info.uncompressed_size)) != info.uncompressed_size)
+      {
+        // Too large file for a 32bit architecture
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
       filename.resize(info.size_filename);
       if (!filename.empty() &&
           unzGetCurrentFileInfo64(pimpl_->unzip_, &info, &filename[0],
@@ -338,14 +407,28 @@ namespace Orthanc
         throw OrthancException(ErrorCode_BadFileFormat);
       }
 
-      content.resize(info.uncompressed_size);
+      {
+        // Prevent ZIP bombs
+        ReaderWriterLock::ReadLock lock(maximumUncompressedFileSizeMutex_);
+
+        if (hasMaximumUncompressedFileSize_ &&
+            info.uncompressed_size > maximumUncompressedFileSize_)
+        {
+          char s[32];
+          sprintf(s, "%0.1f", static_cast<float>(maximumUncompressedFileSize_) / (1024.0f * 1024.0f));
+          throw OrthancException(ErrorCode_BadFileFormat, "Uncompressed size exceeds limit (" + std::string(s) + "MB)");
+        }
+      }
 
-      if (!content.empty())
+      if (info.uncompressed_size == 0)
+      {
+        content.clear();
+      }
+      else
       {
         if (unzOpenCurrentFile(pimpl_->unzip_) == 0)
         {
-          bool success = (unzReadCurrentFile(pimpl_->unzip_, &content[0],
-                                             static_cast<uLong>(content.size())) != 0);
+          bool success = ReadInternal(content, pimpl_->unzip_, info.uncompressed_size);
                           
           if (unzCloseCurrentFile(pimpl_->unzip_) != 0 ||
               !success)
@@ -432,4 +515,19 @@ namespace Orthanc
     }
   }
 #endif
+
+
+  void ZipReader::SetMaximumUncompressedFileSize(uint64_t size)
+  {
+    if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+    else
+    {
+      ReaderWriterLock::WriteLock lock(maximumUncompressedFileSizeMutex_);
+      hasMaximumUncompressedFileSize_ = true;
+      maximumUncompressedFileSize_ = size;
+    }
+  }
 }
Index: Orthanc-1.12.10/OrthancFramework/Sources/Compression/ZipReader.h
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Compression/ZipReader.h
+++ Orthanc-1.12.10/OrthancFramework/Sources/Compression/ZipReader.h
@@ -87,5 +87,7 @@ namespace Orthanc
 #if ORTHANC_SANDBOXED != 1
     static bool IsZipFile(const boost::filesystem::path& path);
 #endif
+
+    static void SetMaximumUncompressedFileSize(uint64_t size);
   };
 }
Index: Orthanc-1.12.10/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp
@@ -42,6 +42,8 @@
 #include <stdio.h>
 #include <memory>
 
+static const uint64_t MAX_FRAME_SIZE = 4llu * 1024llu * 1024llu * 1024llu;  // defensive approach: set a reasonable max size for a frame
+
 namespace Orthanc
 {
   DicomImageInformation::DicomImageInformation(const DicomMap& values)
@@ -130,6 +132,11 @@ namespace Orthanc
       values.GetValue(DICOM_TAG_COLUMNS).ParseFirstUnsignedInteger(width_); // in some US images, we've seen tag values of "800\0"; that's why we parse the 'first' value
       values.GetValue(DICOM_TAG_ROWS).ParseFirstUnsignedInteger(height_);
 
+      if (height_ > 65535 || width_ > 65535)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat, "Image width or height exceed DICOM VR US range (65535)");
+      }
+
       if (!values.ParseUnsignedInteger32(bitsAllocated_, DICOM_TAG_BITS_ALLOCATED))
       {
         throw OrthancException(ErrorCode_BadFileFormat);
@@ -454,13 +461,15 @@ namespace Orthanc
 
   size_t DicomImageInformation::GetFrameSize() const
   {
+    uint64_t totalFrameSize;
+
     if (bitsStored_ == 1)
     {
       assert(GetWidth() % 8 == 0);
       
       if (GetChannelCount() == 1)
       {
-        return GetHeight() * GetWidth() / 8;
+        totalFrameSize = static_cast<uint64_t>(GetHeight()) * static_cast<uint64_t>(GetWidth()) / 8;
       }
       else
       {
@@ -470,10 +479,20 @@ namespace Orthanc
     }
     else
     {
-      return (GetHeight() *
-              GetWidth() *
-              GetBytesPerValue() *
-              GetChannelCount());
+      totalFrameSize = (static_cast<uint64_t>(GetHeight()) *
+                        static_cast<uint64_t>(GetWidth()) *
+                        static_cast<uint64_t>(GetBytesPerValue()) *
+                        static_cast<uint64_t>(GetChannelCount()));
+    }
+
+    if (totalFrameSize > MAX_FRAME_SIZE ||
+        static_cast<uint64_t>(static_cast<size_t>(totalFrameSize)) != totalFrameSize)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "DICOM Frame size overflow");
+    }
+    else
+    {
+      return static_cast<size_t>(totalFrameSize);
     }
   }
 
Index: Orthanc-1.12.10/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp
@@ -49,7 +49,7 @@ namespace Orthanc
         information_.GetBitsStored() >= 32)
     {
       // Not available, as the accessor internally uses int32_t values
-      throw OrthancException(ErrorCode_NotImplemented);
+      throw OrthancException(ErrorCode_NotImplemented, "No support for BitsAllocated > 32 or BitsStored >= 32");
     }
 
     frame_ = 0;
@@ -57,7 +57,7 @@ namespace Orthanc
 
     if (information_.GetNumberOfFrames() * frameOffset_ > size)
     {
-      throw OrthancException(ErrorCode_BadFileFormat);
+      throw OrthancException(ErrorCode_BadFileFormat, "Invalid size");
     }
 
     if (information_.IsSigned())
Index: Orthanc-1.12.10/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp
@@ -204,6 +204,11 @@ namespace Orthanc
       {
         uint16_t length = ReadUnsignedInteger16(p + pos + 6, true);
 
+        if (pos + 8 + length > block.size())
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, "DICOM meta-header tag length exceeds available data");
+        }
+
         std::string value;
         value.assign(p + pos + 8, length);
         NormalizeValue(value, vr);
@@ -237,6 +242,11 @@ namespace Orthanc
           
         uint32_t length = ReadUnsignedInteger32(p + pos + 8, true);
 
+        if (pos + 12 + static_cast<size_t>(length) > block.size())
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, "DICOM meta-header tag length exceeds available data");
+        }
+
         if (tag.GetGroup() == 0x0002)
         {
           std::string value;
Index: Orthanc-1.12.10/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp
@@ -192,6 +192,11 @@ namespace Orthanc
     {
       if (inbuffer[i] == 0xa5)
       {
+        if (i + 2 >= length)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, "Truncated PMSCT_RLE1 escape sequence");
+        }
+
         temp.push_back(inbuffer[i+2]);
         for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--)
         {
@@ -215,6 +220,11 @@ namespace Orthanc
 
       if (temp[i] == 0x5a)
       {
+        if (i + 2 >= temp.size())
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, "Truncated PMSCT_RLE1 delta sequence");
+        }
+
         uint16_t v1 = temp[i + 1];
         uint16_t v2 = temp[i + 2];
         value = (v2 << 8) + v1;
@@ -460,9 +470,12 @@ namespace Orthanc
           throw OrthancException(ErrorCode_NotImplemented, std::string("Palette Color Lookup Table Descriptor invalid palette size: '") + r.c_str() + "'");
         }
 
-        if (pixelLength != target->GetWidth() * target->GetHeight())
+        const uint64_t expectedSize = (static_cast<uint64_t>(target->GetWidth()) *
+                                       static_cast<uint64_t>(target->GetHeight()));
+
+        if (pixelLength != expectedSize)
         {
-          DcmElement *elem;
+          DcmElement *elem = NULL;
           Uint16 bitsAllocated = 0;
 
           if (!dataset.findAndGetUint16(DCM_BitsAllocated, bitsAllocated).good())
@@ -470,7 +483,8 @@ namespace Orthanc
             throw OrthancException(ErrorCode_NotImplemented);  
           }
 
-          if (!dataset.findAndGetElement(DCM_PixelData, elem).good())
+          if (!dataset.findAndGetElement(DCM_PixelData, elem).good() ||
+              elem == NULL)
           {
             throw OrthancException(ErrorCode_NotImplemented);  
           }
@@ -478,9 +492,11 @@ namespace Orthanc
           // In implicit VR files, pixelLength is expressed in words (OW) although pixels can actually be 8 bits
           // -> pixelLength is wrong by a factor of two and the image can still be decoded!
           // seen in some Philips ClearVue 650 images (using 8 bits LUT)
-          if (!(elem->getVR() == EVR_OW && bitsAllocated == 8 && (2*pixelLength == target->GetWidth() * target->GetHeight())))  
+          if (elem->getVR() != EVR_OW ||
+              bitsAllocated != 8 ||
+              2 * pixelLength != expectedSize)
           {
-            throw OrthancException(ErrorCode_NotImplemented);
+            throw OrthancException(ErrorCode_BadFileFormat, "Invalid size");
           }
         }
 
@@ -494,6 +510,11 @@ namespace Orthanc
 
           for (unsigned int x = 0; x < width; x++)
           {
+            if (*source >= paletteSize)
+            {
+              throw OrthancException(ErrorCode_BadFileFormat, "Pixel value exceeds palette");
+            }
+
             p[0] = lutRed[*source] >> offsetBits;
             p[1] = lutGreen[*source] >> offsetBits;
             p[2] = lutBlue[*source] >> offsetBits;
Index: Orthanc-1.12.10/OrthancFramework/Sources/HttpServer/HttpOutput.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/HttpServer/HttpOutput.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/HttpServer/HttpOutput.cpp
@@ -973,7 +973,7 @@ namespace Orthanc
     std::string filename;
     if (stream.HasContentFilename(filename))
     {
-      stateMachine_.AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\"");
+      stateMachine_.SetContentFilename(filename.c_str());
     }
 
     stateMachine_.StartStream(contentType);
Index: Orthanc-1.12.10/OrthancFramework/Sources/HttpServer/HttpServer.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/HttpServer/HttpServer.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/HttpServer/HttpServer.cpp
@@ -185,7 +185,8 @@ namespace Orthanc
       PostDataStatus_Success,
       PostDataStatus_NoLength,
       PostDataStatus_Pending,
-      PostDataStatus_Failure
+      PostDataStatus_Failure,
+      PostDataStatus_RequestEntityTooLarge  // New in Orthanc 1.12.11
     };
   }
 
@@ -491,12 +492,64 @@ namespace Orthanc
     reader.AddChunk(body);        
     reader.CloseStream();
   }
-  
+
+
+  static PostDataStatus ReadBodyUsingFile(std::string& body,
+                                          struct mg_connection *connection,
+                                          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;
+
+    uint64_t readSoFar = 0;
+    std::string tmp(1024 * 1024, 0);
+
+    for (;;)
+    {
+      int r = mg_read(connection, &tmp[0], tmp.size());
+      if (r < 0)
+      {
+        return PostDataStatus_Failure;
+      }
+      else if (r == 0)
+      {
+        break;
+      }
+      else
+      {
+        readSoFar += r;
+
+        if (readSoFar > std::numeric_limits<size_t>::max() ||
+            (hasMaxBodySize &&
+             readSoFar > maxBodySize))
+        {
+          return PostDataStatus_RequestEntityTooLarge;
+        }
+
+        buffer.Append(tmp.c_str(), r);
+      }
+    }
+
+    buffer.Read(body);
+
+    return PostDataStatus_Success;
+  }
+
 
   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 = 100 * 1024 * 1024;  // 100MB
+
+    assert(!hasMaxBodySize || maxBodySize > 0);
+
     size_t length;
     try
     {
@@ -507,86 +560,107 @@ namespace Orthanc
       }
 
       length = static_cast<size_t>(tmp);
+      if (static_cast<int64_t>(length) != tmp)
+      {
+        return PostDataStatus_Failure;
+      }
     }
     catch (boost::bad_lexical_cast&)
     {
       return PostDataStatus_NoLength;
     }
 
-    body.resize(length);
+    if (hasMaxBodySize &&
+        length > maxBodySize)
+    {
+      return PostDataStatus_RequestEntityTooLarge;
+    }
 
-    size_t pos = 0;
-    while (length > 0)
+    if (length < MAXIMUM_BODY_SIZE_IN_MEMORY)
     {
-      int r = mg_read(connection, &body[pos], length);
-      if (r <= 0)
+      /**
+       * Small POST bodies should land into RAM to avoid creating
+       * temporary files, which would result in bad performance.
+       **/
+      body.resize(length);
+
+      size_t pos = 0;
+      while (length > 0)
       {
-        return PostDataStatus_Failure;
+        int r = mg_read(connection, &body[pos], length);
+        if (r <= 0)
+        {
+          return PostDataStatus_Failure;
+        }
+
+        assert(static_cast<size_t>(r) <= length);
+        length -= r;
+        pos += r;
       }
 
-      assert(static_cast<size_t>(r) <= length);
-      length -= r;
-      pos += r;
+      return PostDataStatus_Success;
     }
+    else
+    {
+      /**
+       * Deal with CWE-770 (Machine Spirits UG). If the client wants
+       * to send a large body, use a temporary file to prevent memory
+       * exhaustion by a malicious client that would set a large
+       * "Content-Length" without sending any actual data.
+       **/
 
-    return PostDataStatus_Success;
-  }
-                                                  
-
-  static PostDataStatus ReadBodyWithoutContentLength(std::string& body,
-                                                     struct mg_connection *connection)
-  {
-    // Store the individual chunks in a temporary file, then read it
-    // back into the memory buffer "body"
-    FileBuffer buffer;
+      PostDataStatus status = ReadBodyUsingFile(body, connection,
+                                                true /* do not read after "Content-Length" */, length);
 
-    std::string tmp(1024 * 1024, 0);
-      
-    for (;;)
-    {
-      int r = mg_read(connection, &tmp[0], tmp.size());
-      if (r < 0)
-      {
-        return PostDataStatus_Failure;
-      }
-      else if (r == 0)
+      if (status == PostDataStatus_Success)
       {
-        break;
+        return (body.size() == length ?
+                PostDataStatus_Success :
+                PostDataStatus_Failure);
       }
       else
       {
-        buffer.Append(tmp.c_str(), r);
+        return status;
       }
     }
+  }
 
-    buffer.Read(body);
 
-    return PostDataStatus_Success;
+  static PostDataStatus ReadBodyWithoutContentLength(std::string& body,
+                                                     struct mg_connection *connection,
+                                                     bool hasMaxBodySize,
+                                                     size_t maxBodySize)
+  {
+    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");
 
@@ -594,7 +668,7 @@ namespace Orthanc
     {
       // "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())
@@ -830,7 +904,9 @@ namespace Orthanc
                            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())
     {
@@ -1040,7 +1116,7 @@ namespace Orthanc
           {
 #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))
               {
@@ -1407,7 +1483,7 @@ namespace Orthanc
 
 #if ORTHANC_ENABLE_PUGIXML == 1
     if (HandleWebDav(output, server.GetWebDavBuckets(), request->request_method,
-                     headers, requestUri, connection))
+                     headers, requestUri, connection, server.HasMaxBodySize(), server.GetMaxBodySize()))
     {
       return;
     }
@@ -1447,7 +1523,7 @@ namespace Orthanc
          **/
         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);
@@ -1473,7 +1549,7 @@ namespace Orthanc
             throw OrthancException(ErrorCode_InternalError);
           }
 
-          postStatus = ReadBodyToStream(*stream, connection, headers);
+          postStatus = ReadBodyToStream(*stream, connection, headers, server.HasMaxBodySize(), server.GetMaxBodySize());
 
           if (postStatus == PostDataStatus_Success)
           {
@@ -1482,7 +1558,7 @@ namespace Orthanc
         }
         else
         {
-          postStatus = ReadBodyToString(body, connection, headers);
+          postStatus = ReadBodyToString(body, connection, headers, server.HasMaxBodySize(), server.GetMaxBodySize());
         }
       }
 
@@ -1492,6 +1568,10 @@ namespace Orthanc
           output.SendStatus(HttpStatus_411_LengthRequired);
           return;
 
+        case PostDataStatus_RequestEntityTooLarge:  // New in Orthanc 1.12.11
+          output.SendStatus(HttpStatus_413_RequestEntityTooLarge);
+          return;
+
         case PostDataStatus_Failure:
           output.SendStatus(HttpStatus_400_BadRequest);
           return;
@@ -1685,7 +1765,9 @@ namespace Orthanc
     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";
@@ -2389,4 +2471,21 @@ namespace Orthanc
       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);
+    }
+  }
 }
Index: Orthanc-1.12.10/OrthancFramework/Sources/HttpServer/HttpServer.h
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/HttpServer/HttpServer.h
+++ Orthanc-1.12.10/OrthancFramework/Sources/HttpServer/HttpServer.h
@@ -123,6 +123,10 @@ namespace Orthanc
     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 @@ namespace Orthanc
     MetricsRegistry::AvailableResourcesDecounter* CreateAvailableHttpThreadsDecounter();
 
     void UpdateCurrentThreadName();
+
+    void SetMaxBodySize(uint64_t size);
+
+    bool HasMaxBodySize() const
+    {
+      return hasMaxBodySize_;
+    }
+
+    size_t GetMaxBodySize() const
+    {
+      return maxBodySize_;
+    }
   };
 }
Index: Orthanc-1.12.10/OrthancFramework/Sources/Images/ImageAccessor.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Images/ImageAccessor.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/Images/ImageAccessor.cpp
@@ -165,7 +165,14 @@ namespace Orthanc
   {
     if (buffer_ != NULL)
     {
-      return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_);
+      if (y < height_)
+      {
+        return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
     }
     else
     {
@@ -184,7 +191,14 @@ namespace Orthanc
 
     if (buffer_ != NULL)
     {
-      return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_);
+      if (y < height_)
+      {
+        return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
     }
     else
     {
@@ -210,17 +224,20 @@ namespace Orthanc
                                      unsigned int pitch,
                                      const void *buffer)
   {
+    const uint64_t size = static_cast<uint64_t>(height) * static_cast<uint64_t>(pitch);
+
+    if (static_cast<uint64_t>(GetBytesPerPixel() * width) > static_cast<uint64_t>(pitch) ||
+        static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
     readOnly_ = true;
     format_ = format;
     width_ = width;
     height_ = height;
     pitch_ = pitch;
     buffer_ = reinterpret_cast<uint8_t*>(const_cast<void*>(buffer));
-
-    if (GetBytesPerPixel() * width_ > pitch_)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
   }
 
   void ImageAccessor::GetReadOnlyAccessor(ImageAccessor &target) const
@@ -235,17 +252,20 @@ namespace Orthanc
                                      unsigned int pitch,
                                      void *buffer)
   {
+    const uint64_t size = static_cast<uint64_t>(height) * static_cast<uint64_t>(pitch);
+
+    if (static_cast<uint64_t>(GetBytesPerPixel() * width) > static_cast<uint64_t>(pitch) ||
+        static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
     readOnly_ = false;
     format_ = format;
     width_ = width;
     height_ = height;
     pitch_ = pitch;
     buffer_ = reinterpret_cast<uint8_t*>(buffer);
-
-    if (GetBytesPerPixel() * width_ > pitch_)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
   }
 
 
Index: Orthanc-1.12.10/OrthancFramework/Sources/Images/ImageBuffer.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Images/ImageBuffer.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/Images/ImageBuffer.cpp
@@ -46,8 +46,16 @@ namespace Orthanc
         }
       */
 
-      pitch_ = GetBytesPerPixel() * width_;
-      size_t size = static_cast<size_t>(pitch_) * static_cast<size_t>(height_);
+      const uint64_t tmpPitch = static_cast<uint64_t>(GetBytesPerPixel()) * static_cast<uint64_t>(width_);
+      const uint64_t size = tmpPitch * static_cast<uint64_t>(height_);
+
+      if (static_cast<uint64_t>(static_cast<unsigned int>(tmpPitch)) != tmpPitch ||
+          static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
+      pitch_ = static_cast<unsigned int>(tmpPitch);
 
       if (size == 0)
       {
@@ -55,7 +63,7 @@ namespace Orthanc
       }
       else
       {
-        buffer_ = malloc(size);
+        buffer_ = malloc(static_cast<size_t>(size));
         if (buffer_ == NULL)
         {
           throw OrthancException(ErrorCode_NotEnoughMemory,
Index: Orthanc-1.12.10/OrthancFramework/Sources/Images/PamReader.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancFramework/Sources/Images/PamReader.cpp
+++ Orthanc-1.12.10/OrthancFramework/Sources/Images/PamReader.cpp
@@ -38,6 +38,7 @@
 #include <boost/algorithm/string/find.hpp>
 #include <boost/lexical_cast.hpp>
 
+static const uint64_t MAX_PAM_IMAGE_BUFFER_SIZE = 4llu * 1024llu * 1024llu * 1024llu;  // defensive approach: set a reasonable max size for a PAM image
 
 namespace Orthanc
 {
@@ -181,11 +182,29 @@ namespace Orthanc
     const unsigned int maxValue = LookupIntegerParameter(parameters, "MAXVAL");
     const std::string tupleType = LookupStringParameter(parameters, "TUPLTYPE");
 
+    if (width > 65535 || height > 65535 || channelCount > 4 || maxValue > 65535)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "PAM header values exceed reasonable limits");
+    }
+
     unsigned int bytesPerChannel;
     PixelFormat format;
     GetPixelFormat(format, bytesPerChannel, maxValue, channelCount, tupleType);
 
-    unsigned int pitch = width * channelCount * bytesPerChannel;
+    // unsigned int pitch = width * channelCount * bytesPerChannel;
+    uint64_t pitch = static_cast<uint64_t>(width) * channelCount * bytesPerChannel;
+
+    if (pitch > std::numeric_limits<unsigned int>::max())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "PAM dimensions exceed limits");
+    }
+
+    uint64_t totalSize = pitch * height;
+    if (totalSize > MAX_PAM_IMAGE_BUFFER_SIZE ||
+        static_cast<uint64_t>(static_cast<size_t>(totalSize)) != totalSize)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "PAM image too large");
+    }
 
     if (content_.size() != header.size() + headerDelimiter.size() + pitch * height)
     {
@@ -196,7 +215,7 @@ namespace Orthanc
 
     {
       intptr_t bufferAddr = reinterpret_cast<intptr_t>(&content_[offset]);
-      if((bufferAddr % 8) == 0)
+      if ((bufferAddr % 8) == 0)
         LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr;
       else
         LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr << " (not a multiple of 8!)";
Index: Orthanc-1.12.10/OrthancFramework/Sources/MultiThreading/ReaderWriterLock.h
===================================================================
--- /dev/null
+++ Orthanc-1.12.10/OrthancFramework/Sources/MultiThreading/ReaderWriterLock.h
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../OrthancFramework.h"
+
+#if !defined(__EMSCRIPTEN__)
+// Multithreading is not supported in WebAssembly
+#  include <boost/thread/shared_mutex.hpp>
+#  include <boost/thread/lock_types.hpp>  // For boost::unique_lock<> and boost::shared_lock<>
+#endif
+
+
+namespace Orthanc
+{
+  class ORTHANC_PUBLIC ReaderWriterLock : public boost::noncopyable
+  {
+  private:
+    boost::shared_mutex mutex_;
+
+  public:
+    class ReadLock : public boost::noncopyable
+    {
+    private:
+#if !defined(__EMSCRIPTEN__)
+      boost::shared_lock<boost::shared_mutex> lock_;
+#endif
+
+    public:
+      explicit ReadLock(ReaderWriterLock& that)
+#if !defined(__EMSCRIPTEN__)
+        : lock_(that.mutex_)
+#endif
+      {
+      }
+    };
+
+    class WriteLock : public boost::noncopyable
+    {
+    private:
+#if !defined(__EMSCRIPTEN__)
+      boost::unique_lock<boost::shared_mutex> lock_;
+#endif
+
+    public:
+      explicit WriteLock(ReaderWriterLock& that)
+#if !defined(__EMSCRIPTEN__)
+        : lock_(that.mutex_)
+#endif
+      {
+      }
+    };
+  };
+}
Index: Orthanc-1.12.10/OrthancServer/Sources/ServerContext.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancServer/Sources/ServerContext.cpp
+++ Orthanc-1.12.10/OrthancServer/Sources/ServerContext.cpp
@@ -1926,7 +1926,7 @@ namespace Orthanc
       }
       catch (OrthancException& e)
       {
-        LOG(INFO) << e.GetDetails();
+        LOG(INFO) << "Failed to decode a DICOM frame: " << e.GetDetails();
       }
     }
 
Index: Orthanc-1.12.10/OrthancServer/Sources/main.cpp
===================================================================
--- Orthanc-1.12.10.orig/OrthancServer/Sources/main.cpp
+++ Orthanc-1.12.10/OrthancServer/Sources/main.cpp
@@ -25,6 +25,8 @@
 #include "OrthancRestApi/OrthancRestApi.h"
 
 #include "../../OrthancFramework/Sources/Compatibility.h"
+#include "../../OrthancFramework/Sources/Compression/GzipCompressor.h"
+#include "../../OrthancFramework/Sources/Compression/ZipReader.h"
 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
 #include "../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h"
 #include "../../OrthancFramework/Sources/DicomNetworking/DicomServer.h"
@@ -46,8 +48,8 @@
 #include "OrthancWebDav.h"
 #include "ServerContext.h"
 #include "ServerEnumerations.h"
-#include "ServerJobs/StorageCommitmentScpJob.h"
 #include "ServerJobs/DicomRetrieveScuBaseJob.h"
+#include "ServerJobs/StorageCommitmentScpJob.h"
 #include "ServerToolbox.h"
 #include "StorageCommitmentReports.h"
 
@@ -1094,6 +1096,33 @@ static bool StartHttpServer(ServerContex
       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";
+      }
+
+      const unsigned int maxSizeInArchive = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumFileSizeInArchiveMB", 2048);
+      if (maxSizeInArchive != 0)
+      {
+        LOG(WARNING) << "Limiting on the maximum file size uncompressed from ZIP/gzip archives to " << maxSizeInArchive << "MB";
+        ZipReader::SetMaximumUncompressedFileSize(static_cast<uint64_t>(maxSizeInArchive) *
+                                                  static_cast<uint64_t>(1024 * 1024));
+        GzipCompressor::SetMaximumUncompressedFileSize(static_cast<uint64_t>(maxSizeInArchive) *
+                                                       static_cast<uint64_t>(1024 * 1024));
+      }
+      else
+      {
+        LOG(WARNING) << "No limit on the maximum file size uncompressed from ZIP/gzip archives";
+      }
+
       // Let's assume that the HTTP server is secure
       context.SetHttpServerSecure(true);
 
